@xiashe/skill 0.1.17 → 0.1.19
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 +3 -2
- package/bin/xiashe-skill.mjs +343 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,14 +15,15 @@ This package is intentionally separate from the full `@xiashe/cli` product CLI a
|
|
|
15
15
|
- diagnose whether a local Skill has the expected registry disclosure, Agent Ack instructions, and runtime callback wiring where supported
|
|
16
16
|
- send labeled local verification events so the Dashboard can confirm integration health
|
|
17
17
|
- write an explicit `xiashe.skill.json` registry manifest
|
|
18
|
+
- build and upload the XiaShe complete Skill package to XiaShe File Storage when the `xiashe` target is selected
|
|
18
19
|
- generate one unified `UPLOAD_HANDOFF.md` that the creator's Agent can use with Xiaohongshu's official `skillhub-upload` or `uploader.md`
|
|
19
20
|
- generate optional runtime analytics snippets
|
|
20
21
|
|
|
21
|
-
It does not install background services, run postinstall hooks, read secrets, or override Red Skill upload rules.
|
|
22
|
+
It does not install background services, run postinstall hooks, read secrets, or override Red Skill upload rules. XiaShe package upload is handled by this CLI for the XiaShe-owned registry; the creator's Agent is responsible only for third-party packaging and uploading according to Xiaohongshu's official flow.
|
|
22
23
|
|
|
23
24
|
The user-facing product flow should point creators at the official publish Markdown page and the `xiashe-publish` Skill. Direct CLI commands are implementation details for local Agents and developers.
|
|
24
25
|
|
|
25
|
-
`setup --hub all` now defaults to XiaShe Store and Red Skill. It writes private `.xiashe/*` handoff files for local Agents, plus no-secret public runtime protocol files at `xiashe/runtime.yaml`, `xiashe/AGENT_ACK.md`, and `xiashe/REGISTRY_DISCLOSURE.md` for Red Markdown/YAML source packages when the target platform accepts them. Users should not need to pick files manually. When Red Skill provides `uploader.md`, `skillhub-upload`, or its own upload prompt, follow that official flow first; XiaShe keeps tokens, signing secrets, package hashes, storage ids, and private handoff files local while recording Red usage through Agent Ack when the host Agent safely calls it.
|
|
26
|
+
`setup --hub all` now defaults to XiaShe Store and Red Skill. It writes private `.xiashe/*` handoff files for local Agents, uploads the XiaShe complete package as `.tgz` through the registry storage flow, plus writes no-secret public runtime protocol files at `xiashe/runtime.yaml`, `xiashe/AGENT_ACK.md`, and `xiashe/REGISTRY_DISCLOSURE.md` for Red Markdown/YAML source packages when the target platform accepts them. Users should not need to pick files manually. When Red Skill provides `uploader.md`, `skillhub-upload`, or its own upload prompt, follow that official flow first; XiaShe keeps tokens, signing secrets, package hashes, storage ids, and private handoff files local while recording Red usage through Agent Ack when the host Agent safely calls it.
|
|
26
27
|
|
|
27
28
|
## Local development
|
|
28
29
|
|
package/bin/xiashe-skill.mjs
CHANGED
|
@@ -5,8 +5,10 @@ import { existsSync } 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 { promisify } from 'node:util';
|
|
9
|
+
import { gzip } from 'node:zlib';
|
|
8
10
|
|
|
9
|
-
const VERSION = '0.1.
|
|
11
|
+
const VERSION = '0.1.18';
|
|
10
12
|
const COMMAND_NAME = process.env.XIASHE_SKILL_CLI_NAME || 'xiashe-skill';
|
|
11
13
|
const PRODUCT_NAME = process.env.XIASHE_SKILL_PRODUCT_NAME || (COMMAND_NAME === 'agentpie-skill' ? 'AgentPie' : 'XiaShe');
|
|
12
14
|
const REGISTRY_PROVIDER = process.env.XIASHE_SKILL_REGISTRY_PROVIDER || (COMMAND_NAME === 'agentpie-skill' ? 'agentpie' : 'xiashe');
|
|
@@ -22,6 +24,8 @@ const HANDOFF_FILE = 'UPLOAD_HANDOFF.md';
|
|
|
22
24
|
const DEFAULT_REGISTRY_URL = process.env.XIASHE_SKILL_REGISTRY_URL || `${DEFAULT_BASE_URL}/registry/skill-events`;
|
|
23
25
|
const DEFAULT_AGENT_ACK_URL = process.env.XIASHE_SKILL_AGENT_ACK_URL || `${DEFAULT_BASE_URL}/registry/agent-ack`;
|
|
24
26
|
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`;
|
|
25
29
|
const EVENT_SCHEMA_VERSION = process.env.XIASHE_SKILL_EVENT_SCHEMA_VERSION || (REGISTRY_PROVIDER === 'agentpie'
|
|
26
30
|
? 'agentpie.skill.event.v1'
|
|
27
31
|
: 'xiashe.skill.event.v1');
|
|
@@ -212,6 +216,8 @@ const DEFAULT_IGNORE = new Set([
|
|
|
212
216
|
]);
|
|
213
217
|
const MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
214
218
|
const MAX_SOURCE_BYTES = 30 * 1024 * 1024;
|
|
219
|
+
const MAX_XIASHE_PACKAGE_BYTES = 100 * 1024 * 1024;
|
|
220
|
+
const gzipAsync = promisify(gzip);
|
|
215
221
|
|
|
216
222
|
function usage() {
|
|
217
223
|
return `${COMMAND_NAME} ${VERSION}
|
|
@@ -260,8 +266,11 @@ Options:
|
|
|
260
266
|
--signing-secret <secret> Optional HMAC secret for signed runtime events.
|
|
261
267
|
--source-url <url> User-provided source URL an Agent can use during third-party upload.
|
|
262
268
|
--package-url <url> Deprecated alias for --source-url.
|
|
269
|
+
--package-upload-url <url> Package upload ticket endpoint. Defaults to ${DEFAULT_PACKAGE_UPLOAD_URL}
|
|
270
|
+
--package-artifact-url <url> Package artifact attach endpoint. Defaults to ${DEFAULT_PACKAGE_ARTIFACT_URL}
|
|
263
271
|
--out <path> Output prompt or snippet file.
|
|
264
272
|
--out-dir <path> Output directory for setup artifacts. Defaults to ${WORK_DIR}/.
|
|
273
|
+
--no-xiashe-package-upload Skip automatic ${PRODUCT_NAME} complete package upload.
|
|
265
274
|
--embed-skill-md Also write a clearly marked registry section into SKILL.md.
|
|
266
275
|
--no-skill-md Do not write registry text into SKILL.md. Red hub embeds by default.
|
|
267
276
|
--no-snippet Setup should skip writing the runtime analytics snippet.
|
|
@@ -280,7 +289,7 @@ function fail(message, code = 1) {
|
|
|
280
289
|
|
|
281
290
|
function parseArgs(argv) {
|
|
282
291
|
const args = { _: [], json: false, dryRun: false };
|
|
283
|
-
const booleanFlags = new Set(['embed-skill-md', 'no-skill-md', 'no-snippet', 'no-track']);
|
|
292
|
+
const booleanFlags = new Set(['embed-skill-md', 'no-skill-md', 'no-snippet', 'no-track', 'no-xiashe-package-upload']);
|
|
284
293
|
for (let i = 0; i < argv.length; i += 1) {
|
|
285
294
|
const value = argv[i];
|
|
286
295
|
if (value === '--help' || value === '-h') {
|
|
@@ -699,6 +708,285 @@ async function postJson(url, body, timeoutMs = 20_000) {
|
|
|
699
708
|
}
|
|
700
709
|
}
|
|
701
710
|
|
|
711
|
+
function urlForRegistryPath(baseUrl, pathname, fallback) {
|
|
712
|
+
const base = normalizeText(baseUrl, 1_000) || fallback;
|
|
713
|
+
try {
|
|
714
|
+
return new URL(pathname, base).toString();
|
|
715
|
+
} catch {
|
|
716
|
+
return fallback;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function shouldIgnorePackageEntry(relativePath, entryName, isDirectory = false) {
|
|
721
|
+
const normalized = String(relativePath || '').replace(/\\/g, '/');
|
|
722
|
+
const localRegistryPrefix = `${WORK_DIR}/`;
|
|
723
|
+
const packageArtifactPrefix = `${WORK_DIR}/package-artifact/`;
|
|
724
|
+
if (normalized === '.git' || normalized.startsWith('.git/')) return true;
|
|
725
|
+
if (normalized === 'node_modules' || normalized.startsWith('node_modules/')) return true;
|
|
726
|
+
if (normalized === 'dist' || normalized.startsWith('dist/')) return true;
|
|
727
|
+
if (normalized === 'build' || normalized.startsWith('build/')) return true;
|
|
728
|
+
if (normalized === 'coverage' || normalized.startsWith('coverage/')) return true;
|
|
729
|
+
if (normalized === '.DS_Store' || normalized.endsWith('/.DS_Store')) return true;
|
|
730
|
+
if (entryName === '.env' || entryName.startsWith('.env.')) return true;
|
|
731
|
+
if (/\.(pem|key)$/i.test(entryName)) return true;
|
|
732
|
+
if (packageArtifactPrefix && normalized.startsWith(packageArtifactPrefix)) return true;
|
|
733
|
+
if (isDirectory && (normalized === WORK_DIR || normalized === PUBLIC_PROTOCOL_DIR)) return false;
|
|
734
|
+
if (isDirectory && (normalized.startsWith(`${WORK_DIR}/`) || normalized.startsWith(`${PUBLIC_PROTOCOL_DIR}/`))) return false;
|
|
735
|
+
if ((normalized === WORK_DIR || normalized.startsWith(localRegistryPrefix)) && !isXiashePackageRegistryFile(normalized)) {
|
|
736
|
+
return true;
|
|
737
|
+
}
|
|
738
|
+
const otherRegistryDir = WORK_DIR === '.xiashe' ? '.agentpie' : '.xiashe';
|
|
739
|
+
if (normalized === otherRegistryDir || normalized.startsWith(`${otherRegistryDir}/`)) return true;
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function isXiashePackageRegistryFile(relativePath) {
|
|
744
|
+
const normalized = String(relativePath || '').replace(/\\/g, '/');
|
|
745
|
+
const allowed = new Set([
|
|
746
|
+
`${WORK_DIR}/${MANIFEST_FILE}`,
|
|
747
|
+
`${WORK_DIR}/AGENT_ACK.md`,
|
|
748
|
+
`${WORK_DIR}/REGISTRY_DISCLOSURE.md`,
|
|
749
|
+
`${WORK_DIR}/runtime-events.js`,
|
|
750
|
+
`${PUBLIC_PROTOCOL_DIR}/AGENT_ACK.md`,
|
|
751
|
+
`${PUBLIC_PROTOCOL_DIR}/REGISTRY_DISCLOSURE.md`,
|
|
752
|
+
`${PUBLIC_PROTOCOL_DIR}/runtime.yaml`
|
|
753
|
+
]);
|
|
754
|
+
return allowed.has(normalized);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
async function walkXiashePackageFiles(root, current = root, out = []) {
|
|
758
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
759
|
+
for (const entry of entries) {
|
|
760
|
+
const absolute = path.join(current, entry.name);
|
|
761
|
+
const relative = path.relative(root, absolute).replaceAll(path.sep, '/');
|
|
762
|
+
if (shouldIgnorePackageEntry(relative, entry.name, entry.isDirectory())) continue;
|
|
763
|
+
if (entry.isDirectory()) {
|
|
764
|
+
await walkXiashePackageFiles(root, absolute, out);
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
if (!entry.isFile()) continue;
|
|
768
|
+
const info = await stat(absolute);
|
|
769
|
+
if (info.size > MAX_XIASHE_PACKAGE_BYTES) {
|
|
770
|
+
throw new Error(`Package file is too large: ${relative}`);
|
|
771
|
+
}
|
|
772
|
+
out.push({ absolute, relative, size: info.size, mode: info.mode, mtime: info.mtime });
|
|
773
|
+
}
|
|
774
|
+
return out.sort((a, b) => a.relative.localeCompare(b.relative));
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function octal(value, length) {
|
|
778
|
+
const text = Math.max(0, Math.floor(value)).toString(8);
|
|
779
|
+
return text.length >= length ? `${text.slice(-(length - 1))}\0` : `${text.padStart(length - 1, '0')}\0`;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function splitTarPath(name) {
|
|
783
|
+
const normalized = String(name || '').replace(/\\/g, '/');
|
|
784
|
+
if (Buffer.byteLength(normalized) <= 100) {
|
|
785
|
+
return { name: normalized, prefix: '' };
|
|
786
|
+
}
|
|
787
|
+
const parts = normalized.split('/');
|
|
788
|
+
for (let i = parts.length - 1; i > 0; i -= 1) {
|
|
789
|
+
const namePart = parts.slice(i).join('/');
|
|
790
|
+
const prefix = parts.slice(0, i).join('/');
|
|
791
|
+
if (Buffer.byteLength(namePart) <= 100 && Buffer.byteLength(prefix) <= 155) {
|
|
792
|
+
return { name: namePart, prefix };
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
throw new Error(`Package path is too long for portable tar: ${normalized}`);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function tarHeader(file, size) {
|
|
799
|
+
const header = Buffer.alloc(512, 0);
|
|
800
|
+
const split = splitTarPath(file.relative);
|
|
801
|
+
header.write(split.name, 0, 100, 'utf8');
|
|
802
|
+
header.write(octal(file.mode & 0o777 || 0o644, 8), 100, 8, 'ascii');
|
|
803
|
+
header.write(octal(0, 8), 108, 8, 'ascii');
|
|
804
|
+
header.write(octal(0, 8), 116, 8, 'ascii');
|
|
805
|
+
header.write(octal(size, 12), 124, 12, 'ascii');
|
|
806
|
+
header.write(octal(Math.floor(new Date(file.mtime).getTime() / 1000), 12), 136, 12, 'ascii');
|
|
807
|
+
header.fill(0x20, 148, 156);
|
|
808
|
+
header.write('0', 156, 1, 'ascii');
|
|
809
|
+
header.write('ustar', 257, 6, 'ascii');
|
|
810
|
+
header.write('00', 263, 2, 'ascii');
|
|
811
|
+
if (split.prefix) {
|
|
812
|
+
header.write(split.prefix, 345, 155, 'utf8');
|
|
813
|
+
}
|
|
814
|
+
let checksum = 0;
|
|
815
|
+
for (const byte of header) checksum += byte;
|
|
816
|
+
header.write(octal(checksum, 8), 148, 8, 'ascii');
|
|
817
|
+
return header;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
async function createTarGz(files) {
|
|
821
|
+
const chunks = [];
|
|
822
|
+
let sourceTotalBytes = 0;
|
|
823
|
+
const sourceHash = createHash('sha256');
|
|
824
|
+
for (const file of files) {
|
|
825
|
+
const bytes = await readFile(file.absolute);
|
|
826
|
+
sourceTotalBytes += bytes.length;
|
|
827
|
+
if (sourceTotalBytes > MAX_XIASHE_PACKAGE_BYTES) {
|
|
828
|
+
throw new Error(`Package source exceeds ${Math.round(MAX_XIASHE_PACKAGE_BYTES / (1024 * 1024))}MB.`);
|
|
829
|
+
}
|
|
830
|
+
sourceHash.update(file.relative);
|
|
831
|
+
sourceHash.update('\0');
|
|
832
|
+
sourceHash.update(bytes);
|
|
833
|
+
sourceHash.update('\0');
|
|
834
|
+
chunks.push(tarHeader(file, bytes.length));
|
|
835
|
+
chunks.push(bytes);
|
|
836
|
+
const padding = (512 - (bytes.length % 512)) % 512;
|
|
837
|
+
if (padding > 0) chunks.push(Buffer.alloc(padding, 0));
|
|
838
|
+
}
|
|
839
|
+
chunks.push(Buffer.alloc(1024, 0));
|
|
840
|
+
const tar = Buffer.concat(chunks);
|
|
841
|
+
const gzipped = await gzipAsync(tar, { level: 9 });
|
|
842
|
+
if (gzipped.length > MAX_XIASHE_PACKAGE_BYTES) {
|
|
843
|
+
throw new Error(`Package artifact exceeds ${Math.round(MAX_XIASHE_PACKAGE_BYTES / (1024 * 1024))}MB.`);
|
|
844
|
+
}
|
|
845
|
+
return {
|
|
846
|
+
bytes: gzipped,
|
|
847
|
+
sourceSha256: sourceHash.digest('hex'),
|
|
848
|
+
sourceTotalBytes,
|
|
849
|
+
artifactSha256: createHash('sha256').update(gzipped).digest('hex')
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
async function buildXiashePackageArtifact(root, inspected, flags = {}) {
|
|
854
|
+
const absoluteRoot = path.resolve(root || '.');
|
|
855
|
+
const files = await walkXiashePackageFiles(absoluteRoot);
|
|
856
|
+
const manifestRelative = `${WORK_DIR}/${MANIFEST_FILE}`;
|
|
857
|
+
const skillMarkdownPresent = files.some((file) => /^SKILL(\.[a-z0-9]+)?$/i.test(file.relative));
|
|
858
|
+
const manifestPresent = files.some((file) => file.relative === manifestRelative);
|
|
859
|
+
if (!skillMarkdownPresent) {
|
|
860
|
+
throw new Error('SKILL.md is required before uploading the XiaShe complete package.');
|
|
861
|
+
}
|
|
862
|
+
if (!manifestPresent) {
|
|
863
|
+
throw new Error(`${manifestRelative} is required before uploading the XiaShe complete package.`);
|
|
864
|
+
}
|
|
865
|
+
const artifact = await createTarGz(files);
|
|
866
|
+
const artifactDir = path.resolve(flags['package-out-dir'] || path.join(absoluteRoot, WORK_DIR, 'package-artifact'));
|
|
867
|
+
await mkdir(artifactDir, { recursive: true });
|
|
868
|
+
const fileName = `${safeSkillKey(inspected.skillKey)}-${safeSkillKey(inspected.version || '0.1.0')}.tgz`;
|
|
869
|
+
const artifactPath = path.join(artifactDir, fileName);
|
|
870
|
+
await writeFile(artifactPath, artifact.bytes, { mode: 0o600 });
|
|
871
|
+
return {
|
|
872
|
+
artifactPath,
|
|
873
|
+
fileName,
|
|
874
|
+
contentType: 'application/gzip',
|
|
875
|
+
files,
|
|
876
|
+
fileCount: files.length,
|
|
877
|
+
manifestPresent,
|
|
878
|
+
skillMarkdownPresent,
|
|
879
|
+
sourceSha256: artifact.sourceSha256,
|
|
880
|
+
sourceTotalBytes: artifact.sourceTotalBytes,
|
|
881
|
+
artifactSha256: artifact.artifactSha256,
|
|
882
|
+
sizeBytes: artifact.bytes.length
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
async function uploadBytesToStorage(uploadUrl, bytes, contentType, timeoutMs = 60_000) {
|
|
887
|
+
const controller = new AbortController();
|
|
888
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
889
|
+
try {
|
|
890
|
+
const response = await fetch(uploadUrl, {
|
|
891
|
+
method: 'POST',
|
|
892
|
+
headers: { 'content-type': contentType || 'application/octet-stream' },
|
|
893
|
+
body: bytes,
|
|
894
|
+
signal: controller.signal
|
|
895
|
+
});
|
|
896
|
+
const text = await response.text();
|
|
897
|
+
let payload = {};
|
|
898
|
+
try {
|
|
899
|
+
payload = text ? JSON.parse(text) : {};
|
|
900
|
+
} catch {
|
|
901
|
+
throw new Error(`Bad upload response from ${uploadUrl}`);
|
|
902
|
+
}
|
|
903
|
+
if (!response.ok || payload.ok === false) {
|
|
904
|
+
throw new Error(payload.error || payload.message || `HTTP ${response.status}`);
|
|
905
|
+
}
|
|
906
|
+
const storageId = normalizeText(payload.storageId, 160);
|
|
907
|
+
if (!storageId) {
|
|
908
|
+
throw new Error('Upload response did not include storageId.');
|
|
909
|
+
}
|
|
910
|
+
return { ...payload, storageId };
|
|
911
|
+
} finally {
|
|
912
|
+
clearTimeout(timer);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
async function uploadXiashePackageArtifact(root, flags, manifestResult) {
|
|
917
|
+
if (flags.dryRun || flags['no-xiashe-package-upload']) {
|
|
918
|
+
return {
|
|
919
|
+
ok: true,
|
|
920
|
+
skipped: true,
|
|
921
|
+
reason: flags.dryRun ? 'dry_run' : 'disabled_by_flag'
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
const inspected = await inspectSkill(root, flags);
|
|
925
|
+
const registry = manifestResult.manifest?.registry || inspected.registry || {};
|
|
926
|
+
const publicToken = normalizeText(registry.publicToken, 512);
|
|
927
|
+
if (!publicToken) {
|
|
928
|
+
throw new Error('Cannot upload XiaShe package without registry publicToken.');
|
|
929
|
+
}
|
|
930
|
+
const artifact = await buildXiashePackageArtifact(root, inspected, flags);
|
|
931
|
+
const registrySourceSha256 =
|
|
932
|
+
normalizeText(manifestResult.manifest?.sourceFingerprint?.sha256, 128) ||
|
|
933
|
+
normalizeText(inspected.package?.sha256, 128) ||
|
|
934
|
+
artifact.sourceSha256;
|
|
935
|
+
const claimUrl = normalizeText(flags['claim-url'], 800) || DEFAULT_CLAIM_URL;
|
|
936
|
+
const uploadTicket =
|
|
937
|
+
manifestResult.claimPackageArtifactUpload ||
|
|
938
|
+
await postJson(
|
|
939
|
+
normalizeText(flags['package-upload-url'], 1_000) || urlForRegistryPath(claimUrl, '/registry/skill/package-upload-url', DEFAULT_PACKAGE_UPLOAD_URL),
|
|
940
|
+
{
|
|
941
|
+
publicToken,
|
|
942
|
+
idempotencyKey: registrySourceSha256
|
|
943
|
+
},
|
|
944
|
+
Number(flags['timeout-ms'] || 20_000)
|
|
945
|
+
);
|
|
946
|
+
const uploadUrl = normalizeText(uploadTicket?.uploadUrl, 2_000);
|
|
947
|
+
if (!uploadUrl) {
|
|
948
|
+
throw new Error('XiaShe package upload URL was not returned by registry.');
|
|
949
|
+
}
|
|
950
|
+
const bytes = await readFile(artifact.artifactPath);
|
|
951
|
+
const uploaded = await uploadBytesToStorage(uploadUrl, bytes, artifact.contentType, Number(flags['upload-timeout-ms'] || 90_000));
|
|
952
|
+
const attachResult = await postJson(
|
|
953
|
+
normalizeText(flags['package-artifact-url'], 1_000) || urlForRegistryPath(claimUrl, '/registry/skill/package-artifact', DEFAULT_PACKAGE_ARTIFACT_URL),
|
|
954
|
+
{
|
|
955
|
+
publicToken,
|
|
956
|
+
storageId: uploaded.storageId,
|
|
957
|
+
uploadJobId: normalizeText(uploadTicket?.uploadJobId, 160) || undefined,
|
|
958
|
+
idempotencyKey: registrySourceSha256,
|
|
959
|
+
fileName: artifact.fileName,
|
|
960
|
+
contentType: artifact.contentType,
|
|
961
|
+
packageSha256: registrySourceSha256,
|
|
962
|
+
packageFileCount: artifact.fileCount,
|
|
963
|
+
packageTotalBytes: artifact.sourceTotalBytes,
|
|
964
|
+
version: inspected.version,
|
|
965
|
+
manifestPresent: artifact.manifestPresent,
|
|
966
|
+
skillMarkdownPresent: artifact.skillMarkdownPresent,
|
|
967
|
+
creatorCardUrl: normalizeText(registry.creatorCardUrl, 800) || undefined,
|
|
968
|
+
platformSource: 'xiashe',
|
|
969
|
+
source: `${REGISTRY_PROVIDER}_skill_cli`
|
|
970
|
+
},
|
|
971
|
+
Number(flags['timeout-ms'] || 20_000)
|
|
972
|
+
);
|
|
973
|
+
return {
|
|
974
|
+
ok: true,
|
|
975
|
+
skipped: false,
|
|
976
|
+
artifactPath: artifact.artifactPath,
|
|
977
|
+
fileName: artifact.fileName,
|
|
978
|
+
sizeBytes: artifact.sizeBytes,
|
|
979
|
+
sourceSha256: registrySourceSha256,
|
|
980
|
+
packageSourceSha256: artifact.sourceSha256,
|
|
981
|
+
artifactSha256: artifact.artifactSha256,
|
|
982
|
+
fileCount: artifact.fileCount,
|
|
983
|
+
storageId: uploaded.storageId,
|
|
984
|
+
publishStatus: attachResult.publishStatus ?? null,
|
|
985
|
+
uploadJobId: attachResult.uploadJobId ?? normalizeText(uploadTicket?.uploadJobId, 160) ?? null,
|
|
986
|
+
attachResult
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
|
|
702
990
|
async function inspectSkill(root, flags = {}) {
|
|
703
991
|
const absoluteRoot = path.resolve(root || '.');
|
|
704
992
|
const info = await stat(absoluteRoot).catch(() => null);
|
|
@@ -746,14 +1034,24 @@ async function writeManifest(root, flags) {
|
|
|
746
1034
|
Number(flags['timeout-ms'] || 20_000)
|
|
747
1035
|
)
|
|
748
1036
|
: null;
|
|
749
|
-
const
|
|
1037
|
+
const existingRegistry = inspected.registry || {};
|
|
1038
|
+
const publicToken = normalizeText(flags['public-token'] || claim?.publicToken || existingRegistry.publicToken, 512);
|
|
750
1039
|
if (!publicToken) {
|
|
751
1040
|
throw new Error('Missing --code or --public-token.');
|
|
752
1041
|
}
|
|
753
1042
|
const now = new Date().toISOString();
|
|
754
1043
|
const claimCreatorProfile = normalizeCreatorProfile(claim?.creatorProfile);
|
|
755
|
-
const
|
|
1044
|
+
const existingCreatorProfile = normalizeCreatorProfile(existingRegistry.creatorProfile);
|
|
1045
|
+
const creatorCardUrl = normalizeText(
|
|
1046
|
+
flags['creator-card-url'] ||
|
|
1047
|
+
claim?.creatorCardUrl ||
|
|
1048
|
+
claimCreatorProfile?.cardUrl ||
|
|
1049
|
+
existingRegistry.creatorCardUrl ||
|
|
1050
|
+
existingCreatorProfile?.cardUrl,
|
|
1051
|
+
800
|
|
1052
|
+
) || null;
|
|
756
1053
|
const creatorProfile = normalizeCreatorProfile({
|
|
1054
|
+
...existingCreatorProfile,
|
|
757
1055
|
...claimCreatorProfile,
|
|
758
1056
|
cardUrl: creatorCardUrl
|
|
759
1057
|
});
|
|
@@ -765,15 +1063,17 @@ async function writeManifest(root, flags) {
|
|
|
765
1063
|
version: normalizeText(claim?.version, 80) || inspected.version,
|
|
766
1064
|
registry: {
|
|
767
1065
|
provider: REGISTRY_PROVIDER,
|
|
768
|
-
skillId: normalizeText(flags['skill-id'] || claim?.skillId, 160) || null,
|
|
1066
|
+
skillId: normalizeText(flags['skill-id'] || claim?.skillId || existingRegistry.skillId, 160) || null,
|
|
769
1067
|
publicToken,
|
|
770
|
-
registryUrl: normalizeText(flags['registry-url'] || claim?.registryUrl, 800) || DEFAULT_REGISTRY_URL,
|
|
771
|
-
agentAckUrl: normalizeText(flags['agent-ack-url'], 800) || DEFAULT_AGENT_ACK_URL,
|
|
1068
|
+
registryUrl: normalizeText(flags['registry-url'] || claim?.registryUrl || existingRegistry.registryUrl, 800) || DEFAULT_REGISTRY_URL,
|
|
1069
|
+
agentAckUrl: normalizeText(flags['agent-ack-url'] || existingRegistry.agentAckUrl, 800) || DEFAULT_AGENT_ACK_URL,
|
|
772
1070
|
creatorCardUrl,
|
|
773
1071
|
creatorProfile,
|
|
774
1072
|
eventSchemaVersion: EVENT_SCHEMA_VERSION,
|
|
775
1073
|
signing: {
|
|
776
|
-
mode: normalizeText(flags['signing-secret'],
|
|
1074
|
+
mode: normalizeText(claim?.signingMode || existingRegistry.signing?.mode || existingRegistry.signingMode, 20) || (normalizeText(claim?.signingSecret || flags['signing-secret'] || existingRegistry.signing?.secret, 512) ? 'optional' : 'off'),
|
|
1075
|
+
secret: normalizeText(flags['signing-secret'] || claim?.signingSecret || existingRegistry.signing?.secret, 512) || undefined,
|
|
1076
|
+
secretHint: normalizeText(claim?.signingSecretHint || existingRegistry.signing?.secretHint, 80) || undefined,
|
|
777
1077
|
secretEnv: `${REGISTRY_PROVIDER.toUpperCase()}_SKILL_SIGNING_SECRET`
|
|
778
1078
|
},
|
|
779
1079
|
analytics: {
|
|
@@ -813,8 +1113,8 @@ async function writeManifest(root, flags) {
|
|
|
813
1113
|
hub,
|
|
814
1114
|
sourceSurface: hub,
|
|
815
1115
|
skillKey: safeSkillKey(claim?.skillKey || inspected.skillKey),
|
|
816
|
-
publicSkillId: normalizeText(flags['skill-id'] || claim?.skillId, 160) || null,
|
|
817
|
-
agentAckUrl: normalizeText(flags['agent-ack-url'], 800) || DEFAULT_AGENT_ACK_URL,
|
|
1116
|
+
publicSkillId: normalizeText(flags['skill-id'] || claim?.skillId || existingRegistry.skillId, 160) || null,
|
|
1117
|
+
agentAckUrl: normalizeText(flags['agent-ack-url'] || existingRegistry.agentAckUrl, 800) || DEFAULT_AGENT_ACK_URL,
|
|
818
1118
|
creatorCardUrl,
|
|
819
1119
|
creatorProfile
|
|
820
1120
|
}
|
|
@@ -835,7 +1135,12 @@ async function writeManifest(root, flags) {
|
|
|
835
1135
|
: path.join(inspected.root, MANIFEST_FILE);
|
|
836
1136
|
await mkdir(path.dirname(manifestPath), { recursive: true });
|
|
837
1137
|
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o600 });
|
|
838
|
-
return {
|
|
1138
|
+
return {
|
|
1139
|
+
ok: true,
|
|
1140
|
+
manifestPath,
|
|
1141
|
+
manifest,
|
|
1142
|
+
claimPackageArtifactUpload: claim?.packageArtifactUpload ?? null
|
|
1143
|
+
};
|
|
839
1144
|
}
|
|
840
1145
|
|
|
841
1146
|
function hubInstruction(hub) {
|
|
@@ -1883,7 +2188,13 @@ async function submitTrackEvent(root, flags) {
|
|
|
1883
2188
|
idempotencyKey: normalizeText(flags.idempotencyKey || flags['idempotency-key'], 220) || `${invocationId}:${eventType}`,
|
|
1884
2189
|
occurredAt: Number.isFinite(occurredAt) ? occurredAt : Date.now()
|
|
1885
2190
|
};
|
|
1886
|
-
const signingSecret = normalizeText(
|
|
2191
|
+
const signingSecret = normalizeText(
|
|
2192
|
+
flags['signing-secret'] ||
|
|
2193
|
+
registry.signing?.secret ||
|
|
2194
|
+
process.env[registry.signing?.secretEnv || ''] ||
|
|
2195
|
+
process.env.XIASHE_SKILL_SIGNING_SECRET,
|
|
2196
|
+
512
|
|
2197
|
+
);
|
|
1887
2198
|
const signature = signEventPayload(payload, signingSecret);
|
|
1888
2199
|
if (signature) {
|
|
1889
2200
|
payload.signature = signature;
|
|
@@ -2239,6 +2550,13 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
2239
2550
|
promptResults,
|
|
2240
2551
|
packagePlans
|
|
2241
2552
|
});
|
|
2553
|
+
const xiashePackageArtifact = hubs.includes('xiashe') && REGISTRY_PROVIDER === 'xiashe'
|
|
2554
|
+
? await uploadXiashePackageArtifact(absoluteRoot, flags, manifestResult)
|
|
2555
|
+
: {
|
|
2556
|
+
ok: true,
|
|
2557
|
+
skipped: true,
|
|
2558
|
+
reason: hubs.includes('xiashe') ? 'non_xiashe_provider' : 'xiashe_not_selected'
|
|
2559
|
+
};
|
|
2242
2560
|
|
|
2243
2561
|
const warnings = [];
|
|
2244
2562
|
const promptEvents = [];
|
|
@@ -2273,10 +2591,14 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
2273
2591
|
agentAckPath,
|
|
2274
2592
|
disclosurePath,
|
|
2275
2593
|
snippetPath: snippetResult ? snippetResult.outPath : null,
|
|
2594
|
+
xiashePackageArtifact,
|
|
2276
2595
|
promptEvents,
|
|
2277
2596
|
warnings,
|
|
2278
2597
|
publicProtocol,
|
|
2279
2598
|
next: [
|
|
2599
|
+
xiashePackageArtifact?.skipped
|
|
2600
|
+
? `XiaShe complete package upload was skipped (${xiashePackageArtifact.reason}).`
|
|
2601
|
+
: `XiaShe complete package uploaded: ${path.relative(absoluteRoot, xiashePackageArtifact.artifactPath)} (${xiashePackageArtifact.publishStatus || 'queued'}).`,
|
|
2280
2602
|
`Use ${path.relative(absoluteRoot, handoffPath)} as the single Agent handoff. The Agent should not ask the user to manually pick registry files.`,
|
|
2281
2603
|
`Use ${path.relative(absoluteRoot, profilesPath)} and package-<hub>.json as the platform review profiles and upload allowlists.`,
|
|
2282
2604
|
publicProtocol
|
|
@@ -2345,6 +2667,9 @@ async function main() {
|
|
|
2345
2667
|
...result.packagePlanPaths.map((item) => `Package plan (${item.hub}): ${item.planPath}`),
|
|
2346
2668
|
result.skillMdPath ? `Updated: ${result.skillMdPath}` : null,
|
|
2347
2669
|
result.publicProtocol ? `Public Red protocol: ${result.publicProtocol.runtimePath}, ${result.publicProtocol.agentAckPath}, ${result.publicProtocol.disclosurePath}` : null,
|
|
2670
|
+
result.xiashePackageArtifact && !result.xiashePackageArtifact.skipped
|
|
2671
|
+
? `XiaShe package: ${result.xiashePackageArtifact.artifactPath} (${result.xiashePackageArtifact.publishStatus || 'queued'})`
|
|
2672
|
+
: null,
|
|
2348
2673
|
`Disclosure: ${result.disclosurePath}`,
|
|
2349
2674
|
result.snippetPath ? `Runtime snippet: ${result.snippetPath}` : null,
|
|
2350
2675
|
...result.warnings.map((warning) => `Warning: ${warning}`),
|
|
@@ -2352,10 +2677,10 @@ async function main() {
|
|
|
2352
2677
|
'Next:',
|
|
2353
2678
|
'- Treat the target platform official upload flow as authoritative.',
|
|
2354
2679
|
'- Use the handoff file as the only XiaShe registry checklist.',
|
|
2355
|
-
'-
|
|
2680
|
+
'- XiaShe complete package upload is already handled by this command when the xiashe target is selected.',
|
|
2681
|
+
'- 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.',
|
|
2356
2682
|
'- Check upload readiness and runtime readiness separately before submission.',
|
|
2357
|
-
'-
|
|
2358
|
-
'- Only ask for creator CLI login if the registry public token is missing or rejected.',
|
|
2683
|
+
'- Only ask for creator CLI login if a separate store-management command is needed and the registry public token is missing or rejected.',
|
|
2359
2684
|
'- For Markdown/TXT-only platforms, copy the disclosure text into an allowed public field instead of uploading registry JSON.',
|
|
2360
2685
|
'- Only count runtime usage when the Skill or Agent callback sends real runtime events.'
|
|
2361
2686
|
].filter(Boolean);
|
|
@@ -2377,6 +2702,9 @@ async function main() {
|
|
|
2377
2702
|
result.publicProtocol ? `Wrote ${result.publicProtocol.runtimePath}` : null,
|
|
2378
2703
|
result.publicProtocol ? `Wrote ${result.publicProtocol.agentAckPath}` : null,
|
|
2379
2704
|
result.publicProtocol ? `Wrote ${result.publicProtocol.disclosurePath}` : null,
|
|
2705
|
+
result.xiashePackageArtifact && !result.xiashePackageArtifact.skipped
|
|
2706
|
+
? `Uploaded XiaShe package ${result.xiashePackageArtifact.artifactPath} (${result.xiashePackageArtifact.publishStatus || 'queued'})`
|
|
2707
|
+
: null,
|
|
2380
2708
|
`Wrote ${result.disclosurePath}`,
|
|
2381
2709
|
result.snippetPath ? `Wrote ${result.snippetPath}` : null,
|
|
2382
2710
|
...result.warnings.map((warning) => `Warning: ${warning}`),
|