@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 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. The creator's Agent is responsible for packaging and uploading according to Xiaohongshu's official flow.
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
 
@@ -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.17';
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 publicToken = normalizeText(flags['public-token'] || claim?.publicToken, 512);
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 creatorCardUrl = normalizeText(flags['creator-card-url'] || claim?.creatorCardUrl || claimCreatorProfile?.cardUrl, 800) || null;
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'], 20) ? 'optional' : 'off',
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 { ok: true, manifestPath, manifest };
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(flags['signing-secret'], 512);
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
- '- Before uploading, 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.',
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
- '- For XiaShe Store, run `npx -y @xiashe/cli skills publish draft . --source xiashe`; it should use the local registry public token first and create or reuse a searchable listing without creator login.',
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}`),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiashe/skill",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "xiashe-skill": "bin/xiashe-skill.mjs"