@xiashe/skill 0.1.17 → 0.1.18

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,10 +1063,10 @@ 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,
@@ -813,8 +1111,8 @@ async function writeManifest(root, flags) {
813
1111
  hub,
814
1112
  sourceSurface: hub,
815
1113
  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,
1114
+ publicSkillId: normalizeText(flags['skill-id'] || claim?.skillId || existingRegistry.skillId, 160) || null,
1115
+ agentAckUrl: normalizeText(flags['agent-ack-url'] || existingRegistry.agentAckUrl, 800) || DEFAULT_AGENT_ACK_URL,
818
1116
  creatorCardUrl,
819
1117
  creatorProfile
820
1118
  }
@@ -835,7 +1133,12 @@ async function writeManifest(root, flags) {
835
1133
  : path.join(inspected.root, MANIFEST_FILE);
836
1134
  await mkdir(path.dirname(manifestPath), { recursive: true });
837
1135
  await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o600 });
838
- return { ok: true, manifestPath, manifest };
1136
+ return {
1137
+ ok: true,
1138
+ manifestPath,
1139
+ manifest,
1140
+ claimPackageArtifactUpload: claim?.packageArtifactUpload ?? null
1141
+ };
839
1142
  }
840
1143
 
841
1144
  function hubInstruction(hub) {
@@ -2239,6 +2542,13 @@ async function setupAgentWorkflow(root, flags) {
2239
2542
  promptResults,
2240
2543
  packagePlans
2241
2544
  });
2545
+ const xiashePackageArtifact = hubs.includes('xiashe') && REGISTRY_PROVIDER === 'xiashe'
2546
+ ? await uploadXiashePackageArtifact(absoluteRoot, flags, manifestResult)
2547
+ : {
2548
+ ok: true,
2549
+ skipped: true,
2550
+ reason: hubs.includes('xiashe') ? 'non_xiashe_provider' : 'xiashe_not_selected'
2551
+ };
2242
2552
 
2243
2553
  const warnings = [];
2244
2554
  const promptEvents = [];
@@ -2273,10 +2583,14 @@ async function setupAgentWorkflow(root, flags) {
2273
2583
  agentAckPath,
2274
2584
  disclosurePath,
2275
2585
  snippetPath: snippetResult ? snippetResult.outPath : null,
2586
+ xiashePackageArtifact,
2276
2587
  promptEvents,
2277
2588
  warnings,
2278
2589
  publicProtocol,
2279
2590
  next: [
2591
+ xiashePackageArtifact?.skipped
2592
+ ? `XiaShe complete package upload was skipped (${xiashePackageArtifact.reason}).`
2593
+ : `XiaShe complete package uploaded: ${path.relative(absoluteRoot, xiashePackageArtifact.artifactPath)} (${xiashePackageArtifact.publishStatus || 'queued'}).`,
2280
2594
  `Use ${path.relative(absoluteRoot, handoffPath)} as the single Agent handoff. The Agent should not ask the user to manually pick registry files.`,
2281
2595
  `Use ${path.relative(absoluteRoot, profilesPath)} and package-<hub>.json as the platform review profiles and upload allowlists.`,
2282
2596
  publicProtocol
@@ -2345,6 +2659,9 @@ async function main() {
2345
2659
  ...result.packagePlanPaths.map((item) => `Package plan (${item.hub}): ${item.planPath}`),
2346
2660
  result.skillMdPath ? `Updated: ${result.skillMdPath}` : null,
2347
2661
  result.publicProtocol ? `Public Red protocol: ${result.publicProtocol.runtimePath}, ${result.publicProtocol.agentAckPath}, ${result.publicProtocol.disclosurePath}` : null,
2662
+ result.xiashePackageArtifact && !result.xiashePackageArtifact.skipped
2663
+ ? `XiaShe package: ${result.xiashePackageArtifact.artifactPath} (${result.xiashePackageArtifact.publishStatus || 'queued'})`
2664
+ : null,
2348
2665
  `Disclosure: ${result.disclosurePath}`,
2349
2666
  result.snippetPath ? `Runtime snippet: ${result.snippetPath}` : null,
2350
2667
  ...result.warnings.map((warning) => `Warning: ${warning}`),
@@ -2352,10 +2669,10 @@ async function main() {
2352
2669
  'Next:',
2353
2670
  '- Treat the target platform official upload flow as authoritative.',
2354
2671
  '- 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.',
2672
+ '- XiaShe complete package upload is already handled by this command when the xiashe target is selected.',
2673
+ '- 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
2674
  '- 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.',
2675
+ '- Only ask for creator CLI login if a separate store-management command is needed and the registry public token is missing or rejected.',
2359
2676
  '- For Markdown/TXT-only platforms, copy the disclosure text into an allowed public field instead of uploading registry JSON.',
2360
2677
  '- Only count runtime usage when the Skill or Agent callback sends real runtime events.'
2361
2678
  ].filter(Boolean);
@@ -2377,6 +2694,9 @@ async function main() {
2377
2694
  result.publicProtocol ? `Wrote ${result.publicProtocol.runtimePath}` : null,
2378
2695
  result.publicProtocol ? `Wrote ${result.publicProtocol.agentAckPath}` : null,
2379
2696
  result.publicProtocol ? `Wrote ${result.publicProtocol.disclosurePath}` : null,
2697
+ result.xiashePackageArtifact && !result.xiashePackageArtifact.skipped
2698
+ ? `Uploaded XiaShe package ${result.xiashePackageArtifact.artifactPath} (${result.xiashePackageArtifact.publishStatus || 'queued'})`
2699
+ : null,
2380
2700
  `Wrote ${result.disclosurePath}`,
2381
2701
  result.snippetPath ? `Wrote ${result.snippetPath}` : null,
2382
2702
  ...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.18",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "xiashe-skill": "bin/xiashe-skill.mjs"