@xiashe/skill 0.1.21 → 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 +16 -1
- package/bin/xiashe-skill.mjs +657 -121
- package/package.json +1 -1
package/bin/xiashe-skill.mjs
CHANGED
|
@@ -1,19 +1,97 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { createHash, createHmac } from 'node:crypto';
|
|
4
|
-
import { existsSync } from 'node:fs';
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
5
5
|
import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
6
6
|
import os from 'node:os';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
8
9
|
import { promisify } from 'node:util';
|
|
9
10
|
import { gzip } from 'node:zlib';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
function parseEnvFile(filePath) {
|
|
13
|
+
if (!existsSync(filePath)) return {};
|
|
14
|
+
const parsed = {};
|
|
15
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
16
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
17
|
+
const trimmed = line.trim();
|
|
18
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
19
|
+
const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
20
|
+
if (!match) continue;
|
|
21
|
+
let value = match[2].trim();
|
|
22
|
+
if (
|
|
23
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
24
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
25
|
+
) {
|
|
26
|
+
value = value.slice(1, -1);
|
|
27
|
+
}
|
|
28
|
+
parsed[match[1]] = value;
|
|
29
|
+
}
|
|
30
|
+
return parsed;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function nearestEnvFiles() {
|
|
34
|
+
const files = [];
|
|
35
|
+
let cursor = process.cwd();
|
|
36
|
+
for (let index = 0; index < 8; index += 1) {
|
|
37
|
+
files.push(path.join(cursor, '.env.local'));
|
|
38
|
+
files.push(path.join(cursor, 'convex', '.env.local'));
|
|
39
|
+
const next = path.dirname(cursor);
|
|
40
|
+
if (next === cursor) break;
|
|
41
|
+
cursor = next;
|
|
42
|
+
}
|
|
43
|
+
return files;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function loadLocalEnvDefaults() {
|
|
47
|
+
const cliDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
|
|
48
|
+
const candidates = [
|
|
49
|
+
path.join(cliDir, '.env.local'),
|
|
50
|
+
path.join(cliDir, 'convex', '.env.local'),
|
|
51
|
+
...nearestEnvFiles()
|
|
52
|
+
];
|
|
53
|
+
const env = {};
|
|
54
|
+
for (const filePath of candidates) {
|
|
55
|
+
Object.assign(env, parseEnvFile(filePath));
|
|
56
|
+
}
|
|
57
|
+
return env;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeBaseUrl(value) {
|
|
61
|
+
const text = String(value || '').trim().replace(/\/+$/, '');
|
|
62
|
+
return /^https?:\/\//i.test(text) ? text : '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isTransportError(error) {
|
|
66
|
+
const message = error instanceof Error ? `${error.name || ''} ${error.message || ''}` : String(error || '');
|
|
67
|
+
return /abort|timeout|fetch failed|network|enotfound|eai_again|econnrefused|econnreset|etimedout|unable to verify|certificate/i.test(message);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const LOCAL_ENV_DEFAULTS = loadLocalEnvDefaults();
|
|
71
|
+
const VERSION = process.env.XIASHE_SKILL_CLI_VERSION || '0.1.23';
|
|
12
72
|
const COMMAND_NAME = process.env.XIASHE_SKILL_CLI_NAME || 'xiashe-skill';
|
|
13
73
|
const PRODUCT_NAME = process.env.XIASHE_SKILL_PRODUCT_NAME || (COMMAND_NAME === 'agentpie-skill' ? 'AgentPie' : 'XiaShe');
|
|
14
74
|
const REGISTRY_PROVIDER = process.env.XIASHE_SKILL_REGISTRY_PROVIDER || (COMMAND_NAME === 'agentpie-skill' ? 'agentpie' : 'xiashe');
|
|
75
|
+
const LOCAL_AGENTPIE_BASE_URL = normalizeBaseUrl(
|
|
76
|
+
process.env.AGENTPIE_SKILL_DEFAULT_BASE_URL ||
|
|
77
|
+
LOCAL_ENV_DEFAULTS.AGENTPIE_SKILL_DEFAULT_BASE_URL ||
|
|
78
|
+
process.env.AGENTPIE_SKILL_REGISTRY_PUBLIC_URL ||
|
|
79
|
+
LOCAL_ENV_DEFAULTS.AGENTPIE_SKILL_REGISTRY_PUBLIC_URL ||
|
|
80
|
+
process.env.AGENTPIE_ACTIONS_URL ||
|
|
81
|
+
LOCAL_ENV_DEFAULTS.AGENTPIE_ACTIONS_URL ||
|
|
82
|
+
process.env.CREATOR_SKILL_REGISTRY_PUBLIC_URL ||
|
|
83
|
+
LOCAL_ENV_DEFAULTS.CREATOR_SKILL_REGISTRY_PUBLIC_URL ||
|
|
84
|
+
process.env.AGENTPIE_MCP_PUBLIC_URL ||
|
|
85
|
+
LOCAL_ENV_DEFAULTS.AGENTPIE_MCP_PUBLIC_URL ||
|
|
86
|
+
process.env.PUBLIC_CONVEX_SITE_URL ||
|
|
87
|
+
LOCAL_ENV_DEFAULTS.PUBLIC_CONVEX_SITE_URL ||
|
|
88
|
+
process.env.CONVEX_SITE_URL ||
|
|
89
|
+
LOCAL_ENV_DEFAULTS.CONVEX_SITE_URL ||
|
|
90
|
+
process.env.EXPO_PUBLIC_CONVEX_SITE_URL ||
|
|
91
|
+
LOCAL_ENV_DEFAULTS.EXPO_PUBLIC_CONVEX_SITE_URL
|
|
92
|
+
);
|
|
15
93
|
const DEFAULT_BASE_URL = process.env.XIASHE_SKILL_DEFAULT_BASE_URL || (COMMAND_NAME === 'agentpie-skill'
|
|
16
|
-
? 'https://
|
|
94
|
+
? LOCAL_AGENTPIE_BASE_URL || 'https://agentpie.app'
|
|
17
95
|
: 'https://actions.xiashe.chat');
|
|
18
96
|
const MANIFEST_FILE = process.env.XIASHE_SKILL_MANIFEST_FILE || (COMMAND_NAME === 'agentpie-skill'
|
|
19
97
|
? 'agentpie.skill.json'
|
|
@@ -24,18 +102,17 @@ const HANDOFF_FILE = 'UPLOAD_HANDOFF.md';
|
|
|
24
102
|
const DEFAULT_REGISTRY_URL = process.env.XIASHE_SKILL_REGISTRY_URL || `${DEFAULT_BASE_URL}/registry/skill-events`;
|
|
25
103
|
const DEFAULT_AGENT_ACK_URL = process.env.XIASHE_SKILL_AGENT_ACK_URL || `${DEFAULT_BASE_URL}/registry/agent-ack`;
|
|
26
104
|
const DEFAULT_CLAIM_URL = process.env.XIASHE_SKILL_CLAIM_URL || `${DEFAULT_BASE_URL}/registry/skill/claim`;
|
|
27
|
-
const DEFAULT_PACKAGE_UPLOAD_URL = process.env.XIASHE_SKILL_PACKAGE_UPLOAD_URL || `${DEFAULT_BASE_URL}/registry/skill/package-upload-url`;
|
|
28
|
-
const DEFAULT_PACKAGE_ARTIFACT_URL = process.env.XIASHE_SKILL_PACKAGE_ARTIFACT_URL || `${DEFAULT_BASE_URL}/registry/skill/package-artifact`;
|
|
29
105
|
const EVENT_SCHEMA_VERSION = process.env.XIASHE_SKILL_EVENT_SCHEMA_VERSION || (REGISTRY_PROVIDER === 'agentpie'
|
|
30
106
|
? 'agentpie.skill.event.v1'
|
|
31
107
|
: 'xiashe.skill.event.v1');
|
|
32
108
|
const RUNTIME_SCHEMA_VERSION = process.env.XIASHE_SKILL_RUNTIME_SCHEMA_VERSION || (REGISTRY_PROVIDER === 'agentpie'
|
|
33
109
|
? 'agentpie.skill.runtime.v1'
|
|
34
110
|
: 'xiashe.skill.runtime.v1');
|
|
35
|
-
const
|
|
36
|
-
const
|
|
111
|
+
const FIRST_PARTY_HUB = REGISTRY_PROVIDER === 'agentpie' ? 'agentpie' : 'xiashe';
|
|
112
|
+
const SUPPORTED_HUBS = ['agentpie', 'xiashe', 'red', 'clawhub', 'skillhub', 'claude', 'codex', 'cursor', 'workbuddy', 'dify', 'coze', 'generic'];
|
|
113
|
+
const DEFAULT_SETUP_HUBS = [FIRST_PARTY_HUB, 'red'];
|
|
37
114
|
const ACK_EVENTS = ['installed', 'used', 'completed', 'failed'];
|
|
38
|
-
const RUNTIME_DISTRIBUTION_PLATFORMS = ['xiaohongshu-redskill', 'xiashe', 'coze-store', 'local', 'unknown'];
|
|
115
|
+
const RUNTIME_DISTRIBUTION_PLATFORMS = ['xiaohongshu-redskill', 'agentpie', 'xiashe', 'coze-store', 'local', 'unknown'];
|
|
39
116
|
const RUNTIME_AGENTS = ['codex', 'workbuddy', 'coze', 'claude', 'cursor', 'dify', 'custom-agent', 'unknown'];
|
|
40
117
|
const RUNTIME_HOSTS = ['codex-desktop', 'coze-app', 'web-chat', 'desktop', 'web', 'server', 'mobile', 'unknown'];
|
|
41
118
|
const RUNTIME_SOURCE_SURFACES = ['install', 'first-use-backfill', 'chat-use', 'manual-test', 'creator-card', 'dashboard', 'unknown'];
|
|
@@ -79,6 +156,16 @@ const FORBIDDEN_ACK_FIELDS = [
|
|
|
79
156
|
'privateResearchMaterial'
|
|
80
157
|
];
|
|
81
158
|
const PLATFORM_REVIEW_PROFILES = {
|
|
159
|
+
agentpie: {
|
|
160
|
+
label: 'AgentPie Registry',
|
|
161
|
+
packageMode: 'full_skill_package',
|
|
162
|
+
manifestPolicy: 'upload_allowed',
|
|
163
|
+
disclosurePlacement: ['Skill public page', 'README', `${WORK_DIR}/REGISTRY_DISCLOSURE.md`],
|
|
164
|
+
runtimeDataPolicy: 'runtime_capable',
|
|
165
|
+
defaultDataSource: 'runtime',
|
|
166
|
+
allowedFilePolicy: 'Upload the complete reviewed Skill package, including the local registry manifest.',
|
|
167
|
+
forbiddenPublicFiles: ['.env', 'credentials', 'node_modules', 'dist', 'build', 'browser profiles', 'SSH keys']
|
|
168
|
+
},
|
|
82
169
|
xiashe: {
|
|
83
170
|
label: 'XiaShe Store',
|
|
84
171
|
packageMode: 'full_skill_package',
|
|
@@ -96,8 +183,8 @@ const PLATFORM_REVIEW_PROFILES = {
|
|
|
96
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`],
|
|
97
184
|
runtimeDataPolicy: 'agent_ack_supported',
|
|
98
185
|
defaultDataSource: 'runtime',
|
|
99
|
-
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
|
|
100
|
-
|
|
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'],
|
|
101
188
|
forbiddenPublicFiles: ['credentials', 'cookies', 'browser/account sessions', 'public tokens', 'signing secrets', 'hidden telemetry'],
|
|
102
189
|
safetyChecklist: [
|
|
103
190
|
'功能描述真实、清晰、有实际使用价值,不夸大或误导用户。',
|
|
@@ -116,7 +203,7 @@ const PLATFORM_REVIEW_PROFILES = {
|
|
|
116
203
|
disclosurePlacement: ['SKILL.md', 'README', 'platform description field'],
|
|
117
204
|
runtimeDataPolicy: 'agent_ack_supported',
|
|
118
205
|
defaultDataSource: 'runtime',
|
|
119
|
-
allowedFilePolicy:
|
|
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.`,
|
|
120
207
|
forbiddenPublicFiles: [WORK_DIR, `${WORK_DIR}/${MANIFEST_FILE}`, `${WORK_DIR}/runtime-events.js`, 'public tokens', 'signing secrets']
|
|
121
208
|
},
|
|
122
209
|
skillhub: {
|
|
@@ -196,7 +283,7 @@ const PLATFORM_REVIEW_PROFILES = {
|
|
|
196
283
|
disclosurePlacement: ['README', 'SKILL.md', 'platform description field'],
|
|
197
284
|
runtimeDataPolicy: 'runtime_if_http_or_mcp',
|
|
198
285
|
defaultDataSource: 'attribution',
|
|
199
|
-
allowedFilePolicy:
|
|
286
|
+
allowedFilePolicy: `Follow the target platform official rules first; merge only safe ${PRODUCT_NAME} disclosure and callbacks.`,
|
|
200
287
|
forbiddenPublicFiles: ['public tokens in public docs', 'signing secrets', 'hidden background processes']
|
|
201
288
|
}
|
|
202
289
|
};
|
|
@@ -224,14 +311,15 @@ function usage() {
|
|
|
224
311
|
|
|
225
312
|
Usage:
|
|
226
313
|
${COMMAND_NAME} inspect [skill-dir] [--json]
|
|
227
|
-
${COMMAND_NAME} publish [skill-dir] --code <dynamic-code> [--to
|
|
228
|
-
${COMMAND_NAME} setup [skill-dir] --code <dynamic-code> [--hub all|
|
|
229
|
-
${COMMAND_NAME} setup [skill-dir] --public-token <token> --skill-id <id> [--hub all|
|
|
314
|
+
${COMMAND_NAME} publish [skill-dir] --code <dynamic-code> [--to ${FIRST_PARTY_HUB},red]
|
|
315
|
+
${COMMAND_NAME} setup [skill-dir] --code <dynamic-code> [--hub all|${FIRST_PARTY_HUB}|red]
|
|
316
|
+
${COMMAND_NAME} setup [skill-dir] --public-token <token> --skill-id <id> [--hub all|${FIRST_PARTY_HUB}|red]
|
|
230
317
|
${COMMAND_NAME} doctor [skill-dir] [--json]
|
|
318
|
+
${COMMAND_NAME} preflight --claim-url <url> [--json]
|
|
231
319
|
${COMMAND_NAME} verify [skill-dir] [--hub <hub>] [--scenario <label>] [--dry-run]
|
|
232
320
|
${COMMAND_NAME} attach [skill-dir] --code <dynamic-code> [--name <name>]
|
|
233
321
|
${COMMAND_NAME} attach [skill-dir] --public-token <token> [--skill-id <id>] [--name <name>]
|
|
234
|
-
${COMMAND_NAME} prompt [skill-dir] --hub
|
|
322
|
+
${COMMAND_NAME} prompt [skill-dir] --hub <${FIRST_PARTY_HUB}|red> [--source-url <url>] [--out <file>]
|
|
235
323
|
${COMMAND_NAME} snippet [skill-dir] [--target js|curl] [--out <file>]
|
|
236
324
|
${COMMAND_NAME} track [skill-dir] --event <event> [--hub <hub>] [--installation-id <id>] [--invocation-id <id>]
|
|
237
325
|
|
|
@@ -239,12 +327,13 @@ Options:
|
|
|
239
327
|
--public-token <token> Public write token issued by ${PRODUCT_NAME} registry.
|
|
240
328
|
--code <dynamic-code> One-time ${PRODUCT_NAME} dashboard code used to claim registry identity.
|
|
241
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.
|
|
242
331
|
--skill-id <id> Public ${PRODUCT_NAME} Skill registry id.
|
|
243
332
|
--registry-url <url> Event endpoint written to the manifest.
|
|
244
333
|
--agent-ack-url <url> No-secret Agent Ack endpoint written to the local manifest.
|
|
245
334
|
--name <name> Skill display name override.
|
|
246
335
|
--description <text> Skill description override.
|
|
247
|
-
--hub <hub> Target Skill hub prompt style. setup defaults to
|
|
336
|
+
--hub <hub> Target Skill hub prompt style. setup defaults to ${DEFAULT_SETUP_HUBS.join(',')}.
|
|
248
337
|
--to <hub[,hub]> High-level publish targets. Alias for --hub in publish mode.
|
|
249
338
|
--event <event> Runtime event to submit for testing.
|
|
250
339
|
--installation-id <id> Stable anonymous install id for runtime analytics.
|
|
@@ -267,11 +356,13 @@ Options:
|
|
|
267
356
|
--source-url <url> User-provided source URL an Agent can use during third-party upload.
|
|
268
357
|
--package-url <url> Deprecated alias for --source-url.
|
|
269
358
|
--skill-version <version> Override the Skill release version without editing package.json.
|
|
270
|
-
--package-upload-url <url> Package upload ticket endpoint
|
|
271
|
-
--package-artifact-url <url> Package artifact attach endpoint
|
|
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.
|
|
272
361
|
--out <path> Output prompt or snippet file.
|
|
273
362
|
--out-dir <path> Output directory for setup artifacts. Defaults to ${WORK_DIR}/.
|
|
274
|
-
--no-
|
|
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.
|
|
275
366
|
--embed-skill-md Also write a clearly marked registry section into SKILL.md.
|
|
276
367
|
--no-skill-md Do not write registry text into SKILL.md. Red hub embeds by default.
|
|
277
368
|
--no-snippet Setup should skip writing the runtime analytics snippet.
|
|
@@ -290,7 +381,7 @@ function fail(message, code = 1) {
|
|
|
290
381
|
|
|
291
382
|
function parseArgs(argv) {
|
|
292
383
|
const args = { _: [], json: false, dryRun: false };
|
|
293
|
-
const booleanFlags = new Set(['embed-skill-md', 'no-skill-md', 'no-snippet', 'no-track', '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']);
|
|
294
385
|
for (let i = 0; i < argv.length; i += 1) {
|
|
295
386
|
const value = argv[i];
|
|
296
387
|
if (value === '--help' || value === '-h') {
|
|
@@ -445,7 +536,7 @@ function latestVersionEndpoint(inspectedOrRegistry = {}) {
|
|
|
445
536
|
|
|
446
537
|
function creatorFooterTemplate(inspected, language = 'zh') {
|
|
447
538
|
const registry = inspected.registry || {};
|
|
448
|
-
const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) ||
|
|
539
|
+
const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
|
|
449
540
|
const creatorName = normalizeText(registry.creatorProfile?.displayName || registry.creatorDisplayName || registry.creatorName, 120) || '待创作者补充';
|
|
450
541
|
const creatorSignal = normalizeText(registry.creatorProfile?.xiaSignal || registry.creatorXiaSignal || registry.xiaSignal, 80);
|
|
451
542
|
if (language === 'en') {
|
|
@@ -487,6 +578,9 @@ function normalizeHub(value) {
|
|
|
487
578
|
if (hub === 'xiashechat' || hub === 'xia-she' || hub === 'xiashe-store') {
|
|
488
579
|
return 'xiashe';
|
|
489
580
|
}
|
|
581
|
+
if (hub === 'agent-pie' || hub === 'agentpieapp' || hub === 'agentpie-registry' || hub === 'agentpieregistry') {
|
|
582
|
+
return 'agentpie';
|
|
583
|
+
}
|
|
490
584
|
if (hub === 'claudecode' || hub === 'claude-code') {
|
|
491
585
|
return 'claude';
|
|
492
586
|
}
|
|
@@ -540,8 +634,48 @@ function isLikelyEntrypointFile(filePath) {
|
|
|
540
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);
|
|
541
635
|
}
|
|
542
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
|
+
|
|
543
675
|
function packagePlanForHub(inspected, hub) {
|
|
544
676
|
const profile = reviewProfileForHub(hub);
|
|
677
|
+
const protectedPaidSkill = isProtectedPaidSkill(inspected);
|
|
678
|
+
const thirdPartyProtectedPackage = protectedPaidSkill && !['agentpie', 'xiashe'].includes(profile.hub);
|
|
545
679
|
const localOnly = [];
|
|
546
680
|
const candidateUploadFiles = [];
|
|
547
681
|
const forbiddenPatterns = [
|
|
@@ -562,6 +696,14 @@ function packagePlanForHub(inspected, hub) {
|
|
|
562
696
|
localOnly.push(file.path);
|
|
563
697
|
continue;
|
|
564
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
|
+
}
|
|
565
707
|
if (profile.packageMode === 'full_skill_package') {
|
|
566
708
|
candidateUploadFiles.push(file.path);
|
|
567
709
|
continue;
|
|
@@ -591,6 +733,8 @@ function packagePlanForHub(inspected, hub) {
|
|
|
591
733
|
return {
|
|
592
734
|
hub: profile.hub,
|
|
593
735
|
label: profile.label,
|
|
736
|
+
protectedPaidSkill,
|
|
737
|
+
thirdPartyProtectedPackage,
|
|
594
738
|
reviewProfile: profile,
|
|
595
739
|
dataSourcePolicy: {
|
|
596
740
|
runtime: 'Only real Skill execution callbacks, MCP/HTTP/API Tool calls, webhooks, or external Agent ack.',
|
|
@@ -606,17 +750,24 @@ function packagePlanForHub(inspected, hub) {
|
|
|
606
750
|
`${WORK_DIR}/runtime-events.js`,
|
|
607
751
|
`${WORK_DIR}/platform-profiles.json`
|
|
608
752
|
])).sort(),
|
|
609
|
-
disclosureTargets:
|
|
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,
|
|
610
756
|
forbiddenPatterns,
|
|
611
757
|
safetyChecklist: profile.safetyChecklist || [],
|
|
612
758
|
requiresUserConfirmation: true,
|
|
613
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
|
+
: '',
|
|
614
763
|
profile.allowedFilePolicy,
|
|
615
764
|
profile.manifestPolicy === 'local_only'
|
|
616
|
-
? `Keep ${WORK_DIR}/${MANIFEST_FILE} local by default. Only include
|
|
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.`
|
|
617
766
|
: 'Confirm platform file rules before uploading auxiliary registry metadata.',
|
|
618
767
|
hub === 'red'
|
|
619
|
-
?
|
|
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.`
|
|
620
771
|
: '',
|
|
621
772
|
profile.runtimeDataPolicy === 'attribution_only_by_default'
|
|
622
773
|
? 'Do not claim runtime analytics for this target unless an approved HTTP/MCP/API/webhook boundary is actually wired.'
|
|
@@ -678,12 +829,22 @@ async function inferSkillMetadata(root, flags = {}) {
|
|
|
678
829
|
skillKey: safeSkillKey(flags['skill-key'] || manifest?.skillKey || packageJson?.name || name),
|
|
679
830
|
description: normalizeText(flags.description || manifest?.description || packageJson?.description || firstParagraph || '', 500),
|
|
680
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,
|
|
681
837
|
registry: manifest?.registry || null
|
|
682
838
|
};
|
|
683
839
|
}
|
|
684
840
|
|
|
685
841
|
function shouldIgnore(name) {
|
|
686
|
-
return DEFAULT_IGNORE.has(name) ||
|
|
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');
|
|
687
848
|
}
|
|
688
849
|
|
|
689
850
|
async function walkFiles(root, current = root, out = []) {
|
|
@@ -729,6 +890,7 @@ async function postJson(url, body, timeoutMs = 20_000) {
|
|
|
729
890
|
method: 'POST',
|
|
730
891
|
headers: { 'content-type': 'application/json' },
|
|
731
892
|
body: JSON.stringify(body),
|
|
893
|
+
redirect: 'error',
|
|
732
894
|
signal: controller.signal
|
|
733
895
|
});
|
|
734
896
|
const text = await response.text();
|
|
@@ -742,18 +904,177 @@ async function postJson(url, body, timeoutMs = 20_000) {
|
|
|
742
904
|
throw new Error(payload.error || payload.message || `HTTP ${response.status}`);
|
|
743
905
|
}
|
|
744
906
|
return payload;
|
|
907
|
+
} catch (error) {
|
|
908
|
+
const message = error instanceof Error ? error.message : String(error || 'unknown error');
|
|
909
|
+
if (!isTransportError(error)) {
|
|
910
|
+
throw error;
|
|
911
|
+
}
|
|
912
|
+
throw new Error(`Network request failed for ${url}: ${message}`);
|
|
913
|
+
} finally {
|
|
914
|
+
clearTimeout(timer);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
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}`);
|
|
745
1019
|
} finally {
|
|
746
1020
|
clearTimeout(timer);
|
|
747
1021
|
}
|
|
748
1022
|
}
|
|
749
1023
|
|
|
750
|
-
function
|
|
751
|
-
const
|
|
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;
|
|
752
1060
|
try {
|
|
753
|
-
|
|
1061
|
+
candidate = new URL(raw);
|
|
754
1062
|
} catch {
|
|
755
|
-
|
|
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
|
+
);
|
|
756
1076
|
}
|
|
1077
|
+
return candidate.toString();
|
|
757
1078
|
}
|
|
758
1079
|
|
|
759
1080
|
function shouldIgnorePackageEntry(relativePath, entryName, isDirectory = false) {
|
|
@@ -838,11 +1159,14 @@ function tarHeader(file, size) {
|
|
|
838
1159
|
const header = Buffer.alloc(512, 0);
|
|
839
1160
|
const split = splitTarPath(file.relative);
|
|
840
1161
|
header.write(split.name, 0, 100, 'utf8');
|
|
841
|
-
|
|
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');
|
|
842
1166
|
header.write(octal(0, 8), 108, 8, 'ascii');
|
|
843
1167
|
header.write(octal(0, 8), 116, 8, 'ascii');
|
|
844
1168
|
header.write(octal(size, 12), 124, 12, 'ascii');
|
|
845
|
-
header.write(octal(
|
|
1169
|
+
header.write(octal(0, 12), 136, 12, 'ascii');
|
|
846
1170
|
header.fill(0x20, 148, 156);
|
|
847
1171
|
header.write('0', 156, 1, 'ascii');
|
|
848
1172
|
header.write('ustar', 257, 6, 'ascii');
|
|
@@ -877,7 +1201,10 @@ async function createTarGz(files) {
|
|
|
877
1201
|
}
|
|
878
1202
|
chunks.push(Buffer.alloc(1024, 0));
|
|
879
1203
|
const tar = Buffer.concat(chunks);
|
|
880
|
-
|
|
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 });
|
|
881
1208
|
if (gzipped.length > MAX_XIASHE_PACKAGE_BYTES) {
|
|
882
1209
|
throw new Error(`Package artifact exceeds ${Math.round(MAX_XIASHE_PACKAGE_BYTES / (1024 * 1024))}MB.`);
|
|
883
1210
|
}
|
|
@@ -896,10 +1223,10 @@ async function buildXiashePackageArtifact(root, inspected, flags = {}) {
|
|
|
896
1223
|
const skillMarkdownPresent = files.some((file) => /^SKILL(\.[a-z0-9]+)?$/i.test(file.relative));
|
|
897
1224
|
const manifestPresent = files.some((file) => file.relative === manifestRelative);
|
|
898
1225
|
if (!skillMarkdownPresent) {
|
|
899
|
-
throw new Error(
|
|
1226
|
+
throw new Error(`SKILL.md is required before uploading the ${PRODUCT_NAME} complete package.`);
|
|
900
1227
|
}
|
|
901
1228
|
if (!manifestPresent) {
|
|
902
|
-
throw new Error(`${manifestRelative} is required before uploading the
|
|
1229
|
+
throw new Error(`${manifestRelative} is required before uploading the ${PRODUCT_NAME} complete package.`);
|
|
903
1230
|
}
|
|
904
1231
|
const artifact = await createTarGz(files);
|
|
905
1232
|
const artifactDir = path.resolve(flags['package-out-dir'] || path.join(absoluteRoot, WORK_DIR, 'package-artifact'));
|
|
@@ -930,6 +1257,7 @@ async function uploadBytesToStorage(uploadUrl, bytes, contentType, timeoutMs = 6
|
|
|
930
1257
|
method: 'POST',
|
|
931
1258
|
headers: { 'content-type': contentType || 'application/octet-stream' },
|
|
932
1259
|
body: bytes,
|
|
1260
|
+
redirect: 'error',
|
|
933
1261
|
signal: controller.signal
|
|
934
1262
|
});
|
|
935
1263
|
const text = await response.text();
|
|
@@ -947,13 +1275,42 @@ async function uploadBytesToStorage(uploadUrl, bytes, contentType, timeoutMs = 6
|
|
|
947
1275
|
throw new Error('Upload response did not include storageId.');
|
|
948
1276
|
}
|
|
949
1277
|
return { ...payload, storageId };
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
const message = error instanceof Error ? error.message : String(error || 'unknown error');
|
|
1280
|
+
if (!isTransportError(error)) {
|
|
1281
|
+
throw error;
|
|
1282
|
+
}
|
|
1283
|
+
throw new Error(`Package upload failed for ${uploadUrl}: ${message}`);
|
|
950
1284
|
} finally {
|
|
951
1285
|
clearTimeout(timer);
|
|
952
1286
|
}
|
|
953
1287
|
}
|
|
954
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
|
+
|
|
955
1312
|
async function uploadXiashePackageArtifact(root, flags, manifestResult) {
|
|
956
|
-
if (flags.dryRun || flags['no-xiashe-package-upload']) {
|
|
1313
|
+
if (flags.dryRun || flags['no-package-upload'] || flags['no-xiashe-package-upload']) {
|
|
957
1314
|
return {
|
|
958
1315
|
ok: true,
|
|
959
1316
|
skipped: true,
|
|
@@ -964,39 +1321,93 @@ async function uploadXiashePackageArtifact(root, flags, manifestResult) {
|
|
|
964
1321
|
const registry = manifestResult.manifest?.registry || inspected.registry || {};
|
|
965
1322
|
const publicToken = normalizeText(registry.publicToken, 512);
|
|
966
1323
|
if (!publicToken) {
|
|
967
|
-
throw new Error(
|
|
1324
|
+
throw new Error(`Cannot upload ${PRODUCT_NAME} package without registry publicToken.`);
|
|
968
1325
|
}
|
|
969
1326
|
const artifact = await buildXiashePackageArtifact(root, inspected, flags);
|
|
970
1327
|
const registrySourceSha256 =
|
|
971
1328
|
normalizeText(manifestResult.manifest?.sourceFingerprint?.sha256, 128) ||
|
|
972
1329
|
normalizeText(inspected.package?.sha256, 128) ||
|
|
973
1330
|
artifact.sourceSha256;
|
|
974
|
-
|
|
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
|
+
}
|
|
975
1347
|
const uploadTicket =
|
|
976
1348
|
manifestResult.claimPackageArtifactUpload ||
|
|
977
1349
|
await postJson(
|
|
978
|
-
|
|
1350
|
+
registryEndpointUrl(claimUrl, '/registry/skill/package-upload-url', flags['package-upload-url'], 'package upload'),
|
|
979
1351
|
{
|
|
980
1352
|
publicToken,
|
|
981
1353
|
idempotencyKey: registrySourceSha256
|
|
982
1354
|
},
|
|
983
1355
|
Number(flags['timeout-ms'] || 20_000)
|
|
984
1356
|
);
|
|
985
|
-
|
|
1357
|
+
let activeUploadTicket = uploadTicket;
|
|
1358
|
+
let uploadUrl = normalizeText(activeUploadTicket?.uploadUrl, 2_000);
|
|
986
1359
|
if (!uploadUrl) {
|
|
987
|
-
|
|
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
|
+
}
|
|
988
1397
|
}
|
|
989
1398
|
const bytes = await readFile(artifact.artifactPath);
|
|
990
1399
|
const uploaded = await uploadBytesToStorage(uploadUrl, bytes, artifact.contentType, Number(flags['upload-timeout-ms'] || 90_000));
|
|
991
1400
|
const attachResult = await postJson(
|
|
992
|
-
|
|
1401
|
+
registryEndpointUrl(claimUrl, '/registry/skill/package-artifact', flags['package-artifact-url'], 'package artifact'),
|
|
993
1402
|
{
|
|
994
1403
|
publicToken,
|
|
995
1404
|
storageId: uploaded.storageId,
|
|
996
|
-
uploadJobId: normalizeText(
|
|
1405
|
+
uploadJobId: normalizeText(activeUploadTicket?.uploadJobId, 160) || undefined,
|
|
997
1406
|
idempotencyKey: registrySourceSha256,
|
|
998
1407
|
fileName: artifact.fileName,
|
|
999
1408
|
contentType: artifact.contentType,
|
|
1409
|
+
sourceSha256: registrySourceSha256,
|
|
1410
|
+
artifactSha256: artifact.artifactSha256,
|
|
1000
1411
|
packageSha256: registrySourceSha256,
|
|
1001
1412
|
packageFileCount: artifact.fileCount,
|
|
1002
1413
|
packageTotalBytes: artifact.sourceTotalBytes,
|
|
@@ -1004,11 +1415,21 @@ async function uploadXiashePackageArtifact(root, flags, manifestResult) {
|
|
|
1004
1415
|
manifestPresent: artifact.manifestPresent,
|
|
1005
1416
|
skillMarkdownPresent: artifact.skillMarkdownPresent,
|
|
1006
1417
|
creatorCardUrl: normalizeText(registry.creatorCardUrl, 800) || undefined,
|
|
1007
|
-
platformSource:
|
|
1418
|
+
platformSource: REGISTRY_PROVIDER,
|
|
1008
1419
|
source: `${REGISTRY_PROVIDER}_skill_cli`
|
|
1009
1420
|
},
|
|
1010
1421
|
Number(flags['timeout-ms'] || 20_000)
|
|
1011
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
|
+
}
|
|
1012
1433
|
return {
|
|
1013
1434
|
ok: true,
|
|
1014
1435
|
skipped: false,
|
|
@@ -1016,13 +1437,14 @@ async function uploadXiashePackageArtifact(root, flags, manifestResult) {
|
|
|
1016
1437
|
fileName: artifact.fileName,
|
|
1017
1438
|
sizeBytes: artifact.sizeBytes,
|
|
1018
1439
|
sourceSha256: registrySourceSha256,
|
|
1019
|
-
|
|
1440
|
+
archiveSourceSha256: artifact.sourceSha256,
|
|
1020
1441
|
artifactSha256: artifact.artifactSha256,
|
|
1021
1442
|
fileCount: artifact.fileCount,
|
|
1022
1443
|
storageId: uploaded.storageId,
|
|
1023
|
-
publishStatus: attachResult.publishStatus ?? null,
|
|
1024
|
-
uploadJobId: attachResult.uploadJobId ?? normalizeText(
|
|
1025
|
-
attachResult
|
|
1444
|
+
publishStatus: finalStatus.status ?? attachResult.publishStatus ?? null,
|
|
1445
|
+
uploadJobId: attachResult.uploadJobId ?? normalizeText(activeUploadTicket?.uploadJobId, 160) ?? null,
|
|
1446
|
+
attachResult,
|
|
1447
|
+
finalStatus
|
|
1026
1448
|
};
|
|
1027
1449
|
}
|
|
1028
1450
|
|
|
@@ -1051,9 +1473,19 @@ async function inspectSkill(root, flags = {}) {
|
|
|
1051
1473
|
async function writeManifest(root, flags) {
|
|
1052
1474
|
const inspected = await inspectSkill(root, flags);
|
|
1053
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;
|
|
1054
1486
|
const claim = code
|
|
1055
1487
|
? await postJson(
|
|
1056
|
-
|
|
1488
|
+
registryPreflight.claimUrl,
|
|
1057
1489
|
{
|
|
1058
1490
|
code,
|
|
1059
1491
|
skill: {
|
|
@@ -1061,6 +1493,9 @@ async function writeManifest(root, flags) {
|
|
|
1061
1493
|
skillKey: inspected.skillKey,
|
|
1062
1494
|
description: inspected.description,
|
|
1063
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.
|
|
1064
1499
|
packageSha256: inspected.package.sha256,
|
|
1065
1500
|
packageFileCount: inspected.package.fileCount,
|
|
1066
1501
|
packageTotalBytes: inspected.package.totalBytes
|
|
@@ -1073,7 +1508,13 @@ async function writeManifest(root, flags) {
|
|
|
1073
1508
|
Number(flags['timeout-ms'] || 20_000)
|
|
1074
1509
|
)
|
|
1075
1510
|
: null;
|
|
1076
|
-
|
|
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
|
+
}
|
|
1077
1518
|
const publicToken = normalizeText(flags['public-token'] || claim?.publicToken || existingRegistry.publicToken, 512);
|
|
1078
1519
|
if (!publicToken) {
|
|
1079
1520
|
throw new Error('Missing --code or --public-token.');
|
|
@@ -1100,10 +1541,23 @@ async function writeManifest(root, flags) {
|
|
|
1100
1541
|
skillKey: safeSkillKey(claim?.skillKey || inspected.skillKey),
|
|
1101
1542
|
description: normalizeText(claim?.description, 500) || inspected.description,
|
|
1102
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,
|
|
1103
1549
|
registry: {
|
|
1104
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,
|
|
1105
1554
|
skillId: normalizeText(flags['skill-id'] || claim?.skillId || existingRegistry.skillId, 160) || null,
|
|
1106
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,
|
|
1107
1561
|
registryUrl: normalizeText(flags['registry-url'] || claim?.registryUrl || existingRegistry.registryUrl, 800) || DEFAULT_REGISTRY_URL,
|
|
1108
1562
|
agentAckUrl: normalizeText(flags['agent-ack-url'] || existingRegistry.agentAckUrl, 800) || DEFAULT_AGENT_ACK_URL,
|
|
1109
1563
|
creatorCardUrl,
|
|
@@ -1178,22 +1632,26 @@ async function writeManifest(root, flags) {
|
|
|
1178
1632
|
ok: true,
|
|
1179
1633
|
manifestPath,
|
|
1180
1634
|
manifest,
|
|
1181
|
-
claimPackageArtifactUpload: claim?.packageArtifactUpload ?? null
|
|
1635
|
+
claimPackageArtifactUpload: claim?.packageArtifactUpload ?? null,
|
|
1636
|
+
registryPreflight
|
|
1182
1637
|
};
|
|
1183
1638
|
}
|
|
1184
1639
|
|
|
1185
1640
|
function hubInstruction(hub) {
|
|
1641
|
+
if (hub === 'agentpie') {
|
|
1642
|
+
return 'AgentPie Registry publish is the full Skill package and registry manifest flow. Keep public disclosure, attribution, and runtime analytics callbacks; if the local manifest has a registry public token or a creator-authenticated CLI/API session exists, create or reuse the AgentPie registry draft.';
|
|
1643
|
+
}
|
|
1186
1644
|
if (hub === 'xiashe') {
|
|
1187
1645
|
return '虾舍商店发布是完整 Skill 包和 registry manifest 的主流程。请保留公开披露、归因和 runtime analytics 回传能力;如果本地 manifest 有 registry public token 或已有创作者认证 CLI/API 会话,就创建或复用虾舍商店草稿。';
|
|
1188
1646
|
}
|
|
1189
1647
|
if (hub === 'red') {
|
|
1190
|
-
return
|
|
1648
|
+
return `Red Skill 的官方 uploader.md / skillhub-upload / 页面流程是主流程。请优先使用本机 skillhub-upload;${PRODUCT_NAME} 负责生成平台安全披露、connector/运行时协议和本地 registry handoff,并把 registry id、public token、endpoint 等后台字段保留在本地私有配置或平台私有字段里。`;
|
|
1191
1649
|
}
|
|
1192
1650
|
if (hub === 'clawhub') {
|
|
1193
|
-
return
|
|
1651
|
+
return `ClawHub 的官方 CLI、prompt 或页面流程是主流程。请先确认 ClawHub 当前要求,再把下面的 ${PRODUCT_NAME} registry 要求作为附加说明合并进去。`;
|
|
1194
1652
|
}
|
|
1195
1653
|
if (hub === 'skillhub') {
|
|
1196
|
-
return
|
|
1654
|
+
return `SkillHub 的官方 CLI、prompt 或页面流程是主流程。请先确认 SkillHub 当前要求,再把下面的 ${PRODUCT_NAME} registry 要求作为附加说明合并进去。`;
|
|
1197
1655
|
}
|
|
1198
1656
|
if (hub === 'claude') {
|
|
1199
1657
|
return 'Claude Skills 的官方上传/导入流程是主流程。请先确认 Claude 当前 Skill 格式和共享要求,再把下面的 registry 披露文本放入允许的说明文件。';
|
|
@@ -1213,45 +1671,56 @@ function hubInstruction(hub) {
|
|
|
1213
1671
|
if (hub === 'coze') {
|
|
1214
1672
|
return 'Coze Agent/Plugin/Workflow 的官方配置和发布流程是主流程。请先按 Coze 当前工具、插件或知识库要求配置,再把下面的 registry 回调作为可选 HTTP/API 步骤接入。';
|
|
1215
1673
|
}
|
|
1216
|
-
return
|
|
1674
|
+
return `目标 Skill Hub 的官方 CLI、prompt 或页面流程是主流程。请先确认平台当前要求,再把下面的 ${PRODUCT_NAME} registry 要求作为附加说明合并进去。`;
|
|
1217
1675
|
}
|
|
1218
1676
|
|
|
1219
1677
|
function platformPromptPlaceholder(hub) {
|
|
1678
|
+
if (hub === 'agentpie') {
|
|
1679
|
+
return 'If the user is publishing to AgentPie Registry and the current environment has a creator-authenticated CLI/API session, create the AgentPie registry draft first; otherwise finish registry setup, disclosure, platform profile, and handoff files. Do not apply third-party marketplace limits to the AgentPie complete Skill package.';
|
|
1680
|
+
}
|
|
1220
1681
|
if (hub === 'xiashe') {
|
|
1221
1682
|
return '如果用户要发布到虾舍商店,并且当前环境已有创作者认证 CLI/API 会话,请优先创建虾舍商店草稿;如果没有认证会话,请完成 registry setup、披露、平台 profile 和 handoff 文件,不要要求 Agent 宿主登录或索取账号凭证。不要把第三方平台限制套用到虾舍完整 Skill 包。';
|
|
1222
1683
|
}
|
|
1223
1684
|
if (hub === 'red') {
|
|
1224
|
-
return
|
|
1685
|
+
return `如果本机有 skillhub-upload,请按小红书官方 CLI 流程 login --agent、打包并提交;否则请让用户从小红书创作服务平台复制 uploader.md 里的官方对话上传口令。不要用 ${PRODUCT_NAME} prompt 替代 Red Skill 官方流程。`;
|
|
1225
1686
|
}
|
|
1226
1687
|
if (hub === 'clawhub') {
|
|
1227
|
-
return
|
|
1688
|
+
return `如果 ClawHub 提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用 ${PRODUCT_NAME} prompt 替代 ClawHub 官方流程。`;
|
|
1228
1689
|
}
|
|
1229
1690
|
if (hub === 'skillhub') {
|
|
1230
|
-
return
|
|
1691
|
+
return `如果 SkillHub 提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用 ${PRODUCT_NAME} prompt 替代 SkillHub 官方流程。`;
|
|
1231
1692
|
}
|
|
1232
1693
|
if (hub === 'claude') {
|
|
1233
|
-
return
|
|
1694
|
+
return `如果 Claude 提供官方 Skill 导入/上传说明,请优先使用官方说明;不要用 ${PRODUCT_NAME} prompt 替代 Claude 官方流程。`;
|
|
1234
1695
|
}
|
|
1235
1696
|
if (hub === 'codex') {
|
|
1236
|
-
return
|
|
1697
|
+
return `如果 Codex 提供官方 Skill 或 Agent 能力安装说明,请优先使用官方说明;不要用 ${PRODUCT_NAME} prompt 替代 Codex 官方流程。`;
|
|
1237
1698
|
}
|
|
1238
1699
|
if (hub === 'cursor') {
|
|
1239
|
-
return
|
|
1700
|
+
return `如果 Cursor 提供官方规则、扩展或 Agent 能力安装说明,请优先使用官方说明;不要用 ${PRODUCT_NAME} prompt 替代 Cursor 官方流程。`;
|
|
1240
1701
|
}
|
|
1241
1702
|
if (hub === 'workbuddy') {
|
|
1242
|
-
return
|
|
1703
|
+
return `如果 WorkBuddy 提供官方上传 prompt、网页或 API 流程,请优先使用官方流程;不要用 ${PRODUCT_NAME} prompt 替代 WorkBuddy 官方流程。`;
|
|
1243
1704
|
}
|
|
1244
1705
|
if (hub === 'dify') {
|
|
1245
|
-
return
|
|
1706
|
+
return `如果 Dify 提供官方插件打包/发布命令,请优先使用官方命令;不要用 ${PRODUCT_NAME} prompt 替代 Dify 官方流程。`;
|
|
1246
1707
|
}
|
|
1247
1708
|
if (hub === 'coze') {
|
|
1248
|
-
return
|
|
1709
|
+
return `如果 Coze 提供 Agent/Plugin/Workflow 的配置步骤,请优先使用 Coze 官方配置;不要用 ${PRODUCT_NAME} prompt 替代 Coze 官方流程。`;
|
|
1249
1710
|
}
|
|
1250
|
-
return
|
|
1711
|
+
return `如果目标平台提供官方 CLI 命令或上传 prompt,请优先使用官方命令/prompt;不要用 ${PRODUCT_NAME} prompt 替代平台官方流程。`;
|
|
1251
1712
|
}
|
|
1252
1713
|
|
|
1253
|
-
function uploadCompatibilityLines(hub, manifestFile) {
|
|
1714
|
+
function uploadCompatibilityLines(hub, manifestFile, protectedPaidSkill = false) {
|
|
1254
1715
|
const localManifest = `${WORK_DIR}/${manifestFile}`;
|
|
1716
|
+
if (hub === 'agentpie') {
|
|
1717
|
+
return [
|
|
1718
|
+
'AgentPie Registry compatibility requirements:',
|
|
1719
|
+
`- AgentPie Registry can receive the complete Skill package and ${localManifest} registry manifest.`,
|
|
1720
|
+
'- Keep public disclosure text, attribution links, and runtime event callbacks; do not remove the registry manifest.',
|
|
1721
|
+
'- Before submitting a draft, still exclude .env, keys, node_modules, dist/build, and unrelated local files.'
|
|
1722
|
+
];
|
|
1723
|
+
}
|
|
1255
1724
|
if (hub === 'xiashe') {
|
|
1256
1725
|
return [
|
|
1257
1726
|
'虾舍商店兼容性要求:',
|
|
@@ -1263,14 +1732,16 @@ function uploadCompatibilityLines(hub, manifestFile) {
|
|
|
1263
1732
|
if (hub === 'red') {
|
|
1264
1733
|
return [
|
|
1265
1734
|
'Red Skill 兼容性要求:',
|
|
1266
|
-
|
|
1267
|
-
|
|
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、公开来源与安全使用边界。`,
|
|
1268
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。`,
|
|
1269
1740
|
'- 不要要求用户可见回复追加广告占位、telemetry JSON、包指纹、后台字段或本地路径。',
|
|
1270
|
-
`- ${localManifest}
|
|
1271
|
-
|
|
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 公开介绍或公开包。`,
|
|
1272
1743
|
'- publicSkillId 与 Agent Ack endpoint 只能出现在专门的 no-secret 协议文件或平台私有配置里;不要写进营销介绍、用户可见回复或截图说明。',
|
|
1273
|
-
|
|
1744
|
+
`- Agent 使用 Skill 后,只有宿主平台或本地私有配置实际执行安全 Ack,${PRODUCT_NAME} 才会记录 installed / used / completed / failed 真实统计。`,
|
|
1274
1745
|
'- 如果 Red 平台或运行环境没有执行 Agent Ack,则不要伪造 runtime;只保留上传成功和 reported 数据。',
|
|
1275
1746
|
'',
|
|
1276
1747
|
'Red Skill 基础安全规范:',
|
|
@@ -1295,20 +1766,22 @@ function uploadCompatibilityLines(hub, manifestFile) {
|
|
|
1295
1766
|
function publicDisclosure(inspected, registryUrl, hub = 'generic') {
|
|
1296
1767
|
const creatorCardUrl = normalizeText(inspected.registry?.creatorCardUrl, 800);
|
|
1297
1768
|
const creatorLines = creatorProfileSummaryLines(inspected.registry || {}, 'en');
|
|
1769
|
+
const protectedPaidSkill = isProtectedPaidSkill(inspected);
|
|
1298
1770
|
if (hub === 'red') {
|
|
1299
1771
|
return [
|
|
1300
1772
|
'公开披露文本:',
|
|
1301
1773
|
'```markdown',
|
|
1302
1774
|
`### ${PRODUCT_NAME} Attribution Disclosure`,
|
|
1303
1775
|
'',
|
|
1304
|
-
`This Skill may include
|
|
1305
|
-
creatorCardUrl ? `Creator card: ${creatorCardUrl}` :
|
|
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>`,
|
|
1306
1778
|
...creatorLines.filter((line) => !line.startsWith('- Creator card:')),
|
|
1307
1779
|
'',
|
|
1308
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,
|
|
1309
1782
|
'- Data mode for Red Skill upload: Agent Ack runtime only when the host Agent calls a no-secret Ack protocol; upload/reported otherwise.',
|
|
1310
|
-
|
|
1311
|
-
|
|
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.`,
|
|
1312
1785
|
'- Do not send user prompts, private files, credentials, environment variables, account tokens, raw business content, or personal identifiers.',
|
|
1313
1786
|
'```'
|
|
1314
1787
|
];
|
|
@@ -1322,7 +1795,7 @@ function publicDisclosure(inspected, registryUrl, hub = 'generic') {
|
|
|
1322
1795
|
'',
|
|
1323
1796
|
`- Registry provider: ${REGISTRY_PROVIDER}`,
|
|
1324
1797
|
`- Skill key: ${inspected.skillKey}`,
|
|
1325
|
-
`- Creator card: ${creatorCardUrl ||
|
|
1798
|
+
`- Creator card: ${creatorCardUrl || `<creator card link from ${PRODUCT_NAME} dashboard>`}`,
|
|
1326
1799
|
...creatorLines.filter((line) => !line.startsWith('- Creator card:')),
|
|
1327
1800
|
'- Events, if enabled by the runtime/platform, should contain only public metadata and aggregate status.',
|
|
1328
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.',
|
|
@@ -1334,9 +1807,10 @@ function publicDisclosure(inspected, registryUrl, hub = 'generic') {
|
|
|
1334
1807
|
|
|
1335
1808
|
function agentAckInstructionBlock(inspected, hub = 'generic') {
|
|
1336
1809
|
const registry = inspected.registry || {};
|
|
1810
|
+
const protectedPaidSkill = isProtectedPaidSkill(inspected);
|
|
1337
1811
|
const publicSkillId = registry.skillId || registry.publicSkillId || '<public skill id from local registry manifest>';
|
|
1338
1812
|
const ackUrl = registry.agentAckUrl || DEFAULT_AGENT_ACK_URL;
|
|
1339
|
-
const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) ||
|
|
1813
|
+
const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
|
|
1340
1814
|
const eventSchemaVersion = registry.runtimeSchemaVersion || RUNTIME_SCHEMA_VERSION;
|
|
1341
1815
|
const normalizedHub = normalizeHub(hub) || hub || 'generic';
|
|
1342
1816
|
const distributionPlatform = runtimeDistributionPlatformForHub(normalizedHub);
|
|
@@ -1355,10 +1829,16 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
|
|
|
1355
1829
|
`- Runtime schema: ${eventSchemaVersion}`,
|
|
1356
1830
|
`- Platform hub: ${normalizedHub}`,
|
|
1357
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,
|
|
1358
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.',
|
|
1359
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.',
|
|
1360
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.`,
|
|
1361
|
-
|
|
1841
|
+
`- Match ${PRODUCT_NAME} display text to the current conversation language. Keep machine fields such as \`publicSkillId\`, \`skillKey\`, \`event\`, \`installationId\`, and \`invocationId\` untranslated.`,
|
|
1362
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`.',
|
|
1363
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.',
|
|
1364
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.',
|
|
@@ -1412,8 +1892,9 @@ function agentAckInstructionBlock(inspected, hub = 'generic') {
|
|
|
1412
1892
|
|
|
1413
1893
|
function redPublicTrackingContractBlock(inspected) {
|
|
1414
1894
|
const registry = inspected.registry || {};
|
|
1895
|
+
const protectedPaidSkill = isProtectedPaidSkill(inspected);
|
|
1415
1896
|
const marker = `${REGISTRY_PROVIDER}-runtime-contract`;
|
|
1416
|
-
const cardUrl = normalizeText(registry.creatorCardUrl, 800) ||
|
|
1897
|
+
const cardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
|
|
1417
1898
|
const currentVersion = normalizeText(inspected.version || registry.version, 80) || '1.0.0';
|
|
1418
1899
|
const updateEndpoint = latestVersionEndpoint(inspected);
|
|
1419
1900
|
const footer = creatorFooterTemplate(inspected, 'zh');
|
|
@@ -1422,6 +1903,9 @@ function redPublicTrackingContractBlock(inspected) {
|
|
|
1422
1903
|
`## ${PRODUCT_NAME} Required Runtime Contract`,
|
|
1423
1904
|
'',
|
|
1424
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
|
+
: '',
|
|
1425
1909
|
'',
|
|
1426
1910
|
'### 检查更新',
|
|
1427
1911
|
'',
|
|
@@ -1440,7 +1924,7 @@ function redPublicTrackingContractBlock(inspected) {
|
|
|
1440
1924
|
footer,
|
|
1441
1925
|
'```',
|
|
1442
1926
|
'',
|
|
1443
|
-
|
|
1927
|
+
`如果当前对话使用英文,可以使用 runtime.yaml 里的英文 footer;否则默认使用上面的中文 footer。推荐位只使用 ${PRODUCT_NAME} Ack 响应返回的内容,没有返回时写“无”。`,
|
|
1444
1928
|
'',
|
|
1445
1929
|
'### 安全边界',
|
|
1446
1930
|
'',
|
|
@@ -1461,7 +1945,8 @@ function skillMdRegistryBlock(inspected, hub) {
|
|
|
1461
1945
|
return redPublicTrackingContractBlock(inspected);
|
|
1462
1946
|
}
|
|
1463
1947
|
const registry = inspected.registry || {};
|
|
1464
|
-
const
|
|
1948
|
+
const protectedPaidSkill = isProtectedPaidSkill(inspected);
|
|
1949
|
+
const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
|
|
1465
1950
|
const marker = `${REGISTRY_PROVIDER}-registry`;
|
|
1466
1951
|
const currentVersion = normalizeText(inspected.version || registry.version, 80) || '1.0.0';
|
|
1467
1952
|
const updateEndpoint = latestVersionEndpoint(inspected);
|
|
@@ -1470,6 +1955,9 @@ function skillMdRegistryBlock(inspected, hub) {
|
|
|
1470
1955
|
`## ${PRODUCT_NAME} Required Runtime Contract`,
|
|
1471
1956
|
'',
|
|
1472
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
|
+
: '',
|
|
1473
1961
|
'',
|
|
1474
1962
|
'### Update Check',
|
|
1475
1963
|
'',
|
|
@@ -1542,6 +2030,7 @@ function yamlStringList(values, indent = 6) {
|
|
|
1542
2030
|
function runtimeDistributionPlatformForHub(hub) {
|
|
1543
2031
|
const normalized = normalizeHub(hub) || 'unknown';
|
|
1544
2032
|
if (normalized === 'red') return 'xiaohongshu-redskill';
|
|
2033
|
+
if (normalized === 'agentpie') return 'agentpie';
|
|
1545
2034
|
if (normalized === 'xiashe') return 'xiashe';
|
|
1546
2035
|
if (normalized === 'coze') return 'coze-store';
|
|
1547
2036
|
if (normalized === 'codex' || normalized === 'claude' || normalized === 'cursor' || normalized === 'workbuddy') return 'local';
|
|
@@ -1550,8 +2039,9 @@ function runtimeDistributionPlatformForHub(hub) {
|
|
|
1550
2039
|
|
|
1551
2040
|
function buildRuntimeYaml(inspected, hub = 'red') {
|
|
1552
2041
|
const registry = inspected.registry || {};
|
|
1553
|
-
const
|
|
1554
|
-
const
|
|
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>`;
|
|
1555
2045
|
const creatorName = normalizeText(registry.creatorProfile?.displayName || registry.creatorDisplayName || registry.creatorName, 120) || '<creator display name>';
|
|
1556
2046
|
const creatorSignal = normalizeText(registry.creatorProfile?.xiaSignal || registry.creatorXiaSignal || registry.xiaSignal, 80) || '<creator signal>';
|
|
1557
2047
|
const ackUrl = registry.agentAckUrl || DEFAULT_AGENT_ACK_URL;
|
|
@@ -1563,6 +2053,11 @@ function buildRuntimeYaml(inspected, hub = 'red') {
|
|
|
1563
2053
|
`publicSkillId: ${yamlString(publicSkillId)}`,
|
|
1564
2054
|
`skillKey: ${yamlString(inspected.skillKey)}`,
|
|
1565
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')}`,
|
|
1566
2061
|
'updateCheck:',
|
|
1567
2062
|
" mode: 'prompt_user'",
|
|
1568
2063
|
` currentVersion: ${yamlString(skillVersion)}`,
|
|
@@ -1682,7 +2177,7 @@ async function writePublicProtocolFiles(root, inspected, hub = 'red') {
|
|
|
1682
2177
|
const disclosurePath = path.join(protocolDir, 'REGISTRY_DISCLOSURE.md');
|
|
1683
2178
|
const runtimePath = path.join(protocolDir, 'runtime.yaml');
|
|
1684
2179
|
const registry = inspected.registry || {};
|
|
1685
|
-
const publicSkillId = registry.skillId || registry.publicSkillId ||
|
|
2180
|
+
const publicSkillId = registry.skillId || registry.publicSkillId || `<public skill id from ${PRODUCT_NAME} registry>`;
|
|
1686
2181
|
const agentAck = [
|
|
1687
2182
|
agentAckInstructionBlock(inspected, hub),
|
|
1688
2183
|
'',
|
|
@@ -1752,6 +2247,7 @@ async function uploadPrompt(inspected, flags) {
|
|
|
1752
2247
|
const hub = normalizeHub(flags.hub || 'generic') || 'generic';
|
|
1753
2248
|
const reviewProfile = reviewProfileForHub(hub);
|
|
1754
2249
|
const packagePlan = packagePlanForHub(inspected, hub);
|
|
2250
|
+
const protectedPaidSkill = isProtectedPaidSkill(inspected);
|
|
1755
2251
|
const sourceUrl = normalizeText(flags['source-url'] || flags['package-url'], 1000);
|
|
1756
2252
|
const platformCommand = normalizeText(flags['platform-command'], 1000);
|
|
1757
2253
|
const platformPrompt = await readPlatformPrompt(flags);
|
|
@@ -1759,11 +2255,11 @@ async function uploadPrompt(inspected, flags) {
|
|
|
1759
2255
|
const registryUrl = registry.registryUrl || DEFAULT_REGISTRY_URL;
|
|
1760
2256
|
const agentAckUrl = registry.agentAckUrl || DEFAULT_AGENT_ACK_URL;
|
|
1761
2257
|
const publicSkillId = registry.skillId || registry.publicSkillId || '<public skill id from local registry manifest>';
|
|
1762
|
-
const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) ||
|
|
2258
|
+
const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
|
|
1763
2259
|
const creatorProfileLines = creatorProfileSummaryLines(registry, 'zh').filter((line) => !line.startsWith('- 创作者名片:'));
|
|
1764
2260
|
const redHub = hub === 'red';
|
|
1765
2261
|
const eventPayload = {
|
|
1766
|
-
publicToken: registry.publicToken ||
|
|
2262
|
+
publicToken: registry.publicToken || `<public token from ${WORK_DIR}/${MANIFEST_FILE}>`,
|
|
1767
2263
|
schemaVersion: registry.eventSchemaVersion || EVENT_SCHEMA_VERSION,
|
|
1768
2264
|
eventType: 'hub_upload_succeeded',
|
|
1769
2265
|
eventSource: 'cli',
|
|
@@ -1815,15 +2311,15 @@ async function uploadPrompt(inspected, flags) {
|
|
|
1815
2311
|
'第三方平台官方流程:',
|
|
1816
2312
|
`- ${hubLine}`,
|
|
1817
2313
|
redHub
|
|
1818
|
-
?
|
|
1819
|
-
:
|
|
1820
|
-
|
|
2314
|
+
? `- 平台官方 prompt / CLI / 页面表单是上传依据;${PRODUCT_NAME} 提供本地 registry、Agent Ack 真实统计、安全披露和上传状态记录。`
|
|
2315
|
+
: `- 平台官方 prompt / CLI / 页面表单是上传依据;${PRODUCT_NAME} 只提供 registry 和 analytics 附加要求。`,
|
|
2316
|
+
`- 如果平台官方要求与 ${PRODUCT_NAME} 附加说明冲突,以平台官方上传要求为准;不要为了 analytics 绕过平台规则。`,
|
|
1821
2317
|
platformCommand ? `- 用户提供的官方命令:${platformCommand}` : `- ${platformPromptPlaceholder(hub)}`
|
|
1822
2318
|
];
|
|
1823
2319
|
if (platformPrompt) {
|
|
1824
2320
|
officialFlowLines.push('', '用户提供的第三方平台官方 prompt:', '```text', platformPrompt.trim(), '```');
|
|
1825
2321
|
}
|
|
1826
|
-
const compatibilityLines = uploadCompatibilityLines(hub, MANIFEST_FILE);
|
|
2322
|
+
const compatibilityLines = uploadCompatibilityLines(hub, MANIFEST_FILE, protectedPaidSkill);
|
|
1827
2323
|
const disclosureLines = publicDisclosure(inspected, registryUrl, hub);
|
|
1828
2324
|
const runtimeGuidanceLines = redHub
|
|
1829
2325
|
? [
|
|
@@ -1834,14 +2330,21 @@ async function uploadPrompt(inspected, flags) {
|
|
|
1834
2330
|
`- 描述:${inspected.description || '<从 SKILL.md/README.md 中提炼>'}`,
|
|
1835
2331
|
`- 创作者名片:${creatorCardUrl}`,
|
|
1836
2332
|
...creatorProfileLines,
|
|
1837
|
-
|
|
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,
|
|
1838
2339
|
`- ${WORK_DIR}/、public token、signing secret、registry id、package sha、storage id、upload job id、runtime-events.js 和内部 handoff/checklist 默认只在本地保留,不进入 Red 公开包。`,
|
|
1839
2340
|
`- publicSkillId 和 Agent Ack endpoint 只能出现在 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置里;不要写进营销介绍、用户可见回复或截图说明。`,
|
|
1840
|
-
|
|
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。`,
|
|
1841
2344
|
'- 使用 Skill 前如果能联网,Agent 应先检查最新版本;有新版本就提示用户是否更新,无法联网则继续但说明更新状态未检查。',
|
|
1842
|
-
|
|
2345
|
+
`- 如果输出数字或链接,必须结构化展示;如果平台无法执行安全 Ack,不要伪造 ${PRODUCT_NAME} runtime 数据。`,
|
|
1843
2346
|
'- 上传成功后可以用 hub_upload_succeeded 记录公开 URL;这属于 attribution,不代表真实运行次数。',
|
|
1844
|
-
`- 如果 Agent 读取 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置并执行 Agent Ack,installed / used / completed / failed
|
|
2347
|
+
`- 如果 Agent 读取 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置并执行 Agent Ack,installed / used / completed / failed 会进入 ${PRODUCT_NAME} runtime 统计。`,
|
|
1845
2348
|
'- 如果平台公开展示下载、收藏、安装或使用数字,可以后续按 reported 数据手动导入;不要把 reported 伪造成 runtime。',
|
|
1846
2349
|
'- 先检查“能否安全上传”,再检查“Agent Ack 能否被执行”;二者在报告里分开。',
|
|
1847
2350
|
`- Agent Ack 的字段要拆清楚:distributionPlatform 是分发平台,runtimeAgent 是实际运行的 Agent,runtimeHost 是宿主环境,sourceSurface 是发现入口,scenario 是枚举键,scenarioLabel 是短的人类可读场景。`,
|
|
@@ -1879,7 +2382,9 @@ async function uploadPrompt(inspected, flags) {
|
|
|
1879
2382
|
'',
|
|
1880
2383
|
JSON.stringify(eventPayload, null, 2),
|
|
1881
2384
|
'',
|
|
1882
|
-
|
|
2385
|
+
protectedPaidSkill
|
|
2386
|
+
? `Red Skill 的真实统计只能由宿主平台、安装器或本地私有配置执行安全 Ack 后产生。公开 connector 只保留 ${PRODUCT_NAME} Gateway 接入、创作者名片和安全使用边界。`
|
|
2387
|
+
: 'Red Skill 的真实统计只能由宿主平台、安装器或本地私有配置执行安全 Ack 后产生。公开 SKILL.md 只保留创作者名片和安全使用边界。'
|
|
1883
2388
|
]
|
|
1884
2389
|
: [
|
|
1885
2390
|
`发布成功后,请把公开链接和平台返回的信息回传给${PRODUCT_NAME} registry。只发送公开元数据和聚合事件,不发送用户内容、私密输入或账号凭据。`,
|
|
@@ -1935,7 +2440,7 @@ async function uploadPrompt(inspected, flags) {
|
|
|
1935
2440
|
'- attribution:发布、上传、归因链接点击、公开披露访问,只代表来源漏斗。',
|
|
1936
2441
|
'- reported:平台公开数字或手动导入,例如收藏、下载、安装、静态平台显示使用数。',
|
|
1937
2442
|
'- 如果无法确认 runtime callback 已经接在真实调用边界,就把该平台标记为 attribution 或 reported,不要伪造成 runtime。',
|
|
1938
|
-
|
|
2443
|
+
`- Agent ack:只有平台宿主、安装器或本地私有配置实际执行安全 Ack,才可以在安装/使用/完成/失败后计入 ${PRODUCT_NAME} 实时使用数据。`,
|
|
1939
2444
|
'',
|
|
1940
2445
|
...compatibilityLines,
|
|
1941
2446
|
'',
|
|
@@ -1945,10 +2450,12 @@ async function uploadPrompt(inspected, flags) {
|
|
|
1945
2450
|
? [
|
|
1946
2451
|
'Red 公开包边界:',
|
|
1947
2452
|
'',
|
|
1948
|
-
|
|
1949
|
-
|
|
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 或源码包' : ''}。`,
|
|
1950
2457
|
`- publicSkillId 和 Agent Ack endpoint 只能放在 ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md 或平台私有配置里;不要放进营销介绍、用户可见回复或截图说明。`,
|
|
1951
|
-
|
|
2458
|
+
`- 如果宿主 Agent 不能读取协议文件/私有配置并执行 Ack,Red 安装成功不会自动变成 ${PRODUCT_NAME} runtime 数据。`
|
|
1952
2459
|
]
|
|
1953
2460
|
: [
|
|
1954
2461
|
'Agent Ack 指令(只放在私有 runtime 配置、平台后台字段,或明确不会公开展示的安装说明中):',
|
|
@@ -1987,6 +2494,7 @@ async function writePrompt(root, flags) {
|
|
|
1987
2494
|
|
|
1988
2495
|
async function writeUnifiedHandoff(root, args) {
|
|
1989
2496
|
const relativeOutDir = path.relative(root, args.outDir) || WORK_DIR;
|
|
2497
|
+
const protectedPaidSkill = isProtectedPaidSkill(args.inspected);
|
|
1990
2498
|
const promptIndex = args.promptResults
|
|
1991
2499
|
.map((item) => `- ${item.hub}: ${path.relative(root, item.promptPath)}`)
|
|
1992
2500
|
.join('\n');
|
|
@@ -1995,7 +2503,7 @@ async function writeUnifiedHandoff(root, args) {
|
|
|
1995
2503
|
.join('\n');
|
|
1996
2504
|
const registry = args.inspected.registry || {};
|
|
1997
2505
|
const registryUrl = registry.registryUrl || DEFAULT_REGISTRY_URL;
|
|
1998
|
-
const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) ||
|
|
2506
|
+
const creatorCardUrl = normalizeText(registry.creatorCardUrl, 800) || `<creator card link from ${PRODUCT_NAME} dashboard>`;
|
|
1999
2507
|
const content = [
|
|
2000
2508
|
`# ${PRODUCT_NAME} Skill Upload Handoff`,
|
|
2001
2509
|
'',
|
|
@@ -2012,7 +2520,7 @@ async function writeUnifiedHandoff(root, args) {
|
|
|
2012
2520
|
'1. Identify the target platform from that official prompt, URL, command, or user message.',
|
|
2013
2521
|
`2. Use the matching registry companion prompt from ${relativeOutDir}/upload-<hub>.md only as an internal checklist.`,
|
|
2014
2522
|
'3. Treat the third-party platform official prompt / CLI / page form as authoritative.',
|
|
2015
|
-
`4. For
|
|
2523
|
+
`4. For ${PRODUCT_NAME} Registry, create or reuse the searchable listing. For Red Skill, use the official uploader.md / skillhub-upload flow.`,
|
|
2016
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.`,
|
|
2017
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.`,
|
|
2018
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.',
|
|
@@ -2022,10 +2530,15 @@ async function writeUnifiedHandoff(root, args) {
|
|
|
2022
2530
|
'',
|
|
2023
2531
|
'Red Skill exception:',
|
|
2024
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,
|
|
2025
2536
|
'- Red Skill should use the official uploader.md / skillhub-upload / web upload flow as the source of truth.',
|
|
2026
|
-
|
|
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.`,
|
|
2027
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.`,
|
|
2028
|
-
|
|
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.`,
|
|
2029
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.',
|
|
2030
2543
|
'',
|
|
2031
2544
|
'## Platform mapping',
|
|
@@ -2105,11 +2618,11 @@ async function writeUnifiedHandoff(root, args) {
|
|
|
2105
2618
|
'',
|
|
2106
2619
|
'If the hub cannot run callbacks, keep upload attribution and use campaign links or manual imports. Do not fake runtime events.',
|
|
2107
2620
|
'',
|
|
2108
|
-
|
|
2621
|
+
`## ${PRODUCT_NAME} Registry draft auth`,
|
|
2109
2622
|
'',
|
|
2110
|
-
`For
|
|
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.`,
|
|
2111
2624
|
'',
|
|
2112
|
-
|
|
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.`,
|
|
2113
2626
|
'',
|
|
2114
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.',
|
|
2115
2628
|
''
|
|
@@ -2400,7 +2913,7 @@ async function runDoctor(root, flags) {
|
|
|
2400
2913
|
hasManifest && registry.publicToken && registry.registryUrl
|
|
2401
2914
|
? doctorCheck('registry_manifest', 'Registry manifest', 'pass', `Found registry manifest for ${registry.provider || REGISTRY_PROVIDER}.`)
|
|
2402
2915
|
: redHub
|
|
2403
|
-
? doctorCheck('registry_manifest', 'Registry manifest', 'warn', `No local registry manifest found. This does not block Red Skill safety review, but
|
|
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.`)
|
|
2404
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>.`),
|
|
2405
2918
|
registryBlockPresent(skillMd)
|
|
2406
2919
|
? doctorCheck('registry_block', 'Registry block', 'pass', 'SKILL.md contains the explicit registry disclosure block.')
|
|
@@ -2415,7 +2928,7 @@ async function runDoctor(root, flags) {
|
|
|
2415
2928
|
? doctorCheck('skill_md_contract', 'SKILL.md runtime contract', 'pass', 'SKILL.md contains the current runtime contract, required footer, and update check endpoint.')
|
|
2416
2929
|
: redHub
|
|
2417
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.`)
|
|
2418
|
-
: 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
|
|
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.`),
|
|
2419
2932
|
runtimeCallbackPresent
|
|
2420
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.')
|
|
2421
2934
|
: redHub
|
|
@@ -2443,6 +2956,11 @@ async function runDoctor(root, flags) {
|
|
|
2443
2956
|
: redHub
|
|
2444
2957
|
? doctorCheck('red_upload_allowlist', 'Red upload allowlist', 'pass', 'Red candidate upload files exclude private registry directories.')
|
|
2445
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.'),
|
|
2446
2964
|
redHub && ['AGENT_ACK.md', 'REGISTRY_DISCLOSURE.md', 'runtime.yaml'].every((name) => packagePlan.candidateUploadFiles.includes(`${PUBLIC_PROTOCOL_DIR}/${name}`))
|
|
2447
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.`)
|
|
2448
2966
|
: redHub
|
|
@@ -2650,12 +3168,13 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
2650
3168
|
promptResults,
|
|
2651
3169
|
packagePlans
|
|
2652
3170
|
});
|
|
2653
|
-
const
|
|
3171
|
+
const firstPartyPackageHubSelected = hubs.includes(FIRST_PARTY_HUB);
|
|
3172
|
+
const firstPartyPackageArtifact = firstPartyPackageHubSelected && (REGISTRY_PROVIDER === 'xiashe' || REGISTRY_PROVIDER === 'agentpie')
|
|
2654
3173
|
? await uploadXiashePackageArtifact(absoluteRoot, flags, manifestResult)
|
|
2655
3174
|
: {
|
|
2656
3175
|
ok: true,
|
|
2657
3176
|
skipped: true,
|
|
2658
|
-
reason:
|
|
3177
|
+
reason: firstPartyPackageHubSelected ? 'unsupported_registry_provider' : 'first_party_registry_not_selected'
|
|
2659
3178
|
};
|
|
2660
3179
|
|
|
2661
3180
|
const warnings = [];
|
|
@@ -2691,14 +3210,14 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
2691
3210
|
agentAckPath,
|
|
2692
3211
|
disclosurePath,
|
|
2693
3212
|
snippetPath: snippetResult ? snippetResult.outPath : null,
|
|
2694
|
-
|
|
3213
|
+
firstPartyPackageArtifact,
|
|
2695
3214
|
promptEvents,
|
|
2696
3215
|
warnings,
|
|
2697
3216
|
publicProtocol,
|
|
2698
3217
|
next: [
|
|
2699
|
-
|
|
2700
|
-
?
|
|
2701
|
-
:
|
|
3218
|
+
firstPartyPackageArtifact?.skipped
|
|
3219
|
+
? `${PRODUCT_NAME} complete package upload was skipped (${firstPartyPackageArtifact.reason}).`
|
|
3220
|
+
: `${PRODUCT_NAME} complete package uploaded: ${path.relative(absoluteRoot, firstPartyPackageArtifact.artifactPath)} (${firstPartyPackageArtifact.publishStatus || 'queued'}).`,
|
|
2702
3221
|
`Use ${path.relative(absoluteRoot, handoffPath)} as the single Agent handoff. The Agent should not ask the user to manually pick registry files.`,
|
|
2703
3222
|
`Use ${path.relative(absoluteRoot, profilesPath)} and package-<hub>.json as the platform review profiles and upload allowlists.`,
|
|
2704
3223
|
publicProtocol
|
|
@@ -2748,6 +3267,23 @@ async function main() {
|
|
|
2748
3267
|
}
|
|
2749
3268
|
return;
|
|
2750
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
|
+
}
|
|
2751
3287
|
if (command === 'verify') {
|
|
2752
3288
|
const result = await verifyRegistry(root, flags);
|
|
2753
3289
|
print(flags.json ? result : formatVerify(result), flags.json);
|
|
@@ -2756,7 +3292,7 @@ async function main() {
|
|
|
2756
3292
|
if (command === 'publish') {
|
|
2757
3293
|
const result = await setupAgentWorkflow(root, flags);
|
|
2758
3294
|
if (flags.json) {
|
|
2759
|
-
print({ ok: true, mode:
|
|
3295
|
+
print({ ok: true, mode: `${REGISTRY_PROVIDER}-publish`, ...result }, true);
|
|
2760
3296
|
} else {
|
|
2761
3297
|
const lines = [
|
|
2762
3298
|
`${PRODUCT_NAME} publish handoff prepared.`,
|
|
@@ -2767,8 +3303,8 @@ async function main() {
|
|
|
2767
3303
|
...result.packagePlanPaths.map((item) => `Package plan (${item.hub}): ${item.planPath}`),
|
|
2768
3304
|
result.skillMdPath ? `Updated: ${result.skillMdPath}` : null,
|
|
2769
3305
|
result.publicProtocol ? `Public Red protocol: ${result.publicProtocol.runtimePath}, ${result.publicProtocol.agentAckPath}, ${result.publicProtocol.disclosurePath}` : null,
|
|
2770
|
-
result.
|
|
2771
|
-
?
|
|
3306
|
+
result.firstPartyPackageArtifact && !result.firstPartyPackageArtifact.skipped
|
|
3307
|
+
? `${PRODUCT_NAME} package: ${result.firstPartyPackageArtifact.artifactPath} (${result.firstPartyPackageArtifact.publishStatus || 'queued'})`
|
|
2772
3308
|
: null,
|
|
2773
3309
|
`Disclosure: ${result.disclosurePath}`,
|
|
2774
3310
|
result.snippetPath ? `Runtime snippet: ${result.snippetPath}` : null,
|
|
@@ -2776,8 +3312,8 @@ async function main() {
|
|
|
2776
3312
|
'',
|
|
2777
3313
|
'Next:',
|
|
2778
3314
|
'- Treat the target platform official upload flow as authoritative.',
|
|
2779
|
-
|
|
2780
|
-
|
|
3315
|
+
`- Use the handoff file as the only ${PRODUCT_NAME} registry checklist.`,
|
|
3316
|
+
`- ${PRODUCT_NAME} complete package upload is already handled by this command when the first-party registry target is selected.`,
|
|
2781
3317
|
'- Before uploading to third-party targets, check whether this Skill already has a draft, submitted review, or published listing for the selected target; skip duplicate uploads and verify/update the existing record when possible.',
|
|
2782
3318
|
'- Check upload readiness and runtime readiness separately before submission.',
|
|
2783
3319
|
'- Only ask for creator CLI login if a separate store-management command is needed and the registry public token is missing or rejected.',
|
|
@@ -2802,8 +3338,8 @@ async function main() {
|
|
|
2802
3338
|
result.publicProtocol ? `Wrote ${result.publicProtocol.runtimePath}` : null,
|
|
2803
3339
|
result.publicProtocol ? `Wrote ${result.publicProtocol.agentAckPath}` : null,
|
|
2804
3340
|
result.publicProtocol ? `Wrote ${result.publicProtocol.disclosurePath}` : null,
|
|
2805
|
-
result.
|
|
2806
|
-
? `Uploaded
|
|
3341
|
+
result.firstPartyPackageArtifact && !result.firstPartyPackageArtifact.skipped
|
|
3342
|
+
? `Uploaded ${PRODUCT_NAME} package ${result.firstPartyPackageArtifact.artifactPath} (${result.firstPartyPackageArtifact.publishStatus || 'queued'})`
|
|
2807
3343
|
: null,
|
|
2808
3344
|
`Wrote ${result.disclosurePath}`,
|
|
2809
3345
|
result.snippetPath ? `Wrote ${result.snippetPath}` : null,
|