@xiashe/skill 0.1.23 → 0.1.26
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 +6 -3
- package/bin/xiashe-skill.mjs +340 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,15 +44,18 @@ node packages/xiashe-skill-cli/bin/xiashe-skill.mjs snippet . --target js
|
|
|
44
44
|
node packages/xiashe-skill-cli/bin/xiashe-skill.mjs track . --event skill_invoked --hub red --dry-run --json
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
Use the `claimUrl` returned with a dashboard dynamic code exactly as issued. `publish` runs the same preflight automatically before it consumes a code: it verifies the exact registry origin, protocol version, and environment id exposed by that deployment. A dynamic code without an explicit `--claim-url` is rejected before the code is consumed, so development, CN, and global codes cannot accidentally be sent to the production registry. For
|
|
47
|
+
Use the `claimUrl` returned with a dashboard dynamic code exactly as issued. `publish` runs the same preflight automatically before it consumes a code: it verifies the exact registry origin, protocol version, and environment id exposed by that deployment. A dynamic code without an explicit `--claim-url` is rejected before the code is consumed, so development, CN, and global codes cannot accidentally be sent to the production registry. For an existing Skill update, use the single public `--registry-origin https://<registry-host>` option. The CLI derives its routes, compares that origin and the registry environment id with the local private manifest, and stops before writing or uploading when they differ. Do not copy individual internal endpoint overrides into an update task.
|
|
48
|
+
|
|
49
|
+
When an existing project was originally linked through XiaShe, `@agentpie/skill publish` may adopt its `.xiashe/xiashe.skill.json` only for an update without a dynamic code or explicit public token. The legacy manifest must identify itself as XiaShe and contain its saved registry origin and environment. The CLI verifies both, then writes a separate `.agentpie/agentpie.skill.json`; the XiaShe manifest is retained. A missing value or mismatch stops before a manifest write or package upload, so development, CN, and global Skill identities stay isolated.
|
|
48
50
|
|
|
49
51
|
## Reliable publish and recovery
|
|
50
52
|
|
|
51
53
|
The publish path keeps three different values separate:
|
|
52
54
|
|
|
53
55
|
- **Source fingerprint** — a stable SHA-256 of reviewed Skill source files. It is the idempotency key for the publish job.
|
|
54
|
-
- **Artifact checksum** — SHA-256 of the generated `.tgz` bytes
|
|
55
|
-
- **Storage checksum** —
|
|
56
|
+
- **Artifact checksum** — SHA-256 of the generated `.tgz` bytes, sent with the upload request.
|
|
57
|
+
- **Storage metadata checksum** — an upload-store diagnostic; it is never trusted as the sole proof of the uploaded bytes.
|
|
58
|
+
- **Verified upload checksum** — the backend rehashes the exact stored archive bytes and compares that value with the artifact checksum. None of these are compared with the source fingerprint.
|
|
56
59
|
|
|
57
60
|
If the network breaks after the server receives a dynamic-code claim, rerun the exact same `publish` command before that code expires. The registry accepts only the same code and source fingerprint, returns the original local manifest credentials, and reuses the same package job. It never creates a second Skill or a second upload job. Do not generate a new dashboard code merely because a local HTTP response was interrupted.
|
|
58
61
|
|
package/bin/xiashe-skill.mjs
CHANGED
|
@@ -68,7 +68,7 @@ function isTransportError(error) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
const LOCAL_ENV_DEFAULTS = loadLocalEnvDefaults();
|
|
71
|
-
const VERSION = process.env.XIASHE_SKILL_CLI_VERSION || '0.1.
|
|
71
|
+
const VERSION = process.env.XIASHE_SKILL_CLI_VERSION || '0.1.25';
|
|
72
72
|
const COMMAND_NAME = process.env.XIASHE_SKILL_CLI_NAME || 'xiashe-skill';
|
|
73
73
|
const PRODUCT_NAME = process.env.XIASHE_SKILL_PRODUCT_NAME || (COMMAND_NAME === 'agentpie-skill' ? 'AgentPie' : 'XiaShe');
|
|
74
74
|
const REGISTRY_PROVIDER = process.env.XIASHE_SKILL_REGISTRY_PROVIDER || (COMMAND_NAME === 'agentpie-skill' ? 'agentpie' : 'xiashe');
|
|
@@ -97,7 +97,16 @@ const MANIFEST_FILE = process.env.XIASHE_SKILL_MANIFEST_FILE || (COMMAND_NAME ==
|
|
|
97
97
|
? 'agentpie.skill.json'
|
|
98
98
|
: 'xiashe.skill.json');
|
|
99
99
|
const WORK_DIR = process.env.XIASHE_SKILL_WORK_DIR || (COMMAND_NAME === 'agentpie-skill' ? '.agentpie' : '.xiashe');
|
|
100
|
+
// A Skill that was first linked through XiaShe may later be updated through
|
|
101
|
+
// AgentPie. AgentPie can adopt that local link only for an update, never for a
|
|
102
|
+
// new claim. The registry origin and environment are still checked before we
|
|
103
|
+
// write the AgentPie manifest or upload anything.
|
|
104
|
+
const CAN_ADOPT_LEGACY_XIASHE_MANIFEST = REGISTRY_PROVIDER === 'agentpie' &&
|
|
105
|
+
process.env.XIASHE_SKILL_LEGACY_MANIFEST_COMPAT !== '0';
|
|
106
|
+
const LEGACY_XIASHE_WORK_DIR = '.xiashe';
|
|
107
|
+
const LEGACY_XIASHE_MANIFEST_FILE = 'xiashe.skill.json';
|
|
100
108
|
const PUBLIC_PROTOCOL_DIR = process.env.XIASHE_SKILL_PUBLIC_PROTOCOL_DIR || (REGISTRY_PROVIDER === 'agentpie' ? 'agentpie' : 'xiashe');
|
|
109
|
+
const PUBLIC_PACKAGE_MANIFEST_FILE = 'package-manifest.json';
|
|
101
110
|
const HANDOFF_FILE = 'UPLOAD_HANDOFF.md';
|
|
102
111
|
const DEFAULT_REGISTRY_URL = process.env.XIASHE_SKILL_REGISTRY_URL || `${DEFAULT_BASE_URL}/registry/skill-events`;
|
|
103
112
|
const DEFAULT_AGENT_ACK_URL = process.env.XIASHE_SKILL_AGENT_ACK_URL || `${DEFAULT_BASE_URL}/registry/agent-ack`;
|
|
@@ -163,8 +172,8 @@ const PLATFORM_REVIEW_PROFILES = {
|
|
|
163
172
|
disclosurePlacement: ['Skill public page', 'README', `${WORK_DIR}/REGISTRY_DISCLOSURE.md`],
|
|
164
173
|
runtimeDataPolicy: 'runtime_capable',
|
|
165
174
|
defaultDataSource: 'runtime',
|
|
166
|
-
allowedFilePolicy: 'Upload the complete reviewed Skill package
|
|
167
|
-
forbiddenPublicFiles: ['.env', 'credentials', 'node_modules', 'dist', 'build', 'browser profiles', 'SSH keys']
|
|
175
|
+
allowedFilePolicy: 'Upload the complete reviewed Skill package together with the generated no-secret package manifest. Keep the local registry manifest and runtime credentials on the creator device.',
|
|
176
|
+
forbiddenPublicFiles: [WORK_DIR, '.env', 'credentials', 'node_modules', 'dist', 'build', 'browser profiles', 'SSH keys', 'public tokens', 'signing secrets']
|
|
168
177
|
},
|
|
169
178
|
xiashe: {
|
|
170
179
|
label: 'XiaShe Store',
|
|
@@ -173,8 +182,8 @@ const PLATFORM_REVIEW_PROFILES = {
|
|
|
173
182
|
disclosurePlacement: ['Skill public page', 'README', `${WORK_DIR}/REGISTRY_DISCLOSURE.md`],
|
|
174
183
|
runtimeDataPolicy: 'runtime_capable',
|
|
175
184
|
defaultDataSource: 'runtime',
|
|
176
|
-
allowedFilePolicy: 'Upload the complete reviewed Skill package
|
|
177
|
-
forbiddenPublicFiles: ['.env', 'credentials', 'node_modules', 'dist', 'build', 'browser profiles', 'SSH keys']
|
|
185
|
+
allowedFilePolicy: 'Upload the complete reviewed Skill package together with the generated no-secret package manifest. Keep the local registry manifest and runtime credentials on the creator device.',
|
|
186
|
+
forbiddenPublicFiles: [WORK_DIR, '.env', 'credentials', 'node_modules', 'dist', 'build', 'browser profiles', 'SSH keys', 'public tokens', 'signing secrets']
|
|
178
187
|
},
|
|
179
188
|
red: {
|
|
180
189
|
label: 'Red Skill',
|
|
@@ -304,6 +313,12 @@ const DEFAULT_IGNORE = new Set([
|
|
|
304
313
|
const MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
305
314
|
const MAX_SOURCE_BYTES = 30 * 1024 * 1024;
|
|
306
315
|
const MAX_XIASHE_PACKAGE_BYTES = 100 * 1024 * 1024;
|
|
316
|
+
// Update tasks are frequently run from a chat workspace that contains the
|
|
317
|
+
// original Skill folder one level below the current directory. Look only in a
|
|
318
|
+
// small, project-local area: this is helpful without turning a publish command
|
|
319
|
+
// into a machine-wide search for credentials or manifests.
|
|
320
|
+
const MAX_LOCAL_LINK_DISCOVERY_DEPTH = 4;
|
|
321
|
+
const MAX_LOCAL_LINK_DISCOVERY_DIRECTORIES = 160;
|
|
307
322
|
const gzipAsync = promisify(gzip);
|
|
308
323
|
|
|
309
324
|
function usage() {
|
|
@@ -315,7 +330,7 @@ Usage:
|
|
|
315
330
|
${COMMAND_NAME} setup [skill-dir] --code <dynamic-code> [--hub all|${FIRST_PARTY_HUB}|red]
|
|
316
331
|
${COMMAND_NAME} setup [skill-dir] --public-token <token> --skill-id <id> [--hub all|${FIRST_PARTY_HUB}|red]
|
|
317
332
|
${COMMAND_NAME} doctor [skill-dir] [--json]
|
|
318
|
-
${COMMAND_NAME} preflight --claim-url <url> [--json]
|
|
333
|
+
${COMMAND_NAME} preflight (--claim-url <url> | --registry-origin <url>) [--json]
|
|
319
334
|
${COMMAND_NAME} verify [skill-dir] [--hub <hub>] [--scenario <label>] [--dry-run]
|
|
320
335
|
${COMMAND_NAME} attach [skill-dir] --code <dynamic-code> [--name <name>]
|
|
321
336
|
${COMMAND_NAME} attach [skill-dir] --public-token <token> [--skill-id <id>] [--name <name>]
|
|
@@ -326,6 +341,7 @@ Usage:
|
|
|
326
341
|
Options:
|
|
327
342
|
--public-token <token> Public write token issued by ${PRODUCT_NAME} registry.
|
|
328
343
|
--code <dynamic-code> One-time ${PRODUCT_NAME} dashboard code used to claim registry identity.
|
|
344
|
+
--registry-origin <url> Existing Skill update origin. Derives all registry routes and verifies the saved environment.
|
|
329
345
|
--claim-url <url> Code claim endpoint. Defaults to XIASHE_SKILL_CLAIM_URL or ${DEFAULT_CLAIM_URL}
|
|
330
346
|
--registry-doctor-url <url> Registry preflight endpoint. Defaults to the claim URL origin.
|
|
331
347
|
--skill-id <id> Public ${PRODUCT_NAME} Skill registry id.
|
|
@@ -524,6 +540,10 @@ function latestVersionEndpoint(inspectedOrRegistry = {}) {
|
|
|
524
540
|
const skillKey = safeSkillKey(inspectedOrRegistry.skillKey || registry.skillKey || '');
|
|
525
541
|
const creatorCardUrl = normalizeText(registry.creatorCardUrl || registry.creatorProfile?.cardUrl, 800);
|
|
526
542
|
const publicSkillId = normalizeText(registry.skillId || registry.publicSkillId, 160);
|
|
543
|
+
// Existing Skills must keep checking the registry that originally issued
|
|
544
|
+
// their manifest. Falling back to a CLI default here would make a dev
|
|
545
|
+
// package discover Global/CN updates after a machine-level config change.
|
|
546
|
+
const registryOrigin = normalizeBaseUrl(registry.registryOrigin) || DEFAULT_BASE_URL;
|
|
527
547
|
const params = new URLSearchParams();
|
|
528
548
|
if (publicSkillId) {
|
|
529
549
|
params.set('publicSkillId', publicSkillId);
|
|
@@ -531,7 +551,7 @@ function latestVersionEndpoint(inspectedOrRegistry = {}) {
|
|
|
531
551
|
params.set('skillKey', skillKey);
|
|
532
552
|
params.set('creatorCardUrl', creatorCardUrl);
|
|
533
553
|
}
|
|
534
|
-
return `${
|
|
554
|
+
return `${registryOrigin}/registry/skill/latest${params.toString() ? `?${params.toString()}` : ''}`;
|
|
535
555
|
}
|
|
536
556
|
|
|
537
557
|
function creatorFooterTemplate(inspected, language = 'zh') {
|
|
@@ -807,12 +827,51 @@ async function readJsonFile(filePath) {
|
|
|
807
827
|
}
|
|
808
828
|
}
|
|
809
829
|
|
|
830
|
+
function localManifestPath(root) {
|
|
831
|
+
return path.join(root, WORK_DIR, MANIFEST_FILE);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function legacyXiaSheManifestPath(root) {
|
|
835
|
+
if (!CAN_ADOPT_LEGACY_XIASHE_MANIFEST) return null;
|
|
836
|
+
return path.join(root, LEGACY_XIASHE_WORK_DIR, LEGACY_XIASHE_MANIFEST_FILE);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function canAdoptLegacyXiaSheManifest(flags = {}) {
|
|
840
|
+
// A dynamic code or a supplied public token starts/resumes an explicit
|
|
841
|
+
// identity flow. Never let it silently attach to a legacy local identity.
|
|
842
|
+
return CAN_ADOPT_LEGACY_XIASHE_MANIFEST &&
|
|
843
|
+
!normalizeText(flags.code, 80) &&
|
|
844
|
+
!normalizeText(flags['public-token'], 512) &&
|
|
845
|
+
(!flags['manifest-path'] || Boolean(flags['local-manifest']));
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
async function readSkillManifest(root, flags = {}) {
|
|
849
|
+
const explicitManifestPath = normalizeText(flags['manifest-path'], 2_000);
|
|
850
|
+
const legacyManifestPath = canAdoptLegacyXiaSheManifest(flags)
|
|
851
|
+
? legacyXiaSheManifestPath(root)
|
|
852
|
+
: null;
|
|
853
|
+
const candidates = [
|
|
854
|
+
explicitManifestPath ? path.resolve(explicitManifestPath) : path.join(root, MANIFEST_FILE),
|
|
855
|
+
localManifestPath(root),
|
|
856
|
+
legacyManifestPath
|
|
857
|
+
].filter(Boolean);
|
|
858
|
+
const uniqueCandidates = [...new Set(candidates)];
|
|
859
|
+
|
|
860
|
+
for (const manifestPath of uniqueCandidates) {
|
|
861
|
+
const manifest = await readJsonFile(manifestPath);
|
|
862
|
+
if (!manifest) continue;
|
|
863
|
+
return {
|
|
864
|
+
manifest,
|
|
865
|
+
manifestPath,
|
|
866
|
+
source: manifestPath === legacyManifestPath ? 'legacy_xiashe' : 'current'
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
return { manifest: null, manifestPath: null, source: null };
|
|
870
|
+
}
|
|
871
|
+
|
|
810
872
|
async function inferSkillMetadata(root, flags = {}) {
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
: path.join(root, MANIFEST_FILE);
|
|
814
|
-
const localManifestPath = path.join(root, WORK_DIR, MANIFEST_FILE);
|
|
815
|
-
const manifest = await readJsonFile(manifestPath) || await readJsonFile(localManifestPath);
|
|
873
|
+
const manifestLookup = await readSkillManifest(root, flags);
|
|
874
|
+
const manifest = manifestLookup.manifest;
|
|
816
875
|
const packageJson = await readJsonFile(path.join(root, 'package.json'));
|
|
817
876
|
const skillMdPath = path.join(root, 'SKILL.md');
|
|
818
877
|
const readmePath = path.join(root, 'README.md');
|
|
@@ -834,10 +893,113 @@ async function inferSkillMetadata(root, flags = {}) {
|
|
|
834
893
|
billingModel: normalizeText(flags['billing-model'] || manifest?.billingModel || manifest?.registry?.billingModel, 80) || null,
|
|
835
894
|
monetizationMode: normalizeText(flags['monetization-mode'] || manifest?.monetizationMode || manifest?.registry?.monetizationMode, 80) || null,
|
|
836
895
|
accessMode: normalizeText(flags['access-mode'] || manifest?.accessMode || manifest?.registry?.accessMode, 80) || null,
|
|
896
|
+
manifestPath: manifestLookup.manifestPath,
|
|
897
|
+
manifestSource: manifestLookup.source,
|
|
837
898
|
registry: manifest?.registry || null
|
|
838
899
|
};
|
|
839
900
|
}
|
|
840
901
|
|
|
902
|
+
function hasSkillProjectMarker(root) {
|
|
903
|
+
return ['SKILL.md', 'package.json', 'README.md'].some((fileName) => existsSync(path.join(root, fileName)));
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
async function readExistingSkillLink(root, flags = {}) {
|
|
907
|
+
const manifestLookup = await readSkillManifest(root, flags);
|
|
908
|
+
const manifest = manifestLookup.manifest;
|
|
909
|
+
const registry = manifest?.registry;
|
|
910
|
+
const publicToken = normalizeText(registry?.publicToken, 512);
|
|
911
|
+
const publicSkillId = normalizeText(registry?.publicSkillId || registry?.skillId, 160);
|
|
912
|
+
if (!manifest || !registry || (!publicToken && !publicSkillId)) return null;
|
|
913
|
+
return {
|
|
914
|
+
root,
|
|
915
|
+
manifestPath: manifestLookup.manifestPath,
|
|
916
|
+
manifestSource: manifestLookup.source,
|
|
917
|
+
manifest,
|
|
918
|
+
skillKey: safeSkillKey(manifest.skillKey || registry.skillKey || manifest.name || path.basename(root))
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
async function resolveExistingSkillRoot(root, flags = {}) {
|
|
923
|
+
const requestedRoot = path.resolve(root || '.');
|
|
924
|
+
const info = await stat(requestedRoot).catch(() => null);
|
|
925
|
+
if (!info?.isDirectory()) {
|
|
926
|
+
throw new Error(`Skill directory not found: ${requestedRoot}`);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// A fresh claim or an explicit manifest path is intentional. Do not replace
|
|
930
|
+
// either with automatic discovery, which could attach the wrong Skill.
|
|
931
|
+
if (flags.code || flags['public-token'] || flags['manifest-path']) {
|
|
932
|
+
return { root: requestedRoot, discovered: false };
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const directLink = await readExistingSkillLink(requestedRoot, flags);
|
|
936
|
+
if (directLink) return { root: requestedRoot, discovered: false };
|
|
937
|
+
|
|
938
|
+
const requestedRootHasMarker = hasSkillProjectMarker(requestedRoot);
|
|
939
|
+
const expectedSkillKey = normalizeText(flags['skill-key'], 120)
|
|
940
|
+
? safeSkillKey(flags['skill-key'])
|
|
941
|
+
: requestedRootHasMarker
|
|
942
|
+
? (await inferSkillMetadata(requestedRoot, flags)).skillKey
|
|
943
|
+
: '';
|
|
944
|
+
const candidates = [];
|
|
945
|
+
const candidateRoots = new Set();
|
|
946
|
+
|
|
947
|
+
const addCandidate = async (candidateRoot) => {
|
|
948
|
+
const canonicalRoot = path.resolve(candidateRoot);
|
|
949
|
+
if (candidateRoots.has(canonicalRoot) || !hasSkillProjectMarker(canonicalRoot)) return;
|
|
950
|
+
candidateRoots.add(canonicalRoot);
|
|
951
|
+
const candidate = await readExistingSkillLink(canonicalRoot, flags);
|
|
952
|
+
if (!candidate) return;
|
|
953
|
+
if (expectedSkillKey && candidate.skillKey !== expectedSkillKey) return;
|
|
954
|
+
candidates.push(candidate);
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
// Only walk upward when the requested folder identifies the Skill. Without
|
|
958
|
+
// that key, an unrelated parent workspace with one linked Skill could be
|
|
959
|
+
// selected accidentally. A generic chat workspace is handled by the bounded
|
|
960
|
+
// child search below instead.
|
|
961
|
+
if (expectedSkillKey) {
|
|
962
|
+
let ancestor = path.dirname(requestedRoot);
|
|
963
|
+
for (let index = 0; index < MAX_LOCAL_LINK_DISCOVERY_DEPTH; index += 1) {
|
|
964
|
+
if (ancestor === path.dirname(ancestor)) break;
|
|
965
|
+
await addCandidate(ancestor);
|
|
966
|
+
ancestor = path.dirname(ancestor);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// Then look through a small subtree, covering the common case where a chat
|
|
971
|
+
// task attaches the original Skill directory as a child of its workspace.
|
|
972
|
+
const queue = [{ directory: requestedRoot, depth: 0 }];
|
|
973
|
+
const visited = new Set();
|
|
974
|
+
while (queue.length > 0 && visited.size < MAX_LOCAL_LINK_DISCOVERY_DIRECTORIES) {
|
|
975
|
+
const { directory, depth } = queue.shift();
|
|
976
|
+
const canonicalDirectory = path.resolve(directory);
|
|
977
|
+
if (visited.has(canonicalDirectory)) continue;
|
|
978
|
+
visited.add(canonicalDirectory);
|
|
979
|
+
if (depth > 0) await addCandidate(canonicalDirectory);
|
|
980
|
+
if (depth >= MAX_LOCAL_LINK_DISCOVERY_DEPTH) continue;
|
|
981
|
+
const entries = await readdir(canonicalDirectory, { withFileTypes: true }).catch(() => []);
|
|
982
|
+
for (const entry of entries) {
|
|
983
|
+
if (!entry.isDirectory() || shouldIgnore(entry.name)) continue;
|
|
984
|
+
queue.push({ directory: path.join(canonicalDirectory, entry.name), depth: depth + 1 });
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
if (candidates.length === 1) {
|
|
989
|
+
return { root: candidates[0].root, discovered: true };
|
|
990
|
+
}
|
|
991
|
+
if (candidates.length > 1) {
|
|
992
|
+
throw new Error(
|
|
993
|
+
'CREATOR_SKILL_MANIFEST_AMBIGUOUS: more than one matching local Skill connection was found. ' +
|
|
994
|
+
'The CLI did not update or create any Skill. Run the command from the intended Skill folder.'
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
throw new Error(
|
|
998
|
+
'CREATOR_SKILL_MANIFEST_NOT_FOUND: no matching local Skill connection was found in this project. ' +
|
|
999
|
+
'The CLI did not update or create any Skill. Run the command from the original Skill folder.'
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
841
1003
|
function shouldIgnore(name) {
|
|
842
1004
|
return DEFAULT_IGNORE.has(name) ||
|
|
843
1005
|
name.startsWith('.env.') ||
|
|
@@ -968,6 +1130,49 @@ function claimUrlForExistingManifest(flags = {}, registry = {}) {
|
|
|
968
1130
|
);
|
|
969
1131
|
}
|
|
970
1132
|
|
|
1133
|
+
function normalizeRegistryOrigin(value) {
|
|
1134
|
+
const raw = normalizeText(value, 1_000);
|
|
1135
|
+
if (!raw) return '';
|
|
1136
|
+
let url;
|
|
1137
|
+
try {
|
|
1138
|
+
url = new URL(raw);
|
|
1139
|
+
} catch {
|
|
1140
|
+
throw new Error('CREATOR_SKILL_REGISTRY_ORIGIN_INVALID: registry origin is not a valid URL.');
|
|
1141
|
+
}
|
|
1142
|
+
const host = url.hostname.toLowerCase();
|
|
1143
|
+
const isLocalhost = host === 'localhost' || host === '127.0.0.1' || host === '::1' || host.endsWith('.localhost');
|
|
1144
|
+
if (url.pathname !== '/' || url.search || url.hash || url.username || url.password) {
|
|
1145
|
+
throw new Error('CREATOR_SKILL_REGISTRY_ORIGIN_INVALID: registry origin must not contain a path, query, fragment, or credentials.');
|
|
1146
|
+
}
|
|
1147
|
+
if (url.protocol !== 'https:' && !(isLocalhost && process.env.XIASHE_SKILL_ALLOW_INSECURE_LOCALHOST === '1')) {
|
|
1148
|
+
throw new Error('CREATOR_SKILL_REGISTRY_ORIGIN_INSECURE: only HTTPS registry origins are accepted.');
|
|
1149
|
+
}
|
|
1150
|
+
return url.origin.replace(/\/+$/, '');
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
function registryOriginFromFlags(flags = {}) {
|
|
1154
|
+
return normalizeRegistryOrigin(flags['registry-origin']);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function applyRegistryOriginFlags(flags = {}) {
|
|
1158
|
+
const registryOrigin = registryOriginFromFlags(flags);
|
|
1159
|
+
if (!registryOrigin) return flags;
|
|
1160
|
+
if (normalizeText(flags.code, 80)) {
|
|
1161
|
+
throw new Error('CREATOR_SKILL_REGISTRY_ORIGIN_WITH_CODE_FORBIDDEN: dynamic codes must use the exact dashboard --claim-url, not --registry-origin.');
|
|
1162
|
+
}
|
|
1163
|
+
for (const key of ['claim-url', 'registry-url', 'agent-ack-url', 'registry-doctor-url']) {
|
|
1164
|
+
if (normalizeText(flags[key], 1_000)) {
|
|
1165
|
+
throw new Error(`CREATOR_SKILL_REGISTRY_ORIGIN_CONFLICT: --registry-origin cannot be combined with --${key}.`);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
return {
|
|
1169
|
+
...flags,
|
|
1170
|
+
'claim-url': `${registryOrigin}/registry/skill/claim`,
|
|
1171
|
+
'registry-url': `${registryOrigin}/registry/skill-events`,
|
|
1172
|
+
'agent-ack-url': `${registryOrigin}/registry/agent-ack`
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
|
|
971
1176
|
function configuredRegistryOrigins() {
|
|
972
1177
|
const supplied = [
|
|
973
1178
|
process.env.XIASHE_SKILL_TRUSTED_REGISTRY_ORIGINS,
|
|
@@ -1090,11 +1295,16 @@ function shouldIgnorePackageEntry(relativePath, entryName, isDirectory = false)
|
|
|
1090
1295
|
if (entryName === '.env' || entryName.startsWith('.env.')) return true;
|
|
1091
1296
|
if (/\.(pem|key)$/i.test(entryName)) return true;
|
|
1092
1297
|
if (packageArtifactPrefix && normalized.startsWith(packageArtifactPrefix)) return true;
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1298
|
+
// The local registry workspace carries the public token, signing key and
|
|
1299
|
+
// resumable-upload state. It is intentionally never part of an installable
|
|
1300
|
+
// archive, even for first-party distribution. A separate, no-secret
|
|
1301
|
+
// package manifest is written below `PUBLIC_PROTOCOL_DIR` instead.
|
|
1302
|
+
if (normalized === WORK_DIR || normalized.startsWith(localRegistryPrefix)) {
|
|
1096
1303
|
return true;
|
|
1097
1304
|
}
|
|
1305
|
+
if (normalized === PUBLIC_PROTOCOL_DIR || normalized.startsWith(`${PUBLIC_PROTOCOL_DIR}/`)) {
|
|
1306
|
+
return isDirectory ? false : !isXiashePackageRegistryFile(normalized);
|
|
1307
|
+
}
|
|
1098
1308
|
const otherRegistryDir = WORK_DIR === '.xiashe' ? '.agentpie' : '.xiashe';
|
|
1099
1309
|
if (normalized === otherRegistryDir || normalized.startsWith(`${otherRegistryDir}/`)) return true;
|
|
1100
1310
|
return false;
|
|
@@ -1103,17 +1313,45 @@ function shouldIgnorePackageEntry(relativePath, entryName, isDirectory = false)
|
|
|
1103
1313
|
function isXiashePackageRegistryFile(relativePath) {
|
|
1104
1314
|
const normalized = String(relativePath || '').replace(/\\/g, '/');
|
|
1105
1315
|
const allowed = new Set([
|
|
1106
|
-
`${WORK_DIR}/${MANIFEST_FILE}`,
|
|
1107
|
-
`${WORK_DIR}/AGENT_ACK.md`,
|
|
1108
|
-
`${WORK_DIR}/REGISTRY_DISCLOSURE.md`,
|
|
1109
|
-
`${WORK_DIR}/runtime-events.js`,
|
|
1110
1316
|
`${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md`,
|
|
1111
1317
|
`${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md`,
|
|
1112
|
-
`${PUBLIC_PROTOCOL_DIR}/runtime.yaml
|
|
1318
|
+
`${PUBLIC_PROTOCOL_DIR}/runtime.yaml`,
|
|
1319
|
+
`${PUBLIC_PROTOCOL_DIR}/${PUBLIC_PACKAGE_MANIFEST_FILE}`
|
|
1113
1320
|
]);
|
|
1114
1321
|
return allowed.has(normalized);
|
|
1115
1322
|
}
|
|
1116
1323
|
|
|
1324
|
+
function publicPackageManifestRelativePath() {
|
|
1325
|
+
return `${PUBLIC_PROTOCOL_DIR}/${PUBLIC_PACKAGE_MANIFEST_FILE}`;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
async function writePublicPackageManifest(root, inspected) {
|
|
1329
|
+
const registry = inspected.registry || {};
|
|
1330
|
+
const outputPath = path.join(root, PUBLIC_PROTOCOL_DIR, PUBLIC_PACKAGE_MANIFEST_FILE);
|
|
1331
|
+
const manifest = {
|
|
1332
|
+
schemaVersion: `${REGISTRY_PROVIDER}.skill.package.v1`,
|
|
1333
|
+
skill: {
|
|
1334
|
+
name: inspected.name,
|
|
1335
|
+
skillKey: inspected.skillKey,
|
|
1336
|
+
description: inspected.description || null,
|
|
1337
|
+
version: inspected.version || null,
|
|
1338
|
+
isPaid: Boolean(inspected.isPaid),
|
|
1339
|
+
requiresEntitlement: Boolean(inspected.requiresEntitlement)
|
|
1340
|
+
},
|
|
1341
|
+
creator: {
|
|
1342
|
+
cardUrl: normalizeText(registry.creatorCardUrl || registry.creatorProfile?.cardUrl, 800) || null
|
|
1343
|
+
},
|
|
1344
|
+
safety: {
|
|
1345
|
+
localRegistryStateIncluded: false,
|
|
1346
|
+
credentialsIncluded: false,
|
|
1347
|
+
signingMaterialIncluded: false
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
1351
|
+
await writeFile(outputPath, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o644 });
|
|
1352
|
+
return { outputPath, manifest };
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1117
1355
|
async function walkXiashePackageFiles(root, current = root, out = []) {
|
|
1118
1356
|
const entries = await readdir(current, { withFileTypes: true });
|
|
1119
1357
|
for (const entry of entries) {
|
|
@@ -1218,8 +1456,9 @@ async function createTarGz(files) {
|
|
|
1218
1456
|
|
|
1219
1457
|
async function buildXiashePackageArtifact(root, inspected, flags = {}) {
|
|
1220
1458
|
const absoluteRoot = path.resolve(root || '.');
|
|
1459
|
+
await writePublicPackageManifest(absoluteRoot, inspected);
|
|
1221
1460
|
const files = await walkXiashePackageFiles(absoluteRoot);
|
|
1222
|
-
const manifestRelative =
|
|
1461
|
+
const manifestRelative = publicPackageManifestRelativePath();
|
|
1223
1462
|
const skillMarkdownPresent = files.some((file) => /^SKILL(\.[a-z0-9]+)?$/i.test(file.relative));
|
|
1224
1463
|
const manifestPresent = files.some((file) => file.relative === manifestRelative);
|
|
1225
1464
|
if (!skillMarkdownPresent) {
|
|
@@ -1474,15 +1713,42 @@ async function writeManifest(root, flags) {
|
|
|
1474
1713
|
const inspected = await inspectSkill(root, flags);
|
|
1475
1714
|
const code = normalizeText(flags.code, 80);
|
|
1476
1715
|
const existingRegistry = inspected.registry || {};
|
|
1716
|
+
const savedRegistryOrigin = normalizeBaseUrl(existingRegistry.registryOrigin);
|
|
1717
|
+
const savedEnvironmentId = normalizeText(existingRegistry.environmentId, 160);
|
|
1718
|
+
const requestedRegistryOrigin = registryOriginFromFlags(flags);
|
|
1477
1719
|
const suppliedPublicToken = normalizeText(flags['public-token'], 512);
|
|
1720
|
+
const isLegacyXiaSheMigration = inspected.manifestSource === 'legacy_xiashe';
|
|
1721
|
+
if (
|
|
1722
|
+
isLegacyXiaSheMigration &&
|
|
1723
|
+
(existingRegistry.provider !== 'xiashe' || !savedRegistryOrigin || !savedEnvironmentId)
|
|
1724
|
+
) {
|
|
1725
|
+
throw new Error(
|
|
1726
|
+
'CREATOR_SKILL_LEGACY_MANIFEST_UNVERIFIABLE: this XiaShe local connection does not contain a verifiable registry origin and environment. ' +
|
|
1727
|
+
'No AgentPie manifest was written. Update from the original XiaShe connection or attach the Skill explicitly.'
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1478
1730
|
if (!code && suppliedPublicToken && !explicitClaimUrl(flags) && !claimUrlFromRegistryOrigin(existingRegistry.registryOrigin)) {
|
|
1479
1731
|
throw new Error(
|
|
1480
1732
|
'CREATOR_SKILL_CLAIM_URL_REQUIRED: a new manifest created from --public-token must include --claim-url. ' +
|
|
1481
1733
|
'Existing manifests resume through their saved registry origin.'
|
|
1482
1734
|
);
|
|
1483
1735
|
}
|
|
1736
|
+
if (!code && !savedRegistryOrigin && !requestedRegistryOrigin) {
|
|
1737
|
+
throw new Error(
|
|
1738
|
+
'CREATOR_SKILL_REGISTRY_ORIGIN_REQUIRED: existing Skill updates must include --registry-origin so the CLI can verify the original environment before writing or uploading.'
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
if (!code && savedRegistryOrigin && requestedRegistryOrigin && savedRegistryOrigin !== requestedRegistryOrigin) {
|
|
1742
|
+
throw new Error('CREATOR_SKILL_REGISTRY_ENVIRONMENT_MISMATCH: the supplied registry origin differs from the original Skill manifest. No files were changed.');
|
|
1743
|
+
}
|
|
1484
1744
|
const claimUrl = code ? claimUrlForCode(flags) : claimUrlForExistingManifest(flags, existingRegistry);
|
|
1485
|
-
const registryPreflight =
|
|
1745
|
+
const registryPreflight = await preflightRegistryClaim(claimUrl, flags);
|
|
1746
|
+
if (!code && savedRegistryOrigin && savedRegistryOrigin !== registryPreflight.registryOrigin) {
|
|
1747
|
+
throw new Error('CREATOR_SKILL_REGISTRY_ENVIRONMENT_MISMATCH: the saved Skill manifest belongs to a different registry origin. No files were changed.');
|
|
1748
|
+
}
|
|
1749
|
+
if (!code && savedEnvironmentId && savedEnvironmentId !== registryPreflight.environmentId) {
|
|
1750
|
+
throw new Error('CREATOR_SKILL_REGISTRY_ENVIRONMENT_MISMATCH: the saved Skill manifest belongs to a different registry environment. No files were changed.');
|
|
1751
|
+
}
|
|
1486
1752
|
const claim = code
|
|
1487
1753
|
? await postJson(
|
|
1488
1754
|
registryPreflight.claimUrl,
|
|
@@ -1548,9 +1814,9 @@ async function writeManifest(root, flags) {
|
|
|
1548
1814
|
accessMode: normalizeText(flags['access-mode'] || inspected.accessMode || existingRegistry.accessMode, 80) || null,
|
|
1549
1815
|
registry: {
|
|
1550
1816
|
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,
|
|
1817
|
+
protocolVersion: normalizeText(claim?.registry?.protocolVersion || registryPreflight?.protocolVersion || existingRegistry.protocolVersion, 120) || null,
|
|
1818
|
+
environmentId: normalizeText(claim?.registry?.environmentId || registryPreflight?.environmentId || existingRegistry.environmentId, 160) || null,
|
|
1819
|
+
registryOrigin: normalizeBaseUrl(claim?.registry?.registryOrigin || registryPreflight?.registryOrigin || existingRegistry.registryOrigin) || null,
|
|
1554
1820
|
skillId: normalizeText(flags['skill-id'] || claim?.skillId || existingRegistry.skillId, 160) || null,
|
|
1555
1821
|
publicToken,
|
|
1556
1822
|
isPaid: Boolean(flags['paid-skill'] || flags['protected-skill'] || inspected.isPaid || existingRegistry.isPaid || existingRegistry.paidAccess),
|
|
@@ -1558,8 +1824,8 @@ async function writeManifest(root, flags) {
|
|
|
1558
1824
|
billingModel: normalizeText(flags['billing-model'] || inspected.billingModel || existingRegistry.billingModel, 80) || null,
|
|
1559
1825
|
monetizationMode: normalizeText(flags['monetization-mode'] || inspected.monetizationMode || existingRegistry.monetizationMode, 80) || null,
|
|
1560
1826
|
accessMode: normalizeText(flags['access-mode'] || inspected.accessMode || existingRegistry.accessMode, 80) || null,
|
|
1561
|
-
registryUrl: normalizeText(flags['registry-url'] || claim?.registryUrl || existingRegistry.registryUrl, 800) ||
|
|
1562
|
-
agentAckUrl: normalizeText(flags['agent-ack-url'] || existingRegistry.agentAckUrl, 800) ||
|
|
1827
|
+
registryUrl: normalizeText(flags['registry-url'] || claim?.registryUrl || existingRegistry.registryUrl, 800) || `${registryPreflight.registryOrigin}/registry/skill-events`,
|
|
1828
|
+
agentAckUrl: normalizeText(flags['agent-ack-url'] || existingRegistry.agentAckUrl, 800) || `${registryPreflight.registryOrigin}/registry/agent-ack`,
|
|
1563
1829
|
creatorCardUrl,
|
|
1564
1830
|
creatorProfile,
|
|
1565
1831
|
eventSchemaVersion: EVENT_SCHEMA_VERSION,
|
|
@@ -1607,7 +1873,7 @@ async function writeManifest(root, flags) {
|
|
|
1607
1873
|
sourceSurface: hub,
|
|
1608
1874
|
skillKey: safeSkillKey(claim?.skillKey || inspected.skillKey),
|
|
1609
1875
|
publicSkillId: normalizeText(flags['skill-id'] || claim?.skillId || existingRegistry.skillId, 160) || null,
|
|
1610
|
-
agentAckUrl: normalizeText(flags['agent-ack-url'] || existingRegistry.agentAckUrl, 800) ||
|
|
1876
|
+
agentAckUrl: normalizeText(flags['agent-ack-url'] || existingRegistry.agentAckUrl, 800) || `${registryPreflight.registryOrigin}/registry/agent-ack`,
|
|
1611
1877
|
creatorCardUrl,
|
|
1612
1878
|
creatorProfile
|
|
1613
1879
|
}
|
|
@@ -1632,6 +1898,7 @@ async function writeManifest(root, flags) {
|
|
|
1632
1898
|
ok: true,
|
|
1633
1899
|
manifestPath,
|
|
1634
1900
|
manifest,
|
|
1901
|
+
adoptedLegacyXiaSheManifest: inspected.manifestSource === 'legacy_xiashe',
|
|
1635
1902
|
claimPackageArtifactUpload: claim?.packageArtifactUpload ?? null,
|
|
1636
1903
|
registryPreflight
|
|
1637
1904
|
};
|
|
@@ -1639,10 +1906,10 @@ async function writeManifest(root, flags) {
|
|
|
1639
1906
|
|
|
1640
1907
|
function hubInstruction(hub) {
|
|
1641
1908
|
if (hub === 'agentpie') {
|
|
1642
|
-
return 'AgentPie Registry publish is the full Skill package
|
|
1909
|
+
return 'AgentPie Registry publish is the full Skill package flow. The local registry manifest stays on the creator device; the installable package contains only the generated no-secret package manifest. Keep public disclosure, attribution, and runtime analytics callbacks; if a creator-authenticated CLI/API session exists, create or reuse the AgentPie registry draft.';
|
|
1643
1910
|
}
|
|
1644
1911
|
if (hub === 'xiashe') {
|
|
1645
|
-
return '虾舍商店发布是完整 Skill
|
|
1912
|
+
return '虾舍商店发布是完整 Skill 包的主流程。本地 registry manifest 仅保留在创作者设备;安装包只包含自动生成的不含敏感信息的 package manifest。请保留公开披露、归因和 runtime analytics 回传能力;如果已有创作者认证 CLI/API 会话,就创建或复用虾舍商店草稿。';
|
|
1646
1913
|
}
|
|
1647
1914
|
if (hub === 'red') {
|
|
1648
1915
|
return `Red Skill 的官方 uploader.md / skillhub-upload / 页面流程是主流程。请优先使用本机 skillhub-upload;${PRODUCT_NAME} 负责生成平台安全披露、connector/运行时协议和本地 registry handoff,并把 registry id、public token、endpoint 等后台字段保留在本地私有配置或平台私有字段里。`;
|
|
@@ -1716,16 +1983,16 @@ function uploadCompatibilityLines(hub, manifestFile, protectedPaidSkill = false)
|
|
|
1716
1983
|
if (hub === 'agentpie') {
|
|
1717
1984
|
return [
|
|
1718
1985
|
'AgentPie Registry compatibility requirements:',
|
|
1719
|
-
`- AgentPie Registry
|
|
1720
|
-
'- Keep public disclosure text, attribution links, and runtime event callbacks; do not remove the
|
|
1986
|
+
`- AgentPie Registry receives the complete Skill package. Keep ${localManifest} on the creator device; it is not an installable-package file.`,
|
|
1987
|
+
'- Keep public disclosure text, attribution links, and runtime event callbacks; do not remove the generated public protocol files.',
|
|
1721
1988
|
'- Before submitting a draft, still exclude .env, keys, node_modules, dist/build, and unrelated local files.'
|
|
1722
1989
|
];
|
|
1723
1990
|
}
|
|
1724
1991
|
if (hub === 'xiashe') {
|
|
1725
1992
|
return [
|
|
1726
1993
|
'虾舍商店兼容性要求:',
|
|
1727
|
-
`-
|
|
1728
|
-
'- 保留公开披露文本、归因链接和 runtime event callback
|
|
1994
|
+
`- 虾舍商店接收完整 Skill 包;${localManifest} 仅保留在创作者设备,不属于安装包。`,
|
|
1995
|
+
'- 保留公开披露文本、归因链接和 runtime event callback;不要删除生成的公开协议文件。',
|
|
1729
1996
|
'- 提交草稿前仍需排除 .env、密钥、node_modules、dist/build 和无关本地文件。'
|
|
1730
1997
|
];
|
|
1731
1998
|
}
|
|
@@ -1913,6 +2180,10 @@ function redPublicTrackingContractBlock(inspected) {
|
|
|
1913
2180
|
`- Latest version endpoint: ${updateEndpoint}`,
|
|
1914
2181
|
'- 如果联网可用,首次使用前或每日第一次使用前检查一次。',
|
|
1915
2182
|
'- 如果 latestVersion 与 Current version 不同,先提示用户有新版本,并询问是否更新;未经确认,不要修改本地文件。',
|
|
2183
|
+
...(protectedPaidSkill ? [
|
|
2184
|
+
`- 对这个受保护包,公开 latest version endpoint 只能用于发现更新,不能用于下载。先通过 ${PRODUCT_NAME} Gateway 调用 \`xiashe.check_skill_update\`;用户明确同意后,调用 \`xiashe.get_skill_update\` 并传 \`userConfirmed=true\`。`,
|
|
2185
|
+
'- 更新交付到当前 Agent 的一次性私有地址:先保留旧包备份,在临时目录下载、校验 SHA-256、解压并阅读 SKILL.md,全部成功后再原子替换;任一步失败都保留旧包。已购账户更新不再次收费。'
|
|
2186
|
+
] : []),
|
|
1916
2187
|
'- 如果 endpoint 返回 blocked / paused / disabled,提示当前版本已停用,不要静默继续。',
|
|
1917
2188
|
'- 如果无法联网,继续完成用户任务,并简单说明更新状态未检查。',
|
|
1918
2189
|
'',
|
|
@@ -1965,6 +2236,10 @@ function skillMdRegistryBlock(inspected, hub) {
|
|
|
1965
2236
|
`- Latest version endpoint: ${updateEndpoint}`,
|
|
1966
2237
|
'- When network access is available, check the latest version endpoint before first use or once per day.',
|
|
1967
2238
|
'- If a newer latestVersion is returned, show it and ask the user before changing local files.',
|
|
2239
|
+
...(protectedPaidSkill ? [
|
|
2240
|
+
`- For this protected package, the public latest-version endpoint is discovery-only and must not be used as a download URL. Call ${PRODUCT_NAME} Gateway \`xiashe.check_skill_update\`, then after explicit approval call \`xiashe.get_skill_update\` with \`userConfirmed=true\`.`,
|
|
2241
|
+
'- Keep the installed package as backup; download the one-time private delivery into a temporary directory, verify SHA-256, unpack and read SKILL.md, then atomically replace. Keep the old package on any failure. An entitled account is not charged again for updates.'
|
|
2242
|
+
] : []),
|
|
1968
2243
|
'- If the endpoint returns blocked / paused / disabled, tell the user this installed version is disabled and do not continue silently.',
|
|
1969
2244
|
'- If the network is unavailable, continue the user task and briefly state that update status was not checked.',
|
|
1970
2245
|
'',
|
|
@@ -2065,6 +2340,7 @@ function buildRuntimeYaml(inspected, hub = 'red') {
|
|
|
2065
2340
|
" when: 'before_first_use_or_daily_when_network_available'",
|
|
2066
2341
|
" noNetwork: 'continue_and_state_update_status_unchecked'",
|
|
2067
2342
|
" updateAvailable: 'show_latest_version_and_ask_user_before_changing_files'",
|
|
2343
|
+
` protectedDelivery: ${yamlString(protectedPaidSkill ? 'gateway_check_then_confirmed_one_time_delivery; backup_verify_extract_read_skill_md_then_atomic_replace; no_second_charge_for_entitled_account' : 'not_required')}`,
|
|
2068
2344
|
" blocked: 'tell_user_current_version_is_disabled_and_do_not_continue_silently'",
|
|
2069
2345
|
'creator:',
|
|
2070
2346
|
` displayName: ${yamlString(creatorName)}`,
|
|
@@ -2177,6 +2453,7 @@ async function writePublicProtocolFiles(root, inspected, hub = 'red') {
|
|
|
2177
2453
|
const disclosurePath = path.join(protocolDir, 'REGISTRY_DISCLOSURE.md');
|
|
2178
2454
|
const runtimePath = path.join(protocolDir, 'runtime.yaml');
|
|
2179
2455
|
const registry = inspected.registry || {};
|
|
2456
|
+
const protectedPaidSkill = isProtectedPaidSkill(inspected);
|
|
2180
2457
|
const publicSkillId = registry.skillId || registry.publicSkillId || `<public skill id from ${PRODUCT_NAME} registry>`;
|
|
2181
2458
|
const agentAck = [
|
|
2182
2459
|
agentAckInstructionBlock(inspected, hub),
|
|
@@ -2185,7 +2462,9 @@ async function writePublicProtocolFiles(root, inspected, hub = 'red') {
|
|
|
2185
2462
|
'',
|
|
2186
2463
|
'1. Install the Skill through the target platform official flow.',
|
|
2187
2464
|
`2. Read \`${PUBLIC_PROTOCOL_DIR}/runtime.yaml\` and this file to get the exact \`publicSkillId\`: \`${publicSkillId}\`. Never infer it from the title or directory name.`,
|
|
2188
|
-
|
|
2465
|
+
protectedPaidSkill
|
|
2466
|
+
? `3. When network is available, check \`runtime.yaml\` \`updateCheck.latestVersionEndpoint\` before first use or once per day. If a newer version exists, use ${PRODUCT_NAME} Gateway \`xiashe.check_skill_update\`, ask the user, then call \`xiashe.get_skill_update\` with \`userConfirmed=true\`. Preserve the old local package until the new private archive is verified, unpacked, and its SKILL.md read.`
|
|
2467
|
+
: '3. When network is available, check `runtime.yaml` `updateCheck.latestVersionEndpoint` before first use or once per day. If a newer version exists, ask the user whether to update before modifying local files.',
|
|
2189
2468
|
'4. Send `installed` once after installation succeeds. If first use happens before the install event, backfill `installed` first with the same anonymous `installationId`.',
|
|
2190
2469
|
'5. Send `used` when the Skill starts, then `completed` or `failed` when the task ends.',
|
|
2191
2470
|
'6. End every completed Skill turn with the localized footer from `runtime.yaml`. Use the recommendation slot only when the Ack response returns one.',
|
|
@@ -2567,7 +2846,9 @@ async function writeUnifiedHandoff(root, args) {
|
|
|
2567
2846
|
`Event schema: ${registry.eventSchemaVersion || EVENT_SCHEMA_VERSION}`,
|
|
2568
2847
|
'',
|
|
2569
2848
|
`The simplest cross-platform runtime path is Agent Ack. For Red Skill, the host Agent should read ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md or private runtime config first, then report \`installed\`, \`used\`, \`completed\`, or \`failed\` with publicSkillId, anonymous installationId, and invocationId. Do not paste endpoint/publicSkillId into marketing descriptions, screenshots, or user-visible replies. No public token or account login is required for Agent Ack.`,
|
|
2570
|
-
|
|
2849
|
+
protectedPaidSkill
|
|
2850
|
+
? `For user-side protected updates, use ${PUBLIC_PROTOCOL_DIR}/runtime.yaml \`updateCheck.latestVersionEndpoint\` only to discover the latest version. The Agent must call ${PRODUCT_NAME} Gateway \`xiashe.check_skill_update\`, show the version and get explicit user approval, then call \`xiashe.get_skill_update\` with \`userConfirmed=true\`. Its one-time archive must be verified, unpacked, and read before atomically replacing the local package; keep the old package on failure. Do not use a public registry link as a package download or charge an entitled account again.`
|
|
2851
|
+
: `For user-side updates, the simplest path is prompt-based: read ${PUBLIC_PROTOCOL_DIR}/runtime.yaml \`updateCheck.latestVersionEndpoint\` or the SKILL.md latest version endpoint before first use/daily use; if a newer version exists, show it and ask the user before changing local files. If blocked, do not continue silently.`,
|
|
2571
2852
|
'The creator card link is public attribution and should remain visible in allowed platform instructions and user-facing Skill replies. It is not a secret and should not be replaced by registry ids or tokens.',
|
|
2572
2853
|
`Before sending Agent Ack, read ${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md, ${WORK_DIR}/AGENT_ACK.md, or the private registry manifest and use the exact \`publicSkillId\`. Never guess it from the Skill title, package name, directory name, or marketplace slug.`,
|
|
2573
2854
|
'For `installationId`, use a stable anonymous value for this installed copy/runtime connection. If the platform does not provide one, hash `publicSkillId + platform name + install directory or session id`, store only the opaque hash locally, and never send raw paths, emails, phone numbers, real names, prompts, or account identifiers.',
|
|
@@ -2860,12 +3141,11 @@ function doctorCheck(id, label, status, message, fix = '') {
|
|
|
2860
3141
|
|
|
2861
3142
|
async function runDoctor(root, flags) {
|
|
2862
3143
|
const inspected = await inspectSkill(root, flags);
|
|
3144
|
+
const manifestLookup = await readSkillManifest(inspected.root, flags);
|
|
2863
3145
|
const hub = normalizeHub(flags.hub || flags.to || 'generic') || 'generic';
|
|
2864
3146
|
const redHub = hub === 'red';
|
|
2865
3147
|
const skillMdPath = path.join(inspected.root, 'SKILL.md');
|
|
2866
3148
|
const readmePath = await findReadmePath(inspected.root);
|
|
2867
|
-
const manifestPath = path.join(inspected.root, MANIFEST_FILE);
|
|
2868
|
-
const localManifestPath = path.join(inspected.root, WORK_DIR, MANIFEST_FILE);
|
|
2869
3149
|
const disclosurePath = path.join(inspected.root, WORK_DIR, 'REGISTRY_DISCLOSURE.md');
|
|
2870
3150
|
const publicAgentAckPath = path.join(inspected.root, PUBLIC_PROTOCOL_DIR, 'AGENT_ACK.md');
|
|
2871
3151
|
const publicDisclosurePath = path.join(inspected.root, PUBLIC_PROTOCOL_DIR, 'REGISTRY_DISCLOSURE.md');
|
|
@@ -2891,9 +3171,7 @@ async function runDoctor(root, flags) {
|
|
|
2891
3171
|
scannedCallbackText += `\n${text}`;
|
|
2892
3172
|
}
|
|
2893
3173
|
}
|
|
2894
|
-
const manifest = inspected.registry
|
|
2895
|
-
? await readJsonFile(manifestPath).catch(() => null) || await readJsonFile(localManifestPath).catch(() => null)
|
|
2896
|
-
: null;
|
|
3174
|
+
const manifest = inspected.registry ? manifestLookup.manifest : null;
|
|
2897
3175
|
const textBundle = `${skillMd}\n${readme}`;
|
|
2898
3176
|
const hasManifest = Boolean(manifest);
|
|
2899
3177
|
const registry = manifest?.registry || inspected.registry || {};
|
|
@@ -2914,7 +3192,15 @@ async function runDoctor(root, flags) {
|
|
|
2914
3192
|
? doctorCheck('registry_manifest', 'Registry manifest', 'pass', `Found registry manifest for ${registry.provider || REGISTRY_PROVIDER}.`)
|
|
2915
3193
|
: redHub
|
|
2916
3194
|
? 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.`)
|
|
2917
|
-
: doctorCheck(
|
|
3195
|
+
: doctorCheck(
|
|
3196
|
+
'registry_manifest',
|
|
3197
|
+
'Registry manifest',
|
|
3198
|
+
'fail',
|
|
3199
|
+
CAN_ADOPT_LEGACY_XIASHE_MANIFEST
|
|
3200
|
+
? `Missing usable registry manifest (${MANIFEST_FILE}, ${WORK_DIR}/${MANIFEST_FILE}, or a compatible XiaShe local manifest).`
|
|
3201
|
+
: `Missing usable registry manifest (${MANIFEST_FILE} or ${WORK_DIR}/${MANIFEST_FILE}).`,
|
|
3202
|
+
`Run ${COMMAND_NAME} setup . --code <dashboard-code> or ${COMMAND_NAME} attach . --public-token <token> --skill-id <id>.`
|
|
3203
|
+
),
|
|
2918
3204
|
registryBlockPresent(skillMd)
|
|
2919
3205
|
? doctorCheck('registry_block', 'Registry block', 'pass', 'SKILL.md contains the explicit registry disclosure block.')
|
|
2920
3206
|
: redHub
|
|
@@ -3103,7 +3389,8 @@ function formatVerify(result) {
|
|
|
3103
3389
|
|
|
3104
3390
|
async function setupAgentWorkflow(root, flags) {
|
|
3105
3391
|
const hubs = setupHubsFromFlags(flags);
|
|
3106
|
-
const
|
|
3392
|
+
const rootResolution = await resolveExistingSkillRoot(root, flags);
|
|
3393
|
+
const absoluteRoot = rootResolution.root;
|
|
3107
3394
|
const outDir = path.resolve(flags['out-dir'] || path.join(absoluteRoot, WORK_DIR));
|
|
3108
3395
|
await mkdir(outDir, { recursive: true });
|
|
3109
3396
|
const manifestResult = await writeManifest(absoluteRoot, {
|
|
@@ -3201,6 +3488,8 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
3201
3488
|
return {
|
|
3202
3489
|
ok: true,
|
|
3203
3490
|
hubs,
|
|
3491
|
+
linkedSkillDiscovered: rootResolution.discovered,
|
|
3492
|
+
adoptedLegacyXiaSheManifest: manifestResult.adoptedLegacyXiaSheManifest,
|
|
3204
3493
|
manifestPath: manifestResult.manifestPath,
|
|
3205
3494
|
handoffPath,
|
|
3206
3495
|
promptPaths: promptResults,
|
|
@@ -3215,6 +3504,12 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
3215
3504
|
warnings,
|
|
3216
3505
|
publicProtocol,
|
|
3217
3506
|
next: [
|
|
3507
|
+
rootResolution.discovered
|
|
3508
|
+
? 'An existing local Skill connection was found automatically; no new registry entry was created.'
|
|
3509
|
+
: null,
|
|
3510
|
+
manifestResult.adoptedLegacyXiaSheManifest
|
|
3511
|
+
? 'A compatible XiaShe local connection was reused after the registry origin and environment check; a separate AgentPie manifest was created.'
|
|
3512
|
+
: null,
|
|
3218
3513
|
firstPartyPackageArtifact?.skipped
|
|
3219
3514
|
? `${PRODUCT_NAME} complete package upload was skipped (${firstPartyPackageArtifact.reason}).`
|
|
3220
3515
|
: `${PRODUCT_NAME} complete package uploaded: ${path.relative(absoluteRoot, firstPartyPackageArtifact.artifactPath)} (${firstPartyPackageArtifact.publishStatus || 'queued'}).`,
|
|
@@ -3252,8 +3547,9 @@ async function main() {
|
|
|
3252
3547
|
if (commandArgs[0] && !commandArgs[0].startsWith('--')) {
|
|
3253
3548
|
root = commandArgs.shift();
|
|
3254
3549
|
}
|
|
3255
|
-
|
|
3550
|
+
let flags = parseArgs(commandArgs);
|
|
3256
3551
|
try {
|
|
3552
|
+
flags = applyRegistryOriginFlags(flags);
|
|
3257
3553
|
if (command === 'inspect') {
|
|
3258
3554
|
const result = await inspectSkill(root, flags);
|
|
3259
3555
|
print(result, flags.json);
|