@xiashe/skill 0.1.22 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,7 +31,8 @@ The user-facing product flow should point creators at the official publish Markd
31
31
  node packages/xiashe-skill-cli/bin/xiashe-skill.mjs --help
32
32
  node packages/xiashe-skill-cli/bin/xiashe-skill.mjs inspect .
33
33
  node packages/xiashe-skill-cli/bin/xiashe-skill.mjs doctor .
34
- node packages/xiashe-skill-cli/bin/xiashe-skill.mjs publish . --code XS-XXXX-XXXX --to xiashe,red
34
+ node packages/xiashe-skill-cli/bin/xiashe-skill.mjs preflight --claim-url https://<registry-host>/registry/skill/claim --json
35
+ node packages/xiashe-skill-cli/bin/xiashe-skill.mjs publish . --code XS-XXXX-XXXX --claim-url https://<registry-host>/registry/skill/claim --to xiashe,red
35
36
  node packages/xiashe-skill-cli/bin/xiashe-skill.mjs setup . --code XS-XXXX-XXXX --hub all
36
37
  node packages/xiashe-skill-cli/bin/xiashe-skill.mjs setup . --code XS-XXXX-XXXX --hub red
37
38
  node packages/xiashe-skill-cli/bin/xiashe-skill.mjs verify . --hub red --dry-run --json
@@ -42,3 +43,17 @@ node packages/xiashe-skill-cli/bin/xiashe-skill.mjs prompt . --hub red --source-
42
43
  node packages/xiashe-skill-cli/bin/xiashe-skill.mjs snippet . --target js
43
44
  node packages/xiashe-skill-cli/bin/xiashe-skill.mjs track . --event skill_invoked --hub red --dry-run --json
44
45
  ```
46
+
47
+ Use the `claimUrl` returned with a dashboard dynamic code exactly as issued. `publish` runs the same preflight automatically before it consumes a code: it verifies the exact registry origin, protocol version, and environment id exposed by that deployment. A dynamic code without an explicit `--claim-url` is rejected before the code is consumed, so development, CN, and global codes cannot accidentally be sent to the production registry. For a resumed upload, the CLI uses the registry origin stored in the local private manifest and preflights it again.
48
+
49
+ ## Reliable publish and recovery
50
+
51
+ The publish path keeps three different values separate:
52
+
53
+ - **Source fingerprint** — a stable SHA-256 of reviewed Skill source files. It is the idempotency key for the publish job.
54
+ - **Artifact checksum** — SHA-256 of the generated `.tgz` bytes. The backend compares it only with storage's checksum for those exact bytes.
55
+ - **Storage checksum** — the checksum reported after upload. It must match the artifact checksum; it is never compared with the source fingerprint.
56
+
57
+ If the network breaks after the server receives a dynamic-code claim, rerun the exact same `publish` command before that code expires. The registry accepts only the same code and source fingerprint, returns the original local manifest credentials, and reuses the same package job. It never creates a second Skill or a second upload job. Do not generate a new dashboard code merely because a local HTTP response was interrupted.
58
+
59
+ If an upload has already started, rerun the same command. The CLI queries the existing package job first and either reports success, waits for processing, or resumes the failed upload with the same public token and source fingerprint.
@@ -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.23';
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');
@@ -102,8 +102,6 @@ const HANDOFF_FILE = 'UPLOAD_HANDOFF.md';
102
102
  const DEFAULT_REGISTRY_URL = process.env.XIASHE_SKILL_REGISTRY_URL || `${DEFAULT_BASE_URL}/registry/skill-events`;
103
103
  const DEFAULT_AGENT_ACK_URL = process.env.XIASHE_SKILL_AGENT_ACK_URL || `${DEFAULT_BASE_URL}/registry/agent-ack`;
104
104
  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
105
  const EVENT_SCHEMA_VERSION = process.env.XIASHE_SKILL_EVENT_SCHEMA_VERSION || (REGISTRY_PROVIDER === 'agentpie'
108
106
  ? 'agentpie.skill.event.v1'
109
107
  : 'xiashe.skill.event.v1');
@@ -185,8 +183,8 @@ const PLATFORM_REVIEW_PROFILES = {
185
183
  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
184
  runtimeDataPolicy: 'agent_ack_supported',
187
185
  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'],
186
+ 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.`,
187
+ localOnlyRegistryFiles: [WORK_DIR, `${WORK_DIR}/${MANIFEST_FILE}`, `${WORK_DIR}/runtime-events.js`, 'internal handoff/checklists'],
190
188
  forbiddenPublicFiles: ['credentials', 'cookies', 'browser/account sessions', 'public tokens', 'signing secrets', 'hidden telemetry'],
191
189
  safetyChecklist: [
192
190
  '功能描述真实、清晰、有实际使用价值,不夸大或误导用户。',
@@ -205,7 +203,7 @@ const PLATFORM_REVIEW_PROFILES = {
205
203
  disclosurePlacement: ['SKILL.md', 'README', 'platform description field'],
206
204
  runtimeDataPolicy: 'agent_ack_supported',
207
205
  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.',
206
+ 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
207
  forbiddenPublicFiles: [WORK_DIR, `${WORK_DIR}/${MANIFEST_FILE}`, `${WORK_DIR}/runtime-events.js`, 'public tokens', 'signing secrets']
210
208
  },
211
209
  skillhub: {
@@ -285,7 +283,7 @@ const PLATFORM_REVIEW_PROFILES = {
285
283
  disclosurePlacement: ['README', 'SKILL.md', 'platform description field'],
286
284
  runtimeDataPolicy: 'runtime_if_http_or_mcp',
287
285
  defaultDataSource: 'attribution',
288
- allowedFilePolicy: 'Follow the target platform official rules first; merge only safe XiaShe disclosure and callbacks.',
286
+ allowedFilePolicy: `Follow the target platform official rules first; merge only safe ${PRODUCT_NAME} disclosure and callbacks.`,
289
287
  forbiddenPublicFiles: ['public tokens in public docs', 'signing secrets', 'hidden background processes']
290
288
  }
291
289
  };
@@ -317,6 +315,7 @@ Usage:
317
315
  ${COMMAND_NAME} setup [skill-dir] --code <dynamic-code> [--hub all|${FIRST_PARTY_HUB}|red]
318
316
  ${COMMAND_NAME} setup [skill-dir] --public-token <token> --skill-id <id> [--hub all|${FIRST_PARTY_HUB}|red]
319
317
  ${COMMAND_NAME} doctor [skill-dir] [--json]
318
+ ${COMMAND_NAME} preflight --claim-url <url> [--json]
320
319
  ${COMMAND_NAME} verify [skill-dir] [--hub <hub>] [--scenario <label>] [--dry-run]
321
320
  ${COMMAND_NAME} attach [skill-dir] --code <dynamic-code> [--name <name>]
322
321
  ${COMMAND_NAME} attach [skill-dir] --public-token <token> [--skill-id <id>] [--name <name>]
@@ -328,6 +327,7 @@ Options:
328
327
  --public-token <token> Public write token issued by ${PRODUCT_NAME} registry.
329
328
  --code <dynamic-code> One-time ${PRODUCT_NAME} dashboard code used to claim registry identity.
330
329
  --claim-url <url> Code claim endpoint. Defaults to XIASHE_SKILL_CLAIM_URL or ${DEFAULT_CLAIM_URL}
330
+ --registry-doctor-url <url> Registry preflight endpoint. Defaults to the claim URL origin.
331
331
  --skill-id <id> Public ${PRODUCT_NAME} Skill registry id.
332
332
  --registry-url <url> Event endpoint written to the manifest.
333
333
  --agent-ack-url <url> No-secret Agent Ack endpoint written to the local manifest.
@@ -356,11 +356,13 @@ Options:
356
356
  --source-url <url> User-provided source URL an Agent can use during third-party upload.
357
357
  --package-url <url> Deprecated alias for --source-url.
358
358
  --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}
359
+ --package-upload-url <url> Package upload ticket endpoint on the same registry origin.
360
+ --package-artifact-url <url> Package artifact attach endpoint on the same registry origin.
361
361
  --out <path> Output prompt or snippet file.
362
362
  --out-dir <path> Output directory for setup artifacts. Defaults to ${WORK_DIR}/.
363
363
  --no-package-upload Skip automatic ${PRODUCT_NAME} complete package upload.
364
+ --paid-skill Treat third-party packages as protected paid-Skill connectors only.
365
+ --protected-skill Alias for --paid-skill. Never upload full paid prompt/source to third-party hubs.
364
366
  --embed-skill-md Also write a clearly marked registry section into SKILL.md.
365
367
  --no-skill-md Do not write registry text into SKILL.md. Red hub embeds by default.
366
368
  --no-snippet Setup should skip writing the runtime analytics snippet.
@@ -379,7 +381,7 @@ function fail(message, code = 1) {
379
381
 
380
382
  function parseArgs(argv) {
381
383
  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']);
384
+ 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
385
  for (let i = 0; i < argv.length; i += 1) {
384
386
  const value = argv[i];
385
387
  if (value === '--help' || value === '-h') {
@@ -534,7 +536,7 @@ function latestVersionEndpoint(inspectedOrRegistry = {}) {
534
536
 
535
537
  function creatorFooterTemplate(inspected, language = 'zh') {
536
538
  const registry = inspected.registry || {};
537
- const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
539
+ const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
538
540
  const creatorName = normalizeText(registry.creatorProfile?.displayName || registry.creatorDisplayName || registry.creatorName, 120) || '待创作者补充';
539
541
  const creatorSignal = normalizeText(registry.creatorProfile?.xiaSignal || registry.creatorXiaSignal || registry.xiaSignal, 80);
540
542
  if (language === 'en') {
@@ -632,8 +634,48 @@ function isLikelyEntrypointFile(filePath) {
632
634
  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
635
  }
634
636
 
637
+ function truthyPaidValue(value) {
638
+ if (value === true) return true;
639
+ if (typeof value === 'number') return value > 0;
640
+ const text = normalizeText(value, 120).toLowerCase();
641
+ return [
642
+ 'paid',
643
+ 'protected',
644
+ 'requires_payment',
645
+ 'requires-entitlement',
646
+ 'entitlement',
647
+ 'alipay_ai_collect',
648
+ 'stripe',
649
+ 'charging',
650
+ '收费',
651
+ '付费'
652
+ ].includes(text);
653
+ }
654
+
655
+ function isProtectedPaidSkill(inspected) {
656
+ const registry = inspected.registry || {};
657
+ const candidates = [
658
+ inspected.isPaid,
659
+ inspected.requiresEntitlement,
660
+ inspected.billingModel,
661
+ inspected.monetizationMode,
662
+ inspected.accessMode,
663
+ registry.isPaid,
664
+ registry.paidAccess,
665
+ registry.requiresEntitlement,
666
+ registry.billingModel,
667
+ registry.monetizationMode,
668
+ registry.accessMode,
669
+ registry.payment?.required,
670
+ registry.payment?.channel
671
+ ];
672
+ return candidates.some(truthyPaidValue);
673
+ }
674
+
635
675
  function packagePlanForHub(inspected, hub) {
636
676
  const profile = reviewProfileForHub(hub);
677
+ const protectedPaidSkill = isProtectedPaidSkill(inspected);
678
+ const thirdPartyProtectedPackage = protectedPaidSkill && !['agentpie', 'xiashe'].includes(profile.hub);
637
679
  const localOnly = [];
638
680
  const candidateUploadFiles = [];
639
681
  const forbiddenPatterns = [
@@ -654,6 +696,14 @@ function packagePlanForHub(inspected, hub) {
654
696
  localOnly.push(file.path);
655
697
  continue;
656
698
  }
699
+ if (thirdPartyProtectedPackage) {
700
+ if (isPublicRuntimeProtocolFile(file.path) || /^README(\.[a-z0-9]+)?$/i.test(file.path)) {
701
+ candidateUploadFiles.push(file.path);
702
+ } else {
703
+ localOnly.push(file.path);
704
+ }
705
+ continue;
706
+ }
657
707
  if (profile.packageMode === 'full_skill_package') {
658
708
  candidateUploadFiles.push(file.path);
659
709
  continue;
@@ -683,6 +733,8 @@ function packagePlanForHub(inspected, hub) {
683
733
  return {
684
734
  hub: profile.hub,
685
735
  label: profile.label,
736
+ protectedPaidSkill,
737
+ thirdPartyProtectedPackage,
686
738
  reviewProfile: profile,
687
739
  dataSourcePolicy: {
688
740
  runtime: 'Only real Skill execution callbacks, MCP/HTTP/API Tool calls, webhooks, or external Agent ack.',
@@ -698,17 +750,24 @@ function packagePlanForHub(inspected, hub) {
698
750
  `${WORK_DIR}/runtime-events.js`,
699
751
  `${WORK_DIR}/platform-profiles.json`
700
752
  ])).sort(),
701
- disclosureTargets: profile.disclosurePlacement,
753
+ disclosureTargets: thirdPartyProtectedPackage
754
+ ? ['README', `${PUBLIC_PROTOCOL_DIR}/runtime.yaml`, `${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md`, `${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md`, 'platform private/admin field']
755
+ : profile.disclosurePlacement,
702
756
  forbiddenPatterns,
703
757
  safetyChecklist: profile.safetyChecklist || [],
704
758
  requiresUserConfirmation: true,
705
759
  notes: [
760
+ thirdPartyProtectedPackage
761
+ ? `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.`
762
+ : '',
706
763
  profile.allowedFilePolicy,
707
764
  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.`
765
+ ? `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
766
  : 'Confirm platform file rules before uploading auxiliary registry metadata.',
710
767
  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.`
768
+ ? thirdPartyProtectedPackage
769
+ ? `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.`
770
+ : `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
771
  : '',
713
772
  profile.runtimeDataPolicy === 'attribution_only_by_default'
714
773
  ? 'Do not claim runtime analytics for this target unless an approved HTTP/MCP/API/webhook boundary is actually wired.'
@@ -770,12 +829,22 @@ async function inferSkillMetadata(root, flags = {}) {
770
829
  skillKey: safeSkillKey(flags['skill-key'] || manifest?.skillKey || packageJson?.name || name),
771
830
  description: normalizeText(flags.description || manifest?.description || packageJson?.description || firstParagraph || '', 500),
772
831
  version: normalizeText(flags['skill-version'] || packageJson?.version || manifest?.version || '0.1.0', 80),
832
+ isPaid: Boolean(flags['paid-skill'] || flags['protected-skill'] || manifest?.isPaid || manifest?.registry?.isPaid || manifest?.registry?.paidAccess),
833
+ requiresEntitlement: Boolean(flags['paid-skill'] || flags['protected-skill'] || manifest?.requiresEntitlement || manifest?.registry?.requiresEntitlement),
834
+ billingModel: normalizeText(flags['billing-model'] || manifest?.billingModel || manifest?.registry?.billingModel, 80) || null,
835
+ monetizationMode: normalizeText(flags['monetization-mode'] || manifest?.monetizationMode || manifest?.registry?.monetizationMode, 80) || null,
836
+ accessMode: normalizeText(flags['access-mode'] || manifest?.accessMode || manifest?.registry?.accessMode, 80) || null,
773
837
  registry: manifest?.registry || null
774
838
  };
775
839
  }
776
840
 
777
841
  function shouldIgnore(name) {
778
- return DEFAULT_IGNORE.has(name) || name.endsWith('~') || name.endsWith('.tmp') || name.endsWith('.log');
842
+ return DEFAULT_IGNORE.has(name) ||
843
+ name.startsWith('.env.') ||
844
+ /\.(pem|key)$/i.test(name) ||
845
+ name.endsWith('~') ||
846
+ name.endsWith('.tmp') ||
847
+ name.endsWith('.log');
779
848
  }
780
849
 
781
850
  async function walkFiles(root, current = root, out = []) {
@@ -821,6 +890,7 @@ async function postJson(url, body, timeoutMs = 20_000) {
821
890
  method: 'POST',
822
891
  headers: { 'content-type': 'application/json' },
823
892
  body: JSON.stringify(body),
893
+ redirect: 'error',
824
894
  signal: controller.signal
825
895
  });
826
896
  const text = await response.text();
@@ -845,13 +915,166 @@ async function postJson(url, body, timeoutMs = 20_000) {
845
915
  }
846
916
  }
847
917
 
848
- function urlForRegistryPath(baseUrl, pathname, fallback) {
849
- const base = normalizeText(baseUrl, 1_000) || fallback;
918
+ function registryClaimEndpoint(claimUrl) {
919
+ const raw = normalizeText(claimUrl, 1_000) || DEFAULT_CLAIM_URL;
920
+ let url;
921
+ try {
922
+ url = new URL(raw);
923
+ } catch {
924
+ throw new Error('CREATOR_SKILL_REGISTRY_URL_INVALID: claim URL is not a valid URL.');
925
+ }
926
+ const pathname = url.pathname.replace(/\/+$/, '');
927
+ if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password || url.search || url.hash || pathname !== '/registry/skill/claim') {
928
+ throw new Error('CREATOR_SKILL_REGISTRY_URL_INVALID: claim URL must be a clean https://<trusted-host>/registry/skill/claim endpoint.');
929
+ }
930
+ const host = url.hostname.toLowerCase();
931
+ const isLocalhost = host === 'localhost' || host === '127.0.0.1' || host === '::1' || host.endsWith('.localhost');
932
+ if (url.protocol !== 'https:' && !(isLocalhost && process.env.XIASHE_SKILL_ALLOW_INSECURE_LOCALHOST === '1')) {
933
+ throw new Error('CREATOR_SKILL_REGISTRY_URL_INSECURE: only HTTPS registry endpoints are accepted.');
934
+ }
935
+ return url;
936
+ }
937
+
938
+ function explicitClaimUrl(flags = {}) {
939
+ return normalizeText(
940
+ flags['claim-url'] ||
941
+ process.env.XIASHE_SKILL_CLAIM_URL ||
942
+ LOCAL_ENV_DEFAULTS.XIASHE_SKILL_CLAIM_URL,
943
+ 1_000
944
+ );
945
+ }
946
+
947
+ function claimUrlFromRegistryOrigin(registryOrigin) {
948
+ const origin = normalizeBaseUrl(registryOrigin);
949
+ return origin ? `${origin}/registry/skill/claim` : '';
950
+ }
951
+
952
+ function claimUrlForCode(flags = {}) {
953
+ const claimUrl = explicitClaimUrl(flags);
954
+ if (!claimUrl) {
955
+ throw new Error(
956
+ 'CREATOR_SKILL_CLAIM_URL_REQUIRED: a dynamic code must be used with the exact --claim-url issued by the dashboard. ' +
957
+ 'This prevents dev, CN, and global registry codes from being sent to the wrong environment.'
958
+ );
959
+ }
960
+ return claimUrl;
961
+ }
962
+
963
+ function claimUrlForExistingManifest(flags = {}, registry = {}) {
964
+ return (
965
+ explicitClaimUrl(flags) ||
966
+ claimUrlFromRegistryOrigin(registry.registryOrigin) ||
967
+ DEFAULT_CLAIM_URL
968
+ );
969
+ }
970
+
971
+ function configuredRegistryOrigins() {
972
+ const supplied = [
973
+ process.env.XIASHE_SKILL_TRUSTED_REGISTRY_ORIGINS,
974
+ LOCAL_ENV_DEFAULTS.XIASHE_SKILL_TRUSTED_REGISTRY_ORIGINS,
975
+ DEFAULT_BASE_URL
976
+ ]
977
+ .filter(Boolean)
978
+ .flatMap((value) => String(value).split(','))
979
+ .map((value) => normalizeBaseUrl(value))
980
+ .filter(Boolean)
981
+ .map((value) => new URL(value).origin.toLowerCase());
982
+ return new Set(supplied);
983
+ }
984
+
985
+ function isTrustedRegistryClaimUrl(claimUrl) {
986
+ const endpoint = registryClaimEndpoint(claimUrl);
987
+ const host = endpoint.hostname.toLowerCase();
988
+ const isOfficialHost =
989
+ host === 'xiashe.chat' || host.endsWith('.xiashe.chat') ||
990
+ host === 'agentpie.app' || host.endsWith('.agentpie.app');
991
+ const isLocalhost = host === 'localhost' || host === '127.0.0.1' || host === '::1' || host.endsWith('.localhost');
992
+ const trustedOrigins = configuredRegistryOrigins();
993
+ if (!isOfficialHost && !isLocalhost && !trustedOrigins.has(endpoint.origin.toLowerCase())) {
994
+ 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.');
995
+ }
996
+ return endpoint;
997
+ }
998
+
999
+ async function getJson(url, timeoutMs = 12_000) {
1000
+ const controller = new AbortController();
1001
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1002
+ try {
1003
+ const response = await fetch(url, { method: 'GET', redirect: 'error', signal: controller.signal });
1004
+ const text = await response.text();
1005
+ let payload = {};
1006
+ try {
1007
+ payload = text ? JSON.parse(text) : {};
1008
+ } catch {
1009
+ throw new Error(`Bad JSON response from ${url}`);
1010
+ }
1011
+ if (!response.ok || payload.ok === false) {
1012
+ throw new Error(payload.error || payload.message || `HTTP ${response.status}`);
1013
+ }
1014
+ return payload;
1015
+ } catch (error) {
1016
+ const message = error instanceof Error ? error.message : String(error || 'unknown error');
1017
+ if (!isTransportError(error)) throw error;
1018
+ throw new Error(`Network request failed for ${url}: ${message}`);
1019
+ } finally {
1020
+ clearTimeout(timer);
1021
+ }
1022
+ }
1023
+
1024
+ async function preflightRegistryClaim(claimUrl, flags = {}) {
1025
+ const endpoint = isTrustedRegistryClaimUrl(claimUrl);
1026
+ const expectedOrigin = endpoint.origin.replace(/\/+$/, '');
1027
+ const doctorUrl = registryEndpointUrl(
1028
+ endpoint,
1029
+ '/registry/skill/doctor',
1030
+ flags['registry-doctor-url'],
1031
+ 'registry doctor'
1032
+ );
1033
+ const doctor = await getJson(doctorUrl, Number(flags['timeout-ms'] || 20_000));
1034
+ const registryOrigin = normalizeBaseUrl(doctor.registryOrigin);
1035
+ const protocolVersion = normalizeText(doctor.protocolVersion, 120);
1036
+ const environmentId = normalizeText(doctor.environmentId, 160);
1037
+ if (!registryOrigin || registryOrigin !== expectedOrigin) {
1038
+ 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.`);
1039
+ }
1040
+ if (!protocolVersion || !environmentId) {
1041
+ throw new Error('CREATOR_SKILL_REGISTRY_DOCTOR_INCOMPLETE: update the registry backend before using a dynamic code.');
1042
+ }
1043
+ return {
1044
+ ok: true,
1045
+ claimUrl: endpoint.toString(),
1046
+ doctorUrl,
1047
+ registryOrigin,
1048
+ protocolVersion,
1049
+ environmentId,
1050
+ provider: normalizeText(doctor.provider, 80) || REGISTRY_PROVIDER
1051
+ };
1052
+ }
1053
+
1054
+ function registryEndpointUrl(claimEndpoint, pathname, suppliedUrl, label) {
1055
+ const endpoint = isTrustedRegistryClaimUrl(claimEndpoint);
1056
+ const expected = new URL(pathname, endpoint.origin);
1057
+ const raw = normalizeText(suppliedUrl, 1_000);
1058
+ if (!raw) return expected.toString();
1059
+ let candidate;
850
1060
  try {
851
- return new URL(pathname, base).toString();
1061
+ candidate = new URL(raw);
852
1062
  } catch {
853
- return fallback;
1063
+ throw new Error(`CREATOR_SKILL_REGISTRY_URL_INVALID: ${label} URL is not valid.`);
1064
+ }
1065
+ if (
1066
+ candidate.origin !== endpoint.origin ||
1067
+ candidate.pathname.replace(/\/+$/, '') !== expected.pathname.replace(/\/+$/, '') ||
1068
+ candidate.username ||
1069
+ candidate.password ||
1070
+ candidate.search ||
1071
+ candidate.hash
1072
+ ) {
1073
+ throw new Error(
1074
+ `CREATOR_SKILL_REGISTRY_ENDPOINT_MISMATCH: ${label} must stay on the exact registry origin and path bound to the dynamic code.`
1075
+ );
854
1076
  }
1077
+ return candidate.toString();
855
1078
  }
856
1079
 
857
1080
  function shouldIgnorePackageEntry(relativePath, entryName, isDirectory = false) {
@@ -936,11 +1159,14 @@ function tarHeader(file, size) {
936
1159
  const header = Buffer.alloc(512, 0);
937
1160
  const split = splitTarPath(file.relative);
938
1161
  header.write(split.name, 0, 100, 'utf8');
939
- header.write(octal(file.mode & 0o777 || 0o644, 8), 100, 8, 'ascii');
1162
+ // Keep archive bytes reproducible. Local mtimes and group permissions are
1163
+ // machine-specific and must not make a valid retry look like a new package.
1164
+ const normalizedMode = file.mode & 0o111 ? 0o755 : 0o644;
1165
+ header.write(octal(normalizedMode, 8), 100, 8, 'ascii');
940
1166
  header.write(octal(0, 8), 108, 8, 'ascii');
941
1167
  header.write(octal(0, 8), 116, 8, 'ascii');
942
1168
  header.write(octal(size, 12), 124, 12, 'ascii');
943
- header.write(octal(Math.floor(new Date(file.mtime).getTime() / 1000), 12), 136, 12, 'ascii');
1169
+ header.write(octal(0, 12), 136, 12, 'ascii');
944
1170
  header.fill(0x20, 148, 156);
945
1171
  header.write('0', 156, 1, 'ascii');
946
1172
  header.write('ustar', 257, 6, 'ascii');
@@ -975,7 +1201,10 @@ async function createTarGz(files) {
975
1201
  }
976
1202
  chunks.push(Buffer.alloc(1024, 0));
977
1203
  const tar = Buffer.concat(chunks);
978
- const gzipped = await gzipAsync(tar, { level: 9 });
1204
+ // Node's current gzip implementation defaults to deterministic metadata,
1205
+ // but make the invariant explicit so retries on another host have the same
1206
+ // archive checksum when the files are unchanged.
1207
+ const gzipped = await gzipAsync(tar, { level: 9, mtime: 0 });
979
1208
  if (gzipped.length > MAX_XIASHE_PACKAGE_BYTES) {
980
1209
  throw new Error(`Package artifact exceeds ${Math.round(MAX_XIASHE_PACKAGE_BYTES / (1024 * 1024))}MB.`);
981
1210
  }
@@ -1028,6 +1257,7 @@ async function uploadBytesToStorage(uploadUrl, bytes, contentType, timeoutMs = 6
1028
1257
  method: 'POST',
1029
1258
  headers: { 'content-type': contentType || 'application/octet-stream' },
1030
1259
  body: bytes,
1260
+ redirect: 'error',
1031
1261
  signal: controller.signal
1032
1262
  });
1033
1263
  const text = await response.text();
@@ -1056,6 +1286,29 @@ async function uploadBytesToStorage(uploadUrl, bytes, contentType, timeoutMs = 6
1056
1286
  }
1057
1287
  }
1058
1288
 
1289
+ async function getPackagePublishStatus(claimUrl, publicToken, uploadJobId, idempotencyKey, flags = {}) {
1290
+ return await postJson(
1291
+ registryEndpointUrl(claimUrl, '/registry/skill/package-status', flags['package-status-url'], 'package status'),
1292
+ {
1293
+ publicToken,
1294
+ uploadJobId: normalizeText(uploadJobId, 160) || undefined,
1295
+ idempotencyKey: normalizeText(idempotencyKey, 220) || undefined
1296
+ },
1297
+ Number(flags['timeout-ms'] || 20_000)
1298
+ );
1299
+ }
1300
+
1301
+ async function waitForPackagePublish(claimUrl, publicToken, uploadJobId, idempotencyKey, flags = {}) {
1302
+ const timeoutMs = Math.max(5_000, Math.min(90_000, Number(flags['publish-status-timeout-ms'] || 45_000)));
1303
+ const deadline = Date.now() + timeoutMs;
1304
+ let status = await getPackagePublishStatus(claimUrl, publicToken, uploadJobId, idempotencyKey, flags);
1305
+ while (status.found && (status.state === 'queued' || status.state === 'processing') && Date.now() < deadline) {
1306
+ await new Promise((resolve) => setTimeout(resolve, 1_000));
1307
+ status = await getPackagePublishStatus(claimUrl, publicToken, uploadJobId, idempotencyKey, flags);
1308
+ }
1309
+ return status;
1310
+ }
1311
+
1059
1312
  async function uploadXiashePackageArtifact(root, flags, manifestResult) {
1060
1313
  if (flags.dryRun || flags['no-package-upload'] || flags['no-xiashe-package-upload']) {
1061
1314
  return {
@@ -1075,32 +1328,86 @@ async function uploadXiashePackageArtifact(root, flags, manifestResult) {
1075
1328
  normalizeText(manifestResult.manifest?.sourceFingerprint?.sha256, 128) ||
1076
1329
  normalizeText(inspected.package?.sha256, 128) ||
1077
1330
  artifact.sourceSha256;
1078
- const claimUrl = normalizeText(flags['claim-url'], 800) || DEFAULT_CLAIM_URL;
1331
+ // A resumed upload has no dynamic code to protect it. Bind it back to the
1332
+ // origin recorded in the private manifest instead of silently falling back
1333
+ // to the production registry, then verify that origin before any write.
1334
+ const claimUrl = claimUrlForExistingManifest(flags, registry);
1335
+ const registryPreflight = await preflightRegistryClaim(claimUrl, flags);
1336
+ const expectedRegistryOrigin = normalizeBaseUrl(registry.registryOrigin);
1337
+ const expectedEnvironmentId = normalizeText(registry.environmentId, 160);
1338
+ if (
1339
+ (expectedRegistryOrigin && expectedRegistryOrigin !== registryPreflight.registryOrigin) ||
1340
+ (expectedEnvironmentId && expectedEnvironmentId !== registryPreflight.environmentId)
1341
+ ) {
1342
+ throw new Error(
1343
+ 'CREATOR_SKILL_REGISTRY_ENVIRONMENT_MISMATCH: the local manifest belongs to a different registry environment. ' +
1344
+ 'Do not upload or claim a new code; switch to the original environment or start a new dashboard task.'
1345
+ );
1346
+ }
1079
1347
  const uploadTicket =
1080
1348
  manifestResult.claimPackageArtifactUpload ||
1081
1349
  await postJson(
1082
- normalizeText(flags['package-upload-url'], 1_000) || urlForRegistryPath(claimUrl, '/registry/skill/package-upload-url', DEFAULT_PACKAGE_UPLOAD_URL),
1350
+ registryEndpointUrl(claimUrl, '/registry/skill/package-upload-url', flags['package-upload-url'], 'package upload'),
1083
1351
  {
1084
1352
  publicToken,
1085
1353
  idempotencyKey: registrySourceSha256
1086
1354
  },
1087
1355
  Number(flags['timeout-ms'] || 20_000)
1088
1356
  );
1089
- const uploadUrl = normalizeText(uploadTicket?.uploadUrl, 2_000);
1357
+ let activeUploadTicket = uploadTicket;
1358
+ let uploadUrl = normalizeText(activeUploadTicket?.uploadUrl, 2_000);
1090
1359
  if (!uploadUrl) {
1091
- throw new Error('XiaShe package upload URL was not returned by registry.');
1360
+ const priorStatus = await waitForPackagePublish(
1361
+ claimUrl,
1362
+ publicToken,
1363
+ normalizeText(activeUploadTicket?.uploadJobId, 160),
1364
+ registrySourceSha256,
1365
+ flags
1366
+ );
1367
+ if (priorStatus.found && priorStatus.state === 'succeeded') {
1368
+ return {
1369
+ ok: true,
1370
+ skipped: false,
1371
+ resumed: true,
1372
+ artifactPath: artifact.artifactPath,
1373
+ fileName: artifact.fileName,
1374
+ sizeBytes: artifact.sizeBytes,
1375
+ sourceSha256: registrySourceSha256,
1376
+ archiveSourceSha256: artifact.sourceSha256,
1377
+ artifactSha256: artifact.artifactSha256,
1378
+ fileCount: artifact.fileCount,
1379
+ storageId: priorStatus.storageId ?? null,
1380
+ publishStatus: priorStatus.status,
1381
+ uploadJobId: priorStatus.uploadJobId,
1382
+ attachResult: priorStatus
1383
+ };
1384
+ }
1385
+ if (priorStatus.found && (priorStatus.state === 'queued' || priorStatus.state === 'processing')) {
1386
+ 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.`);
1387
+ }
1388
+ activeUploadTicket = await postJson(
1389
+ registryEndpointUrl(claimUrl, '/registry/skill/package-upload-url', flags['package-upload-url'], 'package upload'),
1390
+ { publicToken, idempotencyKey: registrySourceSha256 },
1391
+ Number(flags['timeout-ms'] || 20_000)
1392
+ );
1393
+ uploadUrl = normalizeText(activeUploadTicket?.uploadUrl, 2_000);
1394
+ if (!uploadUrl) {
1395
+ 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.`);
1396
+ }
1092
1397
  }
1093
1398
  const bytes = await readFile(artifact.artifactPath);
1094
1399
  const uploaded = await uploadBytesToStorage(uploadUrl, bytes, artifact.contentType, Number(flags['upload-timeout-ms'] || 90_000));
1095
1400
  const attachResult = await postJson(
1096
- normalizeText(flags['package-artifact-url'], 1_000) || urlForRegistryPath(claimUrl, '/registry/skill/package-artifact', DEFAULT_PACKAGE_ARTIFACT_URL),
1401
+ registryEndpointUrl(claimUrl, '/registry/skill/package-artifact', flags['package-artifact-url'], 'package artifact'),
1097
1402
  {
1098
1403
  publicToken,
1099
1404
  storageId: uploaded.storageId,
1100
- uploadJobId: normalizeText(uploadTicket?.uploadJobId, 160) || undefined,
1405
+ uploadJobId: normalizeText(activeUploadTicket?.uploadJobId, 160) || undefined,
1101
1406
  idempotencyKey: registrySourceSha256,
1102
1407
  fileName: artifact.fileName,
1103
1408
  contentType: artifact.contentType,
1409
+ sourceSha256: registrySourceSha256,
1410
+ artifactSha256: artifact.artifactSha256,
1104
1411
  packageSha256: registrySourceSha256,
1105
1412
  packageFileCount: artifact.fileCount,
1106
1413
  packageTotalBytes: artifact.sourceTotalBytes,
@@ -1113,6 +1420,16 @@ async function uploadXiashePackageArtifact(root, flags, manifestResult) {
1113
1420
  },
1114
1421
  Number(flags['timeout-ms'] || 20_000)
1115
1422
  );
1423
+ const finalStatus = await waitForPackagePublish(
1424
+ claimUrl,
1425
+ publicToken,
1426
+ normalizeText(attachResult.uploadJobId || activeUploadTicket?.uploadJobId, 160),
1427
+ registrySourceSha256,
1428
+ flags
1429
+ );
1430
+ if (finalStatus.found && finalStatus.state === 'failed') {
1431
+ 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.`);
1432
+ }
1116
1433
  return {
1117
1434
  ok: true,
1118
1435
  skipped: false,
@@ -1120,13 +1437,14 @@ async function uploadXiashePackageArtifact(root, flags, manifestResult) {
1120
1437
  fileName: artifact.fileName,
1121
1438
  sizeBytes: artifact.sizeBytes,
1122
1439
  sourceSha256: registrySourceSha256,
1123
- packageSourceSha256: artifact.sourceSha256,
1440
+ archiveSourceSha256: artifact.sourceSha256,
1124
1441
  artifactSha256: artifact.artifactSha256,
1125
1442
  fileCount: artifact.fileCount,
1126
1443
  storageId: uploaded.storageId,
1127
- publishStatus: attachResult.publishStatus ?? null,
1128
- uploadJobId: attachResult.uploadJobId ?? normalizeText(uploadTicket?.uploadJobId, 160) ?? null,
1129
- attachResult
1444
+ publishStatus: finalStatus.status ?? attachResult.publishStatus ?? null,
1445
+ uploadJobId: attachResult.uploadJobId ?? normalizeText(activeUploadTicket?.uploadJobId, 160) ?? null,
1446
+ attachResult,
1447
+ finalStatus
1130
1448
  };
1131
1449
  }
1132
1450
 
@@ -1155,9 +1473,19 @@ async function inspectSkill(root, flags = {}) {
1155
1473
  async function writeManifest(root, flags) {
1156
1474
  const inspected = await inspectSkill(root, flags);
1157
1475
  const code = normalizeText(flags.code, 80);
1476
+ const existingRegistry = inspected.registry || {};
1477
+ const suppliedPublicToken = normalizeText(flags['public-token'], 512);
1478
+ if (!code && suppliedPublicToken && !explicitClaimUrl(flags) && !claimUrlFromRegistryOrigin(existingRegistry.registryOrigin)) {
1479
+ throw new Error(
1480
+ 'CREATOR_SKILL_CLAIM_URL_REQUIRED: a new manifest created from --public-token must include --claim-url. ' +
1481
+ 'Existing manifests resume through their saved registry origin.'
1482
+ );
1483
+ }
1484
+ const claimUrl = code ? claimUrlForCode(flags) : claimUrlForExistingManifest(flags, existingRegistry);
1485
+ const registryPreflight = code ? await preflightRegistryClaim(claimUrl, flags) : null;
1158
1486
  const claim = code
1159
1487
  ? await postJson(
1160
- normalizeText(flags['claim-url'], 800) || DEFAULT_CLAIM_URL,
1488
+ registryPreflight.claimUrl,
1161
1489
  {
1162
1490
  code,
1163
1491
  skill: {
@@ -1165,6 +1493,9 @@ async function writeManifest(root, flags) {
1165
1493
  skillKey: inspected.skillKey,
1166
1494
  description: inspected.description,
1167
1495
  version: inspected.version,
1496
+ sourceSha256: inspected.package.sha256,
1497
+ // Compatibility with registries published before v2. The value is
1498
+ // still a source fingerprint, never a compressed archive hash.
1168
1499
  packageSha256: inspected.package.sha256,
1169
1500
  packageFileCount: inspected.package.fileCount,
1170
1501
  packageTotalBytes: inspected.package.totalBytes
@@ -1177,7 +1508,13 @@ async function writeManifest(root, flags) {
1177
1508
  Number(flags['timeout-ms'] || 20_000)
1178
1509
  )
1179
1510
  : null;
1180
- const existingRegistry = inspected.registry || {};
1511
+ if (claim && registryPreflight) {
1512
+ const claimEnvironment = normalizeText(claim.registry?.environmentId, 160);
1513
+ const claimOrigin = normalizeBaseUrl(claim.registry?.registryOrigin);
1514
+ if (claimEnvironment !== registryPreflight.environmentId || claimOrigin !== registryPreflight.registryOrigin) {
1515
+ throw new Error('CREATOR_SKILL_REGISTRY_ENVIRONMENT_MISMATCH: the claimed Skill identity was returned by a different registry environment. No package upload was attempted.');
1516
+ }
1517
+ }
1181
1518
  const publicToken = normalizeText(flags['public-token'] || claim?.publicToken || existingRegistry.publicToken, 512);
1182
1519
  if (!publicToken) {
1183
1520
  throw new Error('Missing --code or --public-token.');
@@ -1204,10 +1541,23 @@ async function writeManifest(root, flags) {
1204
1541
  skillKey: safeSkillKey(claim?.skillKey || inspected.skillKey),
1205
1542
  description: normalizeText(claim?.description, 500) || inspected.description,
1206
1543
  version: normalizeText(claim?.version, 80) || inspected.version,
1544
+ isPaid: Boolean(flags['paid-skill'] || flags['protected-skill'] || inspected.isPaid || existingRegistry.isPaid || existingRegistry.paidAccess),
1545
+ requiresEntitlement: Boolean(flags['paid-skill'] || flags['protected-skill'] || inspected.requiresEntitlement || existingRegistry.requiresEntitlement),
1546
+ billingModel: normalizeText(flags['billing-model'] || inspected.billingModel || existingRegistry.billingModel, 80) || null,
1547
+ monetizationMode: normalizeText(flags['monetization-mode'] || inspected.monetizationMode || existingRegistry.monetizationMode, 80) || null,
1548
+ accessMode: normalizeText(flags['access-mode'] || inspected.accessMode || existingRegistry.accessMode, 80) || null,
1207
1549
  registry: {
1208
1550
  provider: REGISTRY_PROVIDER,
1551
+ protocolVersion: normalizeText(claim?.registry?.protocolVersion || registryPreflight?.protocolVersion, 120) || null,
1552
+ environmentId: normalizeText(claim?.registry?.environmentId || registryPreflight?.environmentId, 160) || null,
1553
+ registryOrigin: normalizeBaseUrl(claim?.registry?.registryOrigin || registryPreflight?.registryOrigin) || null,
1209
1554
  skillId: normalizeText(flags['skill-id'] || claim?.skillId || existingRegistry.skillId, 160) || null,
1210
1555
  publicToken,
1556
+ isPaid: Boolean(flags['paid-skill'] || flags['protected-skill'] || inspected.isPaid || existingRegistry.isPaid || existingRegistry.paidAccess),
1557
+ requiresEntitlement: Boolean(flags['paid-skill'] || flags['protected-skill'] || inspected.requiresEntitlement || existingRegistry.requiresEntitlement),
1558
+ billingModel: normalizeText(flags['billing-model'] || inspected.billingModel || existingRegistry.billingModel, 80) || null,
1559
+ monetizationMode: normalizeText(flags['monetization-mode'] || inspected.monetizationMode || existingRegistry.monetizationMode, 80) || null,
1560
+ accessMode: normalizeText(flags['access-mode'] || inspected.accessMode || existingRegistry.accessMode, 80) || null,
1211
1561
  registryUrl: normalizeText(flags['registry-url'] || claim?.registryUrl || existingRegistry.registryUrl, 800) || DEFAULT_REGISTRY_URL,
1212
1562
  agentAckUrl: normalizeText(flags['agent-ack-url'] || existingRegistry.agentAckUrl, 800) || DEFAULT_AGENT_ACK_URL,
1213
1563
  creatorCardUrl,
@@ -1282,7 +1632,8 @@ async function writeManifest(root, flags) {
1282
1632
  ok: true,
1283
1633
  manifestPath,
1284
1634
  manifest,
1285
- claimPackageArtifactUpload: claim?.packageArtifactUpload ?? null
1635
+ claimPackageArtifactUpload: claim?.packageArtifactUpload ?? null,
1636
+ registryPreflight
1286
1637
  };
1287
1638
  }
1288
1639
 
@@ -1294,13 +1645,13 @@ function hubInstruction(hub) {
1294
1645
  return '虾舍商店发布是完整 Skill 包和 registry manifest 的主流程。请保留公开披露、归因和 runtime analytics 回传能力;如果本地 manifest 有 registry public token 或已有创作者认证 CLI/API 会话,就创建或复用虾舍商店草稿。';
1295
1646
  }
1296
1647
  if (hub === 'red') {
1297
- return 'Red Skill 的官方 uploader.md / skillhub-upload / 页面流程是主流程。请优先使用本机 skillhub-upload;虾舍负责在 SKILL.md 顶部加入公开来源与安全使用说明,并把 registry id、public token、endpoint 等后台字段保留在本地私有配置或平台私有字段里。';
1648
+ return `Red Skill 的官方 uploader.md / skillhub-upload / 页面流程是主流程。请优先使用本机 skillhub-upload;${PRODUCT_NAME} 负责生成平台安全披露、connector/运行时协议和本地 registry handoff,并把 registry id、public token、endpoint 等后台字段保留在本地私有配置或平台私有字段里。`;
1298
1649
  }
1299
1650
  if (hub === 'clawhub') {
1300
- return 'ClawHub 的官方 CLI、prompt 或页面流程是主流程。请先确认 ClawHub 当前要求,再把下面的虾舍 registry 要求作为附加说明合并进去。';
1651
+ return `ClawHub 的官方 CLI、prompt 或页面流程是主流程。请先确认 ClawHub 当前要求,再把下面的 ${PRODUCT_NAME} registry 要求作为附加说明合并进去。`;
1301
1652
  }
1302
1653
  if (hub === 'skillhub') {
1303
- return 'SkillHub 的官方 CLI、prompt 或页面流程是主流程。请先确认 SkillHub 当前要求,再把下面的虾舍 registry 要求作为附加说明合并进去。';
1654
+ return `SkillHub 的官方 CLI、prompt 或页面流程是主流程。请先确认 SkillHub 当前要求,再把下面的 ${PRODUCT_NAME} registry 要求作为附加说明合并进去。`;
1304
1655
  }
1305
1656
  if (hub === 'claude') {
1306
1657
  return 'Claude Skills 的官方上传/导入流程是主流程。请先确认 Claude 当前 Skill 格式和共享要求,再把下面的 registry 披露文本放入允许的说明文件。';
@@ -1320,7 +1671,7 @@ function hubInstruction(hub) {
1320
1671
  if (hub === 'coze') {
1321
1672
  return 'Coze Agent/Plugin/Workflow 的官方配置和发布流程是主流程。请先按 Coze 当前工具、插件或知识库要求配置,再把下面的 registry 回调作为可选 HTTP/API 步骤接入。';
1322
1673
  }
1323
- return '目标 Skill Hub 的官方 CLI、prompt 或页面流程是主流程。请先确认平台当前要求,再把下面的虾舍 registry 要求作为附加说明合并进去。';
1674
+ return `目标 Skill Hub 的官方 CLI、prompt 或页面流程是主流程。请先确认平台当前要求,再把下面的 ${PRODUCT_NAME} registry 要求作为附加说明合并进去。`;
1324
1675
  }
1325
1676
 
1326
1677
  function platformPromptPlaceholder(hub) {
@@ -1331,36 +1682,36 @@ function platformPromptPlaceholder(hub) {
1331
1682
  return '如果用户要发布到虾舍商店,并且当前环境已有创作者认证 CLI/API 会话,请优先创建虾舍商店草稿;如果没有认证会话,请完成 registry setup、披露、平台 profile 和 handoff 文件,不要要求 Agent 宿主登录或索取账号凭证。不要把第三方平台限制套用到虾舍完整 Skill 包。';
1332
1683
  }
1333
1684
  if (hub === 'red') {
1334
- return '如果本机有 skillhub-upload,请按小红书官方 CLI 流程 login --agent、打包并提交;否则请让用户从小红书创作服务平台复制 uploader.md 里的官方对话上传口令。不要用虾舍 prompt 替代 Red Skill 官方流程。';
1685
+ return `如果本机有 skillhub-upload,请按小红书官方 CLI 流程 login --agent、打包并提交;否则请让用户从小红书创作服务平台复制 uploader.md 里的官方对话上传口令。不要用 ${PRODUCT_NAME} prompt 替代 Red Skill 官方流程。`;
1335
1686
  }
1336
1687
  if (hub === 'clawhub') {
1337
- return '如果 ClawHub 提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用虾舍 prompt 替代 ClawHub 官方流程。';
1688
+ return `如果 ClawHub 提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用 ${PRODUCT_NAME} prompt 替代 ClawHub 官方流程。`;
1338
1689
  }
1339
1690
  if (hub === 'skillhub') {
1340
- return '如果 SkillHub 提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用虾舍 prompt 替代 SkillHub 官方流程。';
1691
+ return `如果 SkillHub 提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用 ${PRODUCT_NAME} prompt 替代 SkillHub 官方流程。`;
1341
1692
  }
1342
1693
  if (hub === 'claude') {
1343
- return '如果 Claude 提供官方 Skill 导入/上传说明,请优先使用官方说明;不要用虾舍 prompt 替代 Claude 官方流程。';
1694
+ return `如果 Claude 提供官方 Skill 导入/上传说明,请优先使用官方说明;不要用 ${PRODUCT_NAME} prompt 替代 Claude 官方流程。`;
1344
1695
  }
1345
1696
  if (hub === 'codex') {
1346
- return '如果 Codex 提供官方 Skill 或 Agent 能力安装说明,请优先使用官方说明;不要用虾舍 prompt 替代 Codex 官方流程。';
1697
+ return `如果 Codex 提供官方 Skill 或 Agent 能力安装说明,请优先使用官方说明;不要用 ${PRODUCT_NAME} prompt 替代 Codex 官方流程。`;
1347
1698
  }
1348
1699
  if (hub === 'cursor') {
1349
- return '如果 Cursor 提供官方规则、扩展或 Agent 能力安装说明,请优先使用官方说明;不要用虾舍 prompt 替代 Cursor 官方流程。';
1700
+ return `如果 Cursor 提供官方规则、扩展或 Agent 能力安装说明,请优先使用官方说明;不要用 ${PRODUCT_NAME} prompt 替代 Cursor 官方流程。`;
1350
1701
  }
1351
1702
  if (hub === 'workbuddy') {
1352
- return '如果 WorkBuddy 提供官方上传 prompt、网页或 API 流程,请优先使用官方流程;不要用虾舍 prompt 替代 WorkBuddy 官方流程。';
1703
+ return `如果 WorkBuddy 提供官方上传 prompt、网页或 API 流程,请优先使用官方流程;不要用 ${PRODUCT_NAME} prompt 替代 WorkBuddy 官方流程。`;
1353
1704
  }
1354
1705
  if (hub === 'dify') {
1355
- return '如果 Dify 提供官方插件打包/发布命令,请优先使用官方命令;不要用虾舍 prompt 替代 Dify 官方流程。';
1706
+ return `如果 Dify 提供官方插件打包/发布命令,请优先使用官方命令;不要用 ${PRODUCT_NAME} prompt 替代 Dify 官方流程。`;
1356
1707
  }
1357
1708
  if (hub === 'coze') {
1358
- return '如果 Coze 提供 Agent/Plugin/Workflow 的配置步骤,请优先使用 Coze 官方配置;不要用虾舍 prompt 替代 Coze 官方流程。';
1709
+ return `如果 Coze 提供 Agent/Plugin/Workflow 的配置步骤,请优先使用 Coze 官方配置;不要用 ${PRODUCT_NAME} prompt 替代 Coze 官方流程。`;
1359
1710
  }
1360
- return '如果目标平台提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用虾舍 prompt 替代平台官方流程。';
1711
+ return `如果目标平台提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用 ${PRODUCT_NAME} prompt 替代平台官方流程。`;
1361
1712
  }
1362
1713
 
1363
- function uploadCompatibilityLines(hub, manifestFile) {
1714
+ function uploadCompatibilityLines(hub, manifestFile, protectedPaidSkill = false) {
1364
1715
  const localManifest = `${WORK_DIR}/${manifestFile}`;
1365
1716
  if (hub === 'agentpie') {
1366
1717
  return [
@@ -1381,14 +1732,16 @@ function uploadCompatibilityLines(hub, manifestFile) {
1381
1732
  if (hub === 'red') {
1382
1733
  return [
1383
1734
  'Red Skill 兼容性要求:',
1384
- '- 以小红书官方 `uploader.md`、`skillhub-upload` 或页面上传流程为准;不要用虾舍 prompt 替代官方流程。',
1385
- `- 上传包必须在 SKILL.md 最开头写清楚 ${PRODUCT_NAME} Required Runtime Contract:更新检查、创作者名片、强制 footer、公开来源和安全使用边界。`,
1735
+ `- 以小红书官方 \`uploader.md\`、\`skillhub-upload\` 或页面上传流程为准;不要用 ${PRODUCT_NAME} prompt 替代官方流程。`,
1736
+ protectedPaidSkill
1737
+ ? `- 付费受保护 Skill 的 Red 公开包只能是 connector/bootstrap:写清楚 ${PRODUCT_NAME} Gateway 接入、付费访问边界、创作者名片和安全使用边界,不上传完整 SKILL.md/prompt/source。`
1738
+ : `- 上传包必须在 SKILL.md 最开头写清楚 ${PRODUCT_NAME} Required Runtime Contract:更新检查、创作者名片、强制 footer、公开来源与安全使用边界。`,
1386
1739
  `- 如果平台接受 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
1740
  '- 不要要求用户可见回复追加广告占位、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 公开介绍或公开包。',
1741
+ `- ${localManifest} 默认只保留在用户本地,用于 ${PRODUCT_NAME} registry claim、Agent Ack 和后续手动/公开数据导入;不要放进 Red 公开介绍。`,
1742
+ `- public token、signing secret、registry id、package sha、storage id、runtime-events.js、${WORK_DIR} 内部 handoff/checklist 不进入 Red 公开介绍或公开包。`,
1390
1743
  '- publicSkillId 与 Agent Ack endpoint 只能出现在专门的 no-secret 协议文件或平台私有配置里;不要写进营销介绍、用户可见回复或截图说明。',
1391
- '- Agent 使用 Skill 后,只有宿主平台或本地私有配置实际执行安全 Ack,虾舍才会记录 installed / used / completed / failed 真实统计。',
1744
+ `- Agent 使用 Skill 后,只有宿主平台或本地私有配置实际执行安全 Ack,${PRODUCT_NAME} 才会记录 installed / used / completed / failed 真实统计。`,
1392
1745
  '- 如果 Red 平台或运行环境没有执行 Agent Ack,则不要伪造 runtime;只保留上传成功和 reported 数据。',
1393
1746
  '',
1394
1747
  'Red Skill 基础安全规范:',
@@ -1413,20 +1766,22 @@ function uploadCompatibilityLines(hub, manifestFile) {
1413
1766
  function publicDisclosure(inspected, registryUrl, hub = 'generic') {
1414
1767
  const creatorCardUrl = normalizeText(inspected.registry?.creatorCardUrl, 800);
1415
1768
  const creatorLines = creatorProfileSummaryLines(inspected.registry || {}, 'en');
1769
+ const protectedPaidSkill = isProtectedPaidSkill(inspected);
1416
1770
  if (hub === 'red') {
1417
1771
  return [
1418
1772
  '公开披露文本:',
1419
1773
  '```markdown',
1420
1774
  `### ${PRODUCT_NAME} Attribution Disclosure`,
1421
1775
  '',
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>',
1776
+ `This Skill may include ${PRODUCT_NAME} creator attribution or a disclosure link for public listing and aggregate reporting.`,
1777
+ creatorCardUrl ? `Creator card: ${creatorCardUrl}` : `Creator card: <creator card link from ${PRODUCT_NAME} dashboard>`,
1424
1778
  ...creatorLines.filter((line) => !line.startsWith('- Creator card:')),
1425
1779
  '',
1426
1780
  `- Public protocol files, when included: ${PUBLIC_PROTOCOL_DIR}/runtime.yaml, ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, and ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md.`,
1781
+ 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
1782
  '- 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.',
1783
+ `- 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.`,
1784
+ `- 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
1785
  '- Do not send user prompts, private files, credentials, environment variables, account tokens, raw business content, or personal identifiers.',
1431
1786
  '```'
1432
1787
  ];
@@ -1440,7 +1795,7 @@ function publicDisclosure(inspected, registryUrl, hub = 'generic') {
1440
1795
  '',
1441
1796
  `- Registry provider: ${REGISTRY_PROVIDER}`,
1442
1797
  `- Skill key: ${inspected.skillKey}`,
1443
- `- Creator card: ${creatorCardUrl || '<creator card link from XiaShe dashboard>'}`,
1798
+ `- Creator card: ${creatorCardUrl || `<creator card link from ${PRODUCT_NAME} dashboard>`}`,
1444
1799
  ...creatorLines.filter((line) => !line.startsWith('- Creator card:')),
1445
1800
  '- Events, if enabled by the runtime/platform, should contain only public metadata and aggregate status.',
1446
1801
  '- 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 +1807,10 @@ function publicDisclosure(inspected, registryUrl, hub = 'generic') {
1452
1807
 
1453
1808
  function agentAckInstructionBlock(inspected, hub = 'generic') {
1454
1809
  const registry = inspected.registry || {};
1810
+ const protectedPaidSkill = isProtectedPaidSkill(inspected);
1455
1811
  const publicSkillId = registry.skillId || registry.publicSkillId || '<public skill id from local registry manifest>';
1456
1812
  const ackUrl = registry.agentAckUrl || DEFAULT_AGENT_ACK_URL;
1457
- const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
1813
+ const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
1458
1814
  const eventSchemaVersion = registry.runtimeSchemaVersion || RUNTIME_SCHEMA_VERSION;
1459
1815
  const normalizedHub = normalizeHub(hub) || hub || 'generic';
1460
1816
  const distributionPlatform = runtimeDistributionPlatformForHub(normalizedHub);
@@ -1473,10 +1829,16 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
1473
1829
  `- Runtime schema: ${eventSchemaVersion}`,
1474
1830
  `- Platform hub: ${normalizedHub}`,
1475
1831
  `- Distribution platform: ${distributionPlatform}`,
1832
+ protectedPaidSkill
1833
+ ? `- 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.`
1834
+ : `- Access mode: public/free Skill instructions may be used according to this package and the target platform rules.`,
1835
+ protectedPaidSkill
1836
+ ? `- 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.`
1837
+ : null,
1476
1838
  '- 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
1839
  '- 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
1840
  `- 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.',
1841
+ `- Match ${PRODUCT_NAME} display text to the current conversation language. Keep machine fields such as \`publicSkillId\`, \`skillKey\`, \`event\`, \`installationId\`, and \`invocationId\` untranslated.`,
1480
1842
  '- 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
1843
  '- 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
1844
  '- 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 +1892,9 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
1530
1892
 
1531
1893
  function redPublicTrackingContractBlock(inspected) {
1532
1894
  const registry = inspected.registry || {};
1895
+ const protectedPaidSkill = isProtectedPaidSkill(inspected);
1533
1896
  const marker = `${REGISTRY_PROVIDER}-runtime-contract`;
1534
- const cardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
1897
+ const cardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
1535
1898
  const currentVersion = normalizeText(inspected.version || registry.version, 80) || '1.0.0';
1536
1899
  const updateEndpoint = latestVersionEndpoint(inspected);
1537
1900
  const footer = creatorFooterTemplate(inspected, 'zh');
@@ -1540,6 +1903,9 @@ function redPublicTrackingContractBlock(inspected) {
1540
1903
  `## ${PRODUCT_NAME} Required Runtime Contract`,
1541
1904
  '',
1542
1905
  '使用本 Skill 时,必须优先遵循本小节。这个区块是给下游 Agent 读取的公开运行时契约,不是内部注释。',
1906
+ protectedPaidSkill
1907
+ ? `\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`
1908
+ : '',
1543
1909
  '',
1544
1910
  '### 检查更新',
1545
1911
  '',
@@ -1558,7 +1924,7 @@ function redPublicTrackingContractBlock(inspected) {
1558
1924
  footer,
1559
1925
  '```',
1560
1926
  '',
1561
- '如果当前对话使用英文,可以使用 runtime.yaml 里的英文 footer;否则默认使用上面的中文 footer。推荐位只使用 XiaShe Ack 响应返回的内容,没有返回时写“无”。',
1927
+ `如果当前对话使用英文,可以使用 runtime.yaml 里的英文 footer;否则默认使用上面的中文 footer。推荐位只使用 ${PRODUCT_NAME} Ack 响应返回的内容,没有返回时写“无”。`,
1562
1928
  '',
1563
1929
  '### 安全边界',
1564
1930
  '',
@@ -1579,7 +1945,8 @@ function skillMdRegistryBlock(inspected, hub) {
1579
1945
  return redPublicTrackingContractBlock(inspected);
1580
1946
  }
1581
1947
  const registry = inspected.registry || {};
1582
- const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
1948
+ const protectedPaidSkill = isProtectedPaidSkill(inspected);
1949
+ const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
1583
1950
  const marker = `${REGISTRY_PROVIDER}-registry`;
1584
1951
  const currentVersion = normalizeText(inspected.version || registry.version, 80) || '1.0.0';
1585
1952
  const updateEndpoint = latestVersionEndpoint(inspected);
@@ -1588,6 +1955,9 @@ function skillMdRegistryBlock(inspected, hub) {
1588
1955
  `## ${PRODUCT_NAME} Required Runtime Contract`,
1589
1956
  '',
1590
1957
  `This Skill is published by its creator through ${PRODUCT_NAME}. Follow this section before using the Skill.`,
1958
+ protectedPaidSkill
1959
+ ? `\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`
1960
+ : '',
1591
1961
  '',
1592
1962
  '### Update Check',
1593
1963
  '',
@@ -1669,8 +2039,9 @@ function runtimeDistributionPlatformForHub(hub) {
1669
2039
 
1670
2040
  function buildRuntimeYaml(inspected, hub = 'red') {
1671
2041
  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>';
2042
+ const protectedPaidSkill = isProtectedPaidSkill(inspected);
2043
+ const publicSkillId = registry.skillId || registry.publicSkillId || `<public skill id from ${PRODUCT_NAME} registry>`;
2044
+ const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
1674
2045
  const creatorName = normalizeText(registry.creatorProfile?.displayName || registry.creatorDisplayName || registry.creatorName, 120) || '<creator display name>';
1675
2046
  const creatorSignal = normalizeText(registry.creatorProfile?.xiaSignal || registry.creatorXiaSignal || registry.xiaSignal, 80) || '<creator signal>';
1676
2047
  const ackUrl = registry.agentAckUrl || DEFAULT_AGENT_ACK_URL;
@@ -1682,6 +2053,11 @@ function buildRuntimeYaml(inspected, hub = 'red') {
1682
2053
  `publicSkillId: ${yamlString(publicSkillId)}`,
1683
2054
  `skillKey: ${yamlString(inspected.skillKey)}`,
1684
2055
  `skillVersion: ${yamlString(skillVersion)}`,
2056
+ 'access:',
2057
+ ` mode: ${yamlString(protectedPaidSkill ? 'protected_paid_connector' : 'public_or_free')}`,
2058
+ ` paidPromptDeliveredBy: ${yamlString(protectedPaidSkill ? `${PRODUCT_NAME} Gateway after entitlement verification` : 'public package or target platform rules')}`,
2059
+ ` firstUsePayment: ${yamlString(protectedPaidSkill ? 'show hosted payment session returned by gateway, poll status, retry prompt after entitlement' : 'not_required_by_protocol')}`,
2060
+ ` neverUploadToThirdParty: ${yamlString(protectedPaidSkill ? 'full prompt, source package, package URL, storage id, public token, internal manifest' : 'secrets, private manifest, tokens, package hashes, storage ids')}`,
1685
2061
  'updateCheck:',
1686
2062
  " mode: 'prompt_user'",
1687
2063
  ` currentVersion: ${yamlString(skillVersion)}`,
@@ -1801,7 +2177,7 @@ async function writePublicProtocolFiles(root, inspected, hub = 'red') {
1801
2177
  const disclosurePath = path.join(protocolDir, 'REGISTRY_DISCLOSURE.md');
1802
2178
  const runtimePath = path.join(protocolDir, 'runtime.yaml');
1803
2179
  const registry = inspected.registry || {};
1804
- const publicSkillId = registry.skillId || registry.publicSkillId || '<public skill id from XiaShe registry>';
2180
+ const publicSkillId = registry.skillId || registry.publicSkillId || `<public skill id from ${PRODUCT_NAME} registry>`;
1805
2181
  const agentAck = [
1806
2182
  agentAckInstructionBlock(inspected, hub),
1807
2183
  '',
@@ -1871,6 +2247,7 @@ async function uploadPrompt(inspected, flags) {
1871
2247
  const hub = normalizeHub(flags.hub || 'generic') || 'generic';
1872
2248
  const reviewProfile = reviewProfileForHub(hub);
1873
2249
  const packagePlan = packagePlanForHub(inspected, hub);
2250
+ const protectedPaidSkill = isProtectedPaidSkill(inspected);
1874
2251
  const sourceUrl = normalizeText(flags['source-url'] || flags['package-url'], 1000);
1875
2252
  const platformCommand = normalizeText(flags['platform-command'], 1000);
1876
2253
  const platformPrompt = await readPlatformPrompt(flags);
@@ -1878,11 +2255,11 @@ async function uploadPrompt(inspected, flags) {
1878
2255
  const registryUrl = registry.registryUrl || DEFAULT_REGISTRY_URL;
1879
2256
  const agentAckUrl = registry.agentAckUrl || DEFAULT_AGENT_ACK_URL;
1880
2257
  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>';
2258
+ const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
1882
2259
  const creatorProfileLines = creatorProfileSummaryLines(registry, 'zh').filter((line) => !line.startsWith('- 创作者名片:'));
1883
2260
  const redHub = hub === 'red';
1884
2261
  const eventPayload = {
1885
- publicToken: registry.publicToken || '<xiashe public token from xiashe.skill.json>',
2262
+ publicToken: registry.publicToken || `<public token from ${WORK_DIR}/${MANIFEST_FILE}>`,
1886
2263
  schemaVersion: registry.eventSchemaVersion || EVENT_SCHEMA_VERSION,
1887
2264
  eventType: 'hub_upload_succeeded',
1888
2265
  eventSource: 'cli',
@@ -1934,15 +2311,15 @@ async function uploadPrompt(inspected, flags) {
1934
2311
  '第三方平台官方流程:',
1935
2312
  `- ${hubLine}`,
1936
2313
  redHub
1937
- ? '- 平台官方 prompt / CLI / 页面表单是上传依据;虾舍提供本地 registry、Agent Ack 真实统计、安全披露和上传状态记录。'
1938
- : '- 平台官方 prompt / CLI / 页面表单是上传依据;虾舍只提供 registry 和 analytics 附加要求。',
1939
- '- 如果平台官方要求与虾舍附加说明冲突,以平台官方上传要求为准;不要为了 analytics 绕过平台规则。',
2314
+ ? `- 平台官方 prompt / CLI / 页面表单是上传依据;${PRODUCT_NAME} 提供本地 registry、Agent Ack 真实统计、安全披露和上传状态记录。`
2315
+ : `- 平台官方 prompt / CLI / 页面表单是上传依据;${PRODUCT_NAME} 只提供 registry 和 analytics 附加要求。`,
2316
+ `- 如果平台官方要求与 ${PRODUCT_NAME} 附加说明冲突,以平台官方上传要求为准;不要为了 analytics 绕过平台规则。`,
1940
2317
  platformCommand ? `- 用户提供的官方命令:${platformCommand}` : `- ${platformPromptPlaceholder(hub)}`
1941
2318
  ];
1942
2319
  if (platformPrompt) {
1943
2320
  officialFlowLines.push('', '用户提供的第三方平台官方 prompt:', '```text', platformPrompt.trim(), '```');
1944
2321
  }
1945
- const compatibilityLines = uploadCompatibilityLines(hub, MANIFEST_FILE);
2322
+ const compatibilityLines = uploadCompatibilityLines(hub, MANIFEST_FILE, protectedPaidSkill);
1946
2323
  const disclosureLines = publicDisclosure(inspected, registryUrl, hub);
1947
2324
  const runtimeGuidanceLines = redHub
1948
2325
  ? [
@@ -1953,14 +2330,21 @@ async function uploadPrompt(inspected, flags) {
1953
2330
  `- 描述:${inspected.description || '<从 SKILL.md/README.md 中提炼>'}`,
1954
2331
  `- 创作者名片:${creatorCardUrl}`,
1955
2332
  ...creatorProfileLines,
1956
- `- Red Skill 上传包按官方流程准备;如果平台接受 Markdown/YAML 源包,可以包含 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 和 ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这三个无密钥运行时协议文件。`,
2333
+ protectedPaidSkill
2334
+ ? `- 这是付费受保护 Skill:Red Skill 公开包只能是 ${PRODUCT_NAME} connector/bootstrap。不要上传完整 prompt、源码包、包下载链接、storage id、内部 manifest 或任何可绕过网关的材料。`
2335
+ : `- Red Skill 上传包按官方流程准备;如果平台接受 Markdown/YAML 源包,可以包含 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 和 ${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这三个无密钥运行时协议文件。`,
2336
+ protectedPaidSkill
2337
+ ? `- 外部 Agent 第一次真实使用时必须回到 ${PRODUCT_NAME} Gateway 获取完整受保护包;如果返回 paymentRequired,就展示购买信息、等待用户确认支付、轮询 status,再重试 get_skill_prompt,然后下载、校验、解压并读取 SKILL.md。`
2338
+ : null,
1957
2339
  `- ${WORK_DIR}/、public token、signing secret、registry id、package sha、storage id、upload job id、runtime-events.js 和内部 handoff/checklist 默认只在本地保留,不进入 Red 公开包。`,
1958
2340
  `- 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。`,
2341
+ protectedPaidSkill
2342
+ ? `- 公开 connector 说明必须写清楚 ${PRODUCT_NAME} Gateway 接入、付费访问边界、latest version endpoint、用户确认后更新、创作者名片链接和安全边界;不要要求下游 Agent 在用户可见回复里追加 telemetry JSON。`
2343
+ : `- SKILL.md 最开头必须写 ${PRODUCT_NAME} Required Runtime Contract:当前版本、latest version endpoint、用户确认后更新、创作者名片链接、强制 footer 和安全边界;不要要求下游 Agent 在用户可见回复里追加 telemetry JSON。`,
1960
2344
  '- 使用 Skill 前如果能联网,Agent 应先检查最新版本;有新版本就提示用户是否更新,无法联网则继续但说明更新状态未检查。',
1961
- '- 如果输出数字或链接,必须结构化展示;如果平台无法执行安全 Ack,不要伪造虾舍 runtime 数据。',
2345
+ `- 如果输出数字或链接,必须结构化展示;如果平台无法执行安全 Ack,不要伪造 ${PRODUCT_NAME} runtime 数据。`,
1962
2346
  '- 上传成功后可以用 hub_upload_succeeded 记录公开 URL;这属于 attribution,不代表真实运行次数。',
1963
- `- 如果 Agent 读取 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置并执行 Agent Ack,installed / used / completed / failed 会进入虾舍 runtime 统计。`,
2347
+ `- 如果 Agent 读取 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置并执行 Agent Ack,installed / used / completed / failed 会进入 ${PRODUCT_NAME} runtime 统计。`,
1964
2348
  '- 如果平台公开展示下载、收藏、安装或使用数字,可以后续按 reported 数据手动导入;不要把 reported 伪造成 runtime。',
1965
2349
  '- 先检查“能否安全上传”,再检查“Agent Ack 能否被执行”;二者在报告里分开。',
1966
2350
  `- Agent Ack 的字段要拆清楚:distributionPlatform 是分发平台,runtimeAgent 是实际运行的 Agent,runtimeHost 是宿主环境,sourceSurface 是发现入口,scenario 是枚举键,scenarioLabel 是短的人类可读场景。`,
@@ -1998,7 +2382,9 @@ async function uploadPrompt(inspected, flags) {
1998
2382
  '',
1999
2383
  JSON.stringify(eventPayload, null, 2),
2000
2384
  '',
2001
- 'Red Skill 的真实统计只能由宿主平台、安装器或本地私有配置执行安全 Ack 后产生。公开 SKILL.md 只保留创作者名片和安全使用边界。'
2385
+ protectedPaidSkill
2386
+ ? `Red Skill 的真实统计只能由宿主平台、安装器或本地私有配置执行安全 Ack 后产生。公开 connector 只保留 ${PRODUCT_NAME} Gateway 接入、创作者名片和安全使用边界。`
2387
+ : 'Red Skill 的真实统计只能由宿主平台、安装器或本地私有配置执行安全 Ack 后产生。公开 SKILL.md 只保留创作者名片和安全使用边界。'
2002
2388
  ]
2003
2389
  : [
2004
2390
  `发布成功后,请把公开链接和平台返回的信息回传给${PRODUCT_NAME} registry。只发送公开元数据和聚合事件,不发送用户内容、私密输入或账号凭据。`,
@@ -2054,7 +2440,7 @@ async function uploadPrompt(inspected, flags) {
2054
2440
  '- attribution:发布、上传、归因链接点击、公开披露访问,只代表来源漏斗。',
2055
2441
  '- reported:平台公开数字或手动导入,例如收藏、下载、安装、静态平台显示使用数。',
2056
2442
  '- 如果无法确认 runtime callback 已经接在真实调用边界,就把该平台标记为 attribution 或 reported,不要伪造成 runtime。',
2057
- '- Agent ack:只有平台宿主、安装器或本地私有配置实际执行安全 Ack,才可以在安装/使用/完成/失败后计入虾舍实时使用数据。',
2443
+ `- Agent ack:只有平台宿主、安装器或本地私有配置实际执行安全 Ack,才可以在安装/使用/完成/失败后计入 ${PRODUCT_NAME} 实时使用数据。`,
2058
2444
  '',
2059
2445
  ...compatibilityLines,
2060
2446
  '',
@@ -2064,10 +2450,12 @@ async function uploadPrompt(inspected, flags) {
2064
2450
  ? [
2065
2451
  'Red 公开包边界:',
2066
2452
  '',
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、账号会话或构建产物。`,
2453
+ protectedPaidSkill
2454
+ ? `- 付费 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 公开包。`
2455
+ : `- 可以包含:SKILL.md、README/公开 references,以及 ${PUBLIC_PROTOCOL_DIR}/runtime.yaml、${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md、${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md 这类无密钥运行时协议文件。`,
2456
+ `- 不要包含:${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
2457
  `- publicSkillId 和 Agent Ack endpoint 只能放在 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置里;不要放进营销介绍、用户可见回复或截图说明。`,
2070
- '- 如果宿主 Agent 不能读取协议文件/私有配置并执行 Ack,Red 安装成功不会自动变成虾舍 runtime 数据。'
2458
+ `- 如果宿主 Agent 不能读取协议文件/私有配置并执行 Ack,Red 安装成功不会自动变成 ${PRODUCT_NAME} runtime 数据。`
2071
2459
  ]
2072
2460
  : [
2073
2461
  'Agent Ack 指令(只放在私有 runtime 配置、平台后台字段,或明确不会公开展示的安装说明中):',
@@ -2106,6 +2494,7 @@ async function writePrompt(root, flags) {
2106
2494
 
2107
2495
  async function writeUnifiedHandoff(root, args) {
2108
2496
  const relativeOutDir = path.relative(root, args.outDir) || WORK_DIR;
2497
+ const protectedPaidSkill = isProtectedPaidSkill(args.inspected);
2109
2498
  const promptIndex = args.promptResults
2110
2499
  .map((item) => `- ${item.hub}: ${path.relative(root, item.promptPath)}`)
2111
2500
  .join('\n');
@@ -2114,7 +2503,7 @@ async function writeUnifiedHandoff(root, args) {
2114
2503
  .join('\n');
2115
2504
  const registry = args.inspected.registry || {};
2116
2505
  const registryUrl = registry.registryUrl || DEFAULT_REGISTRY_URL;
2117
- const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || '<creator card link from XiaShe dashboard>';
2506
+ const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
2118
2507
  const content = [
2119
2508
  `# ${PRODUCT_NAME} Skill Upload Handoff`,
2120
2509
  '',
@@ -2131,7 +2520,7 @@ async function writeUnifiedHandoff(root, args) {
2131
2520
  '1. Identify the target platform from that official prompt, URL, command, or user message.',
2132
2521
  `2. Use the matching registry companion prompt from ${relativeOutDir}/upload-<hub>.md only as an internal checklist.`,
2133
2522
  '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.`,
2523
+ `4. For ${PRODUCT_NAME} Registry, create or reuse the searchable listing. For Red Skill, use the official uploader.md / skillhub-upload flow.`,
2135
2524
  `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
2525
  `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
2526
  '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 +2530,15 @@ async function writeUnifiedHandoff(root, args) {
2141
2530
  '',
2142
2531
  'Red Skill exception:',
2143
2532
  '',
2533
+ protectedPaidSkill
2534
+ ? `- 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.`
2535
+ : null,
2144
2536
  '- 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.`,
2537
+ protectedPaidSkill
2538
+ ? `- 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.`
2539
+ : `- 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
2540
  `- 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.',
2541
+ `- 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
2542
  '- 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
2543
  '',
2150
2544
  '## Platform mapping',
@@ -2224,11 +2618,11 @@ async function writeUnifiedHandoff(root, args) {
2224
2618
  '',
2225
2619
  'If the hub cannot run callbacks, keep upload attribution and use campaign links or manual imports. Do not fake runtime events.',
2226
2620
  '',
2227
- '## XiaShe Store draft auth',
2621
+ `## ${PRODUCT_NAME} Registry draft auth`,
2228
2622
  '',
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.`,
2623
+ `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
2624
  '',
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.',
2625
+ `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
2626
  '',
2233
2627
  '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
2628
  ''
@@ -2519,7 +2913,7 @@ async function runDoctor(root, flags) {
2519
2913
  hasManifest && registry.publicToken && registry.registryUrl
2520
2914
  ? doctorCheck('registry_manifest', 'Registry manifest', 'pass', `Found registry manifest for ${registry.provider || REGISTRY_PROVIDER}.`)
2521
2915
  : 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.`)
2916
+ ? 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
2917
  : 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
2918
  registryBlockPresent(skillMd)
2525
2919
  ? doctorCheck('registry_block', 'Registry block', 'pass', 'SKILL.md contains the explicit registry disclosure block.')
@@ -2534,7 +2928,7 @@ async function runDoctor(root, flags) {
2534
2928
  ? doctorCheck('skill_md_contract', 'SKILL.md runtime contract', 'pass', 'SKILL.md contains the current runtime contract, required footer, and update check endpoint.')
2535
2929
  : redHub
2536
2930
  ? 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.`),
2931
+ : 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
2932
  runtimeCallbackPresent
2539
2933
  ? 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
2934
  : redHub
@@ -2562,6 +2956,11 @@ async function runDoctor(root, flags) {
2562
2956
  : redHub
2563
2957
  ? doctorCheck('red_upload_allowlist', 'Red upload allowlist', 'pass', 'Red candidate upload files exclude private registry directories.')
2564
2958
  : doctorCheck('red_upload_allowlist', 'Red upload allowlist', 'pass', 'Not required for this hub.'),
2959
+ redHub && protectedPaidSkill && packagePlan.candidateUploadFiles.some((file) => /(^|\/)SKILL(\.[a-z0-9]+)?$/i.test(file) || (!isPublicRuntimeProtocolFile(file) && !/^README(\.[a-z0-9]+)?$/i.test(file)))
2960
+ ? 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.`)
2961
+ : redHub && protectedPaidSkill
2962
+ ? 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.')
2963
+ : doctorCheck('red_paid_connector_boundary', 'Red paid connector boundary', 'pass', 'Not required for this hub/Skill.'),
2565
2964
  redHub && ['AGENT_ACK.md', 'REGISTRY_DISCLOSURE.md', 'runtime.yaml'].every((name) => packagePlan.candidateUploadFiles.includes(`${PUBLIC_PROTOCOL_DIR}/${name}`))
2566
2965
  ? 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
2966
  : redHub
@@ -2868,6 +3267,23 @@ async function main() {
2868
3267
  }
2869
3268
  return;
2870
3269
  }
3270
+ if (command === 'preflight') {
3271
+ const claimUrl = explicitClaimUrl(flags);
3272
+ if (!claimUrl) {
3273
+ throw new Error('CREATOR_SKILL_CLAIM_URL_REQUIRED: pass the exact --claim-url issued by the dashboard.');
3274
+ }
3275
+ const result = await preflightRegistryClaim(
3276
+ claimUrl,
3277
+ flags
3278
+ );
3279
+ print(flags.json ? result : [
3280
+ `${PRODUCT_NAME} registry preflight passed.`,
3281
+ `Environment: ${result.environmentId}`,
3282
+ `Registry: ${result.registryOrigin}`,
3283
+ `Protocol: ${result.protocolVersion}`
3284
+ ].join('\n'), flags.json);
3285
+ return;
3286
+ }
2871
3287
  if (command === 'verify') {
2872
3288
  const result = await verifyRegistry(root, flags);
2873
3289
  print(flags.json ? result : formatVerify(result), flags.json);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiashe/skill",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "xiashe-skill": "bin/xiashe-skill.mjs"