@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.
- package/README.md +17 -1
- package/bin/xiashe-skill.mjs +562 -111
- package/package.json +1 -1
package/bin/xiashe-skill.mjs
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
189
|
-
|
|
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:
|
|
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:
|
|
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
|
|
360
|
-
--package-artifact-url <url> Package artifact attach endpoint
|
|
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) ||
|
|
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:
|
|
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
|
|
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
|
-
?
|
|
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) ||
|
|
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
|
|
849
|
-
const
|
|
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
|
-
|
|
1062
|
+
candidate = new URL(raw);
|
|
852
1063
|
} catch {
|
|
853
|
-
|
|
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
|
-
|
|
871
|
-
|
|
872
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1392
|
+
let activeUploadTicket = uploadTicket;
|
|
1393
|
+
let uploadUrl = normalizeText(activeUploadTicket?.uploadUrl, 2_000);
|
|
1090
1394
|
if (!uploadUrl) {
|
|
1091
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1680
|
+
return '虾舍商店发布是完整 Skill 包的主流程。本地 registry manifest 仅保留在创作者设备;安装包只包含自动生成的不含敏感信息的 package manifest。请保留公开披露、归因和 runtime analytics 回传能力;如果已有创作者认证 CLI/API 会话,就创建或复用虾舍商店草稿。';
|
|
1295
1681
|
}
|
|
1296
1682
|
if (hub === 'red') {
|
|
1297
|
-
return
|
|
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
|
|
1686
|
+
return `ClawHub 的官方 CLI、prompt 或页面流程是主流程。请先确认 ClawHub 当前要求,再把下面的 ${PRODUCT_NAME} registry 要求作为附加说明合并进去。`;
|
|
1301
1687
|
}
|
|
1302
1688
|
if (hub === 'skillhub') {
|
|
1303
|
-
return
|
|
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
|
|
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
|
|
1720
|
+
return `如果本机有 skillhub-upload,请按小红书官方 CLI 流程 login --agent、打包并提交;否则请让用户从小红书创作服务平台复制 uploader.md 里的官方对话上传口令。不要用 ${PRODUCT_NAME} prompt 替代 Red Skill 官方流程。`;
|
|
1335
1721
|
}
|
|
1336
1722
|
if (hub === 'clawhub') {
|
|
1337
|
-
return
|
|
1723
|
+
return `如果 ClawHub 提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用 ${PRODUCT_NAME} prompt 替代 ClawHub 官方流程。`;
|
|
1338
1724
|
}
|
|
1339
1725
|
if (hub === 'skillhub') {
|
|
1340
|
-
return
|
|
1726
|
+
return `如果 SkillHub 提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用 ${PRODUCT_NAME} prompt 替代 SkillHub 官方流程。`;
|
|
1341
1727
|
}
|
|
1342
1728
|
if (hub === 'claude') {
|
|
1343
|
-
return
|
|
1729
|
+
return `如果 Claude 提供官方 Skill 导入/上传说明,请优先使用官方说明;不要用 ${PRODUCT_NAME} prompt 替代 Claude 官方流程。`;
|
|
1344
1730
|
}
|
|
1345
1731
|
if (hub === 'codex') {
|
|
1346
|
-
return
|
|
1732
|
+
return `如果 Codex 提供官方 Skill 或 Agent 能力安装说明,请优先使用官方说明;不要用 ${PRODUCT_NAME} prompt 替代 Codex 官方流程。`;
|
|
1347
1733
|
}
|
|
1348
1734
|
if (hub === 'cursor') {
|
|
1349
|
-
return
|
|
1735
|
+
return `如果 Cursor 提供官方规则、扩展或 Agent 能力安装说明,请优先使用官方说明;不要用 ${PRODUCT_NAME} prompt 替代 Cursor 官方流程。`;
|
|
1350
1736
|
}
|
|
1351
1737
|
if (hub === 'workbuddy') {
|
|
1352
|
-
return
|
|
1738
|
+
return `如果 WorkBuddy 提供官方上传 prompt、网页或 API 流程,请优先使用官方流程;不要用 ${PRODUCT_NAME} prompt 替代 WorkBuddy 官方流程。`;
|
|
1353
1739
|
}
|
|
1354
1740
|
if (hub === 'dify') {
|
|
1355
|
-
return
|
|
1741
|
+
return `如果 Dify 提供官方插件打包/发布命令,请优先使用官方命令;不要用 ${PRODUCT_NAME} prompt 替代 Dify 官方流程。`;
|
|
1356
1742
|
}
|
|
1357
1743
|
if (hub === 'coze') {
|
|
1358
|
-
return
|
|
1744
|
+
return `如果 Coze 提供 Agent/Plugin/Workflow 的配置步骤,请优先使用 Coze 官方配置;不要用 ${PRODUCT_NAME} prompt 替代 Coze 官方流程。`;
|
|
1359
1745
|
}
|
|
1360
|
-
return
|
|
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
|
|
1369
|
-
'- Keep public disclosure text, attribution links, and runtime event callbacks; do not remove the
|
|
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
|
-
`-
|
|
1377
|
-
'- 保留公开披露文本、归因链接和 runtime event callback
|
|
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
|
-
|
|
1385
|
-
|
|
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}
|
|
1389
|
-
|
|
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
|
-
|
|
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
|
|
1423
|
-
creatorCardUrl ? `Creator card: ${creatorCardUrl}` :
|
|
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
|
-
|
|
1429
|
-
|
|
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 ||
|
|
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) ||
|
|
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
|
-
|
|
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) ||
|
|
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
|
-
|
|
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
|
|
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
|
|
1673
|
-
const
|
|
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 ||
|
|
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) ||
|
|
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 ||
|
|
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
|
-
?
|
|
1938
|
-
:
|
|
1939
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2068
|
-
|
|
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
|
-
|
|
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) ||
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2656
|
+
`## ${PRODUCT_NAME} Registry draft auth`,
|
|
2228
2657
|
'',
|
|
2229
|
-
`For
|
|
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
|
-
|
|
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
|
|
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
|
|
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);
|