@xiashe/skill 0.1.24 → 0.1.27
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 -1
- package/bin/xiashe-skill.mjs +359 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,7 +44,9 @@ 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`
|
|
47
|
+
Use the `claimUrl` returned with a dashboard dynamic code exactly as issued. `publish` validates that destination before it consumes a code, so a code cannot accidentally be sent to a different service. For an existing Skill update, run the normal publish command from the original project folder. The CLI reads and validates its saved local connection before it writes or uploads anything. Do not copy internal endpoint options into a user-facing update task.
|
|
48
|
+
|
|
49
|
+
When an existing project was originally linked through XiaShe, `@agentpie/skill publish` can reuse that local connection only for an update without a dynamic code or public token. It validates the recorded connection first, then creates a separate AgentPie local connection while retaining the XiaShe original. If it cannot verify the link, it stops before changing files or uploading a package. This keeps distinct services isolated without asking users to understand internal deployment details.
|
|
48
50
|
|
|
49
51
|
## Reliable publish and recovery
|
|
50
52
|
|
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.27';
|
|
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,6 +97,14 @@ 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');
|
|
101
109
|
const PUBLIC_PACKAGE_MANIFEST_FILE = 'package-manifest.json';
|
|
102
110
|
const HANDOFF_FILE = 'UPLOAD_HANDOFF.md';
|
|
@@ -305,6 +313,12 @@ const DEFAULT_IGNORE = new Set([
|
|
|
305
313
|
const MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
306
314
|
const MAX_SOURCE_BYTES = 30 * 1024 * 1024;
|
|
307
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;
|
|
308
322
|
const gzipAsync = promisify(gzip);
|
|
309
323
|
|
|
310
324
|
function usage() {
|
|
@@ -316,7 +330,7 @@ Usage:
|
|
|
316
330
|
${COMMAND_NAME} setup [skill-dir] --code <dynamic-code> [--hub all|${FIRST_PARTY_HUB}|red]
|
|
317
331
|
${COMMAND_NAME} setup [skill-dir] --public-token <token> --skill-id <id> [--hub all|${FIRST_PARTY_HUB}|red]
|
|
318
332
|
${COMMAND_NAME} doctor [skill-dir] [--json]
|
|
319
|
-
${COMMAND_NAME} preflight --claim-url <url> [--json]
|
|
333
|
+
${COMMAND_NAME} preflight (--claim-url <url> | --registry-origin <url>) [--json]
|
|
320
334
|
${COMMAND_NAME} verify [skill-dir] [--hub <hub>] [--scenario <label>] [--dry-run]
|
|
321
335
|
${COMMAND_NAME} attach [skill-dir] --code <dynamic-code> [--name <name>]
|
|
322
336
|
${COMMAND_NAME} attach [skill-dir] --public-token <token> [--skill-id <id>] [--name <name>]
|
|
@@ -327,6 +341,7 @@ Usage:
|
|
|
327
341
|
Options:
|
|
328
342
|
--public-token <token> Public write token issued by ${PRODUCT_NAME} registry.
|
|
329
343
|
--code <dynamic-code> One-time ${PRODUCT_NAME} dashboard code used to claim registry identity.
|
|
344
|
+
--registry-origin <url> Advanced recovery option for an existing Skill. Normal updates do not need it.
|
|
330
345
|
--claim-url <url> Code claim endpoint. Defaults to XIASHE_SKILL_CLAIM_URL or ${DEFAULT_CLAIM_URL}
|
|
331
346
|
--registry-doctor-url <url> Registry preflight endpoint. Defaults to the claim URL origin.
|
|
332
347
|
--skill-id <id> Public ${PRODUCT_NAME} Skill registry id.
|
|
@@ -525,6 +540,10 @@ function latestVersionEndpoint(inspectedOrRegistry = {}) {
|
|
|
525
540
|
const skillKey = safeSkillKey(inspectedOrRegistry.skillKey || registry.skillKey || '');
|
|
526
541
|
const creatorCardUrl = normalizeText(registry.creatorCardUrl || registry.creatorProfile?.cardUrl, 800);
|
|
527
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 = resolveStoredRegistryOrigin(registry) || DEFAULT_BASE_URL;
|
|
528
547
|
const params = new URLSearchParams();
|
|
529
548
|
if (publicSkillId) {
|
|
530
549
|
params.set('publicSkillId', publicSkillId);
|
|
@@ -532,7 +551,7 @@ function latestVersionEndpoint(inspectedOrRegistry = {}) {
|
|
|
532
551
|
params.set('skillKey', skillKey);
|
|
533
552
|
params.set('creatorCardUrl', creatorCardUrl);
|
|
534
553
|
}
|
|
535
|
-
return `${
|
|
554
|
+
return `${registryOrigin}/registry/skill/latest${params.toString() ? `?${params.toString()}` : ''}`;
|
|
536
555
|
}
|
|
537
556
|
|
|
538
557
|
function creatorFooterTemplate(inspected, language = 'zh') {
|
|
@@ -808,12 +827,51 @@ async function readJsonFile(filePath) {
|
|
|
808
827
|
}
|
|
809
828
|
}
|
|
810
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
|
+
|
|
811
872
|
async function inferSkillMetadata(root, flags = {}) {
|
|
812
|
-
const
|
|
813
|
-
|
|
814
|
-
: path.join(root, MANIFEST_FILE);
|
|
815
|
-
const localManifestPath = path.join(root, WORK_DIR, MANIFEST_FILE);
|
|
816
|
-
const manifest = await readJsonFile(manifestPath) || await readJsonFile(localManifestPath);
|
|
873
|
+
const manifestLookup = await readSkillManifest(root, flags);
|
|
874
|
+
const manifest = manifestLookup.manifest;
|
|
817
875
|
const packageJson = await readJsonFile(path.join(root, 'package.json'));
|
|
818
876
|
const skillMdPath = path.join(root, 'SKILL.md');
|
|
819
877
|
const readmePath = path.join(root, 'README.md');
|
|
@@ -835,10 +893,113 @@ async function inferSkillMetadata(root, flags = {}) {
|
|
|
835
893
|
billingModel: normalizeText(flags['billing-model'] || manifest?.billingModel || manifest?.registry?.billingModel, 80) || null,
|
|
836
894
|
monetizationMode: normalizeText(flags['monetization-mode'] || manifest?.monetizationMode || manifest?.registry?.monetizationMode, 80) || null,
|
|
837
895
|
accessMode: normalizeText(flags['access-mode'] || manifest?.accessMode || manifest?.registry?.accessMode, 80) || null,
|
|
896
|
+
manifestPath: manifestLookup.manifestPath,
|
|
897
|
+
manifestSource: manifestLookup.source,
|
|
838
898
|
registry: manifest?.registry || null
|
|
839
899
|
};
|
|
840
900
|
}
|
|
841
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_SOURCE_AMBIGUOUS: more than one Skill project matches this update. ' +
|
|
994
|
+
'No Skill was changed. Run this task from the intended Skill project folder.'
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
throw new Error(
|
|
998
|
+
'CREATOR_SKILL_SOURCE_NOT_FOUND: this task does not include the original Skill project. ' +
|
|
999
|
+
'No Skill was created or changed. Add that project folder and retry.'
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
842
1003
|
function shouldIgnore(name) {
|
|
843
1004
|
return DEFAULT_IGNORE.has(name) ||
|
|
844
1005
|
name.startsWith('.env.') ||
|
|
@@ -962,11 +1123,106 @@ function claimUrlForCode(flags = {}) {
|
|
|
962
1123
|
}
|
|
963
1124
|
|
|
964
1125
|
function claimUrlForExistingManifest(flags = {}, registry = {}) {
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1126
|
+
const claimUrl = explicitClaimUrl(flags) || claimUrlFromRegistryOrigin(resolveStoredRegistryOrigin(registry));
|
|
1127
|
+
if (!claimUrl) {
|
|
1128
|
+
throw new Error(
|
|
1129
|
+
'CREATOR_SKILL_CONNECTION_UNVERIFIABLE: this local Skill connection cannot be verified safely. ' +
|
|
1130
|
+
'No Skill was created or changed. Add the original Skill project folder and retry.'
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
return claimUrl;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function normalizeRegistryOrigin(value) {
|
|
1137
|
+
const raw = normalizeText(value, 1_000);
|
|
1138
|
+
if (!raw) return '';
|
|
1139
|
+
let url;
|
|
1140
|
+
try {
|
|
1141
|
+
url = new URL(raw);
|
|
1142
|
+
} catch {
|
|
1143
|
+
throw new Error('CREATOR_SKILL_REGISTRY_ORIGIN_INVALID: registry origin is not a valid URL.');
|
|
1144
|
+
}
|
|
1145
|
+
const host = url.hostname.toLowerCase();
|
|
1146
|
+
const isLocalhost = host === 'localhost' || host === '127.0.0.1' || host === '::1' || host.endsWith('.localhost');
|
|
1147
|
+
if (url.pathname !== '/' || url.search || url.hash || url.username || url.password) {
|
|
1148
|
+
throw new Error('CREATOR_SKILL_REGISTRY_ORIGIN_INVALID: registry origin must not contain a path, query, fragment, or credentials.');
|
|
1149
|
+
}
|
|
1150
|
+
if (url.protocol !== 'https:' && !(isLocalhost && process.env.XIASHE_SKILL_ALLOW_INSECURE_LOCALHOST === '1')) {
|
|
1151
|
+
throw new Error('CREATOR_SKILL_REGISTRY_ORIGIN_INSECURE: only HTTPS registry origins are accepted.');
|
|
1152
|
+
}
|
|
1153
|
+
return url.origin.replace(/\/+$/, '');
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function registryOriginFromFlags(flags = {}) {
|
|
1157
|
+
return normalizeRegistryOrigin(flags['registry-origin']);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
const STORED_REGISTRY_ENDPOINT_PATHS = new Set([
|
|
1161
|
+
'/registry/skill/claim',
|
|
1162
|
+
'/registry/skill-events',
|
|
1163
|
+
'/registry/agent-ack',
|
|
1164
|
+
'/registry/skill/latest',
|
|
1165
|
+
'/registry/skill/package-upload-url',
|
|
1166
|
+
'/registry/skill/package-status'
|
|
1167
|
+
]);
|
|
1168
|
+
|
|
1169
|
+
function registryOriginFromStoredEndpoint(value) {
|
|
1170
|
+
const raw = normalizeText(value, 2_000);
|
|
1171
|
+
if (!raw) return '';
|
|
1172
|
+
let url;
|
|
1173
|
+
try {
|
|
1174
|
+
url = new URL(raw);
|
|
1175
|
+
} catch {
|
|
1176
|
+
return '';
|
|
1177
|
+
}
|
|
1178
|
+
if (url.username || url.password || url.hash) return '';
|
|
1179
|
+
const pathname = url.pathname.replace(/\/+$/, '') || '/';
|
|
1180
|
+
if (!STORED_REGISTRY_ENDPOINT_PATHS.has(pathname)) return '';
|
|
1181
|
+
try {
|
|
1182
|
+
return normalizeRegistryOrigin(url.origin);
|
|
1183
|
+
} catch {
|
|
1184
|
+
return '';
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
function resolveStoredRegistryOrigin(registry = {}) {
|
|
1189
|
+
const directOrigin = normalizeBaseUrl(registry.registryOrigin);
|
|
1190
|
+
if (directOrigin) {
|
|
1191
|
+
try {
|
|
1192
|
+
return normalizeRegistryOrigin(directOrigin);
|
|
1193
|
+
} catch {
|
|
1194
|
+
return '';
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
for (const endpoint of [
|
|
1198
|
+
registry.claimUrl,
|
|
1199
|
+
registry.registryUrl,
|
|
1200
|
+
registry.agentAckUrl,
|
|
1201
|
+
registry.latestVersionEndpoint
|
|
1202
|
+
]) {
|
|
1203
|
+
const origin = registryOriginFromStoredEndpoint(endpoint);
|
|
1204
|
+
if (origin) return origin;
|
|
1205
|
+
}
|
|
1206
|
+
return '';
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
function applyRegistryOriginFlags(flags = {}) {
|
|
1210
|
+
const registryOrigin = registryOriginFromFlags(flags);
|
|
1211
|
+
if (!registryOrigin) return flags;
|
|
1212
|
+
if (normalizeText(flags.code, 80)) {
|
|
1213
|
+
throw new Error('CREATOR_SKILL_REGISTRY_ORIGIN_WITH_CODE_FORBIDDEN: dynamic codes must use the exact dashboard --claim-url, not --registry-origin.');
|
|
1214
|
+
}
|
|
1215
|
+
for (const key of ['claim-url', 'registry-url', 'agent-ack-url', 'registry-doctor-url']) {
|
|
1216
|
+
if (normalizeText(flags[key], 1_000)) {
|
|
1217
|
+
throw new Error(`CREATOR_SKILL_REGISTRY_ORIGIN_CONFLICT: --registry-origin cannot be combined with --${key}.`);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
return {
|
|
1221
|
+
...flags,
|
|
1222
|
+
'claim-url': `${registryOrigin}/registry/skill/claim`,
|
|
1223
|
+
'registry-url': `${registryOrigin}/registry/skill-events`,
|
|
1224
|
+
'agent-ack-url': `${registryOrigin}/registry/agent-ack`
|
|
1225
|
+
};
|
|
970
1226
|
}
|
|
971
1227
|
|
|
972
1228
|
function configuredRegistryOrigins() {
|
|
@@ -1368,15 +1624,15 @@ async function uploadXiashePackageArtifact(root, flags, manifestResult) {
|
|
|
1368
1624
|
// to the production registry, then verify that origin before any write.
|
|
1369
1625
|
const claimUrl = claimUrlForExistingManifest(flags, registry);
|
|
1370
1626
|
const registryPreflight = await preflightRegistryClaim(claimUrl, flags);
|
|
1371
|
-
const expectedRegistryOrigin =
|
|
1627
|
+
const expectedRegistryOrigin = resolveStoredRegistryOrigin(registry);
|
|
1372
1628
|
const expectedEnvironmentId = normalizeText(registry.environmentId, 160);
|
|
1373
1629
|
if (
|
|
1374
1630
|
(expectedRegistryOrigin && expectedRegistryOrigin !== registryPreflight.registryOrigin) ||
|
|
1375
1631
|
(expectedEnvironmentId && expectedEnvironmentId !== registryPreflight.environmentId)
|
|
1376
1632
|
) {
|
|
1377
1633
|
throw new Error(
|
|
1378
|
-
'
|
|
1379
|
-
'
|
|
1634
|
+
'CREATOR_SKILL_CONNECTION_MISMATCH: this project is linked to a different Skill service. ' +
|
|
1635
|
+
'No package was uploaded.'
|
|
1380
1636
|
);
|
|
1381
1637
|
}
|
|
1382
1638
|
const uploadTicket =
|
|
@@ -1508,16 +1764,47 @@ async function inspectSkill(root, flags = {}) {
|
|
|
1508
1764
|
async function writeManifest(root, flags) {
|
|
1509
1765
|
const inspected = await inspectSkill(root, flags);
|
|
1510
1766
|
const code = normalizeText(flags.code, 80);
|
|
1511
|
-
const
|
|
1767
|
+
const rawExistingRegistry = inspected.registry || {};
|
|
1768
|
+
const savedRegistryOrigin = resolveStoredRegistryOrigin(rawExistingRegistry);
|
|
1769
|
+
const existingRegistry = savedRegistryOrigin && rawExistingRegistry.registryOrigin !== savedRegistryOrigin
|
|
1770
|
+
? { ...rawExistingRegistry, registryOrigin: savedRegistryOrigin }
|
|
1771
|
+
: rawExistingRegistry;
|
|
1772
|
+
const savedEnvironmentId = normalizeText(existingRegistry.environmentId, 160);
|
|
1773
|
+
const requestedRegistryOrigin = registryOriginFromFlags(flags);
|
|
1512
1774
|
const suppliedPublicToken = normalizeText(flags['public-token'], 512);
|
|
1513
|
-
|
|
1775
|
+
const isLegacyXiaSheMigration = inspected.manifestSource === 'legacy_xiashe';
|
|
1776
|
+
if (
|
|
1777
|
+
isLegacyXiaSheMigration &&
|
|
1778
|
+
(existingRegistry.provider !== 'xiashe' || !savedRegistryOrigin)
|
|
1779
|
+
) {
|
|
1780
|
+
throw new Error(
|
|
1781
|
+
'CREATOR_SKILL_LEGACY_CONNECTION_UNVERIFIABLE: this older XiaShe Skill connection cannot be verified safely. ' +
|
|
1782
|
+
'No AgentPie Skill was created or changed. Use the original Skill project folder and refresh its connection before updating.'
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1785
|
+
if (!code && suppliedPublicToken && !explicitClaimUrl(flags) && !savedRegistryOrigin) {
|
|
1514
1786
|
throw new Error(
|
|
1515
1787
|
'CREATOR_SKILL_CLAIM_URL_REQUIRED: a new manifest created from --public-token must include --claim-url. ' +
|
|
1516
1788
|
'Existing manifests resume through their saved registry origin.'
|
|
1517
1789
|
);
|
|
1518
1790
|
}
|
|
1791
|
+
if (!code && !savedRegistryOrigin && !requestedRegistryOrigin) {
|
|
1792
|
+
throw new Error(
|
|
1793
|
+
'CREATOR_SKILL_CONNECTION_UNVERIFIABLE: this local Skill connection cannot be verified safely. ' +
|
|
1794
|
+
'No Skill was created or changed. Add the original Skill project folder and retry.'
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1797
|
+
if (!code && savedRegistryOrigin && requestedRegistryOrigin && savedRegistryOrigin !== requestedRegistryOrigin) {
|
|
1798
|
+
throw new Error('CREATOR_SKILL_CONNECTION_MISMATCH: this project is linked to a different Skill service. No files were changed.');
|
|
1799
|
+
}
|
|
1519
1800
|
const claimUrl = code ? claimUrlForCode(flags) : claimUrlForExistingManifest(flags, existingRegistry);
|
|
1520
|
-
const registryPreflight =
|
|
1801
|
+
const registryPreflight = await preflightRegistryClaim(claimUrl, flags);
|
|
1802
|
+
if (!code && savedRegistryOrigin && savedRegistryOrigin !== registryPreflight.registryOrigin) {
|
|
1803
|
+
throw new Error('CREATOR_SKILL_CONNECTION_MISMATCH: this project is linked to a different Skill service. No files were changed.');
|
|
1804
|
+
}
|
|
1805
|
+
if (!code && savedEnvironmentId && savedEnvironmentId !== registryPreflight.environmentId) {
|
|
1806
|
+
throw new Error('CREATOR_SKILL_CONNECTION_MISMATCH: this project is linked to a different Skill service. No files were changed.');
|
|
1807
|
+
}
|
|
1521
1808
|
const claim = code
|
|
1522
1809
|
? await postJson(
|
|
1523
1810
|
registryPreflight.claimUrl,
|
|
@@ -1547,12 +1834,15 @@ async function writeManifest(root, flags) {
|
|
|
1547
1834
|
const claimEnvironment = normalizeText(claim.registry?.environmentId, 160);
|
|
1548
1835
|
const claimOrigin = normalizeBaseUrl(claim.registry?.registryOrigin);
|
|
1549
1836
|
if (claimEnvironment !== registryPreflight.environmentId || claimOrigin !== registryPreflight.registryOrigin) {
|
|
1550
|
-
throw new Error('
|
|
1837
|
+
throw new Error('CREATOR_SKILL_CONNECTION_MISMATCH: the returned Skill identity does not match this local connection. No package upload was attempted.');
|
|
1551
1838
|
}
|
|
1552
1839
|
}
|
|
1553
1840
|
const publicToken = normalizeText(flags['public-token'] || claim?.publicToken || existingRegistry.publicToken, 512);
|
|
1554
1841
|
if (!publicToken) {
|
|
1555
|
-
throw new Error(
|
|
1842
|
+
throw new Error(
|
|
1843
|
+
'CREATOR_SKILL_CONNECTION_UNVERIFIABLE: this local Skill connection cannot be verified safely. ' +
|
|
1844
|
+
'No Skill was created or changed. Add the original Skill project folder and retry.'
|
|
1845
|
+
);
|
|
1556
1846
|
}
|
|
1557
1847
|
const now = new Date().toISOString();
|
|
1558
1848
|
const claimCreatorProfile = normalizeCreatorProfile(claim?.creatorProfile);
|
|
@@ -1583,9 +1873,9 @@ async function writeManifest(root, flags) {
|
|
|
1583
1873
|
accessMode: normalizeText(flags['access-mode'] || inspected.accessMode || existingRegistry.accessMode, 80) || null,
|
|
1584
1874
|
registry: {
|
|
1585
1875
|
provider: REGISTRY_PROVIDER,
|
|
1586
|
-
protocolVersion: normalizeText(claim?.registry?.protocolVersion || registryPreflight?.protocolVersion, 120) || null,
|
|
1587
|
-
environmentId: normalizeText(claim?.registry?.environmentId || registryPreflight?.environmentId, 160) || null,
|
|
1588
|
-
registryOrigin: normalizeBaseUrl(claim?.registry?.registryOrigin || registryPreflight?.registryOrigin) || null,
|
|
1876
|
+
protocolVersion: normalizeText(claim?.registry?.protocolVersion || registryPreflight?.protocolVersion || existingRegistry.protocolVersion, 120) || null,
|
|
1877
|
+
environmentId: normalizeText(claim?.registry?.environmentId || registryPreflight?.environmentId || existingRegistry.environmentId, 160) || null,
|
|
1878
|
+
registryOrigin: normalizeBaseUrl(claim?.registry?.registryOrigin || registryPreflight?.registryOrigin || existingRegistry.registryOrigin) || null,
|
|
1589
1879
|
skillId: normalizeText(flags['skill-id'] || claim?.skillId || existingRegistry.skillId, 160) || null,
|
|
1590
1880
|
publicToken,
|
|
1591
1881
|
isPaid: Boolean(flags['paid-skill'] || flags['protected-skill'] || inspected.isPaid || existingRegistry.isPaid || existingRegistry.paidAccess),
|
|
@@ -1593,8 +1883,8 @@ async function writeManifest(root, flags) {
|
|
|
1593
1883
|
billingModel: normalizeText(flags['billing-model'] || inspected.billingModel || existingRegistry.billingModel, 80) || null,
|
|
1594
1884
|
monetizationMode: normalizeText(flags['monetization-mode'] || inspected.monetizationMode || existingRegistry.monetizationMode, 80) || null,
|
|
1595
1885
|
accessMode: normalizeText(flags['access-mode'] || inspected.accessMode || existingRegistry.accessMode, 80) || null,
|
|
1596
|
-
registryUrl: normalizeText(flags['registry-url'] || claim?.registryUrl || existingRegistry.registryUrl, 800) ||
|
|
1597
|
-
agentAckUrl: normalizeText(flags['agent-ack-url'] || existingRegistry.agentAckUrl, 800) ||
|
|
1886
|
+
registryUrl: normalizeText(flags['registry-url'] || claim?.registryUrl || existingRegistry.registryUrl, 800) || `${registryPreflight.registryOrigin}/registry/skill-events`,
|
|
1887
|
+
agentAckUrl: normalizeText(flags['agent-ack-url'] || existingRegistry.agentAckUrl, 800) || `${registryPreflight.registryOrigin}/registry/agent-ack`,
|
|
1598
1888
|
creatorCardUrl,
|
|
1599
1889
|
creatorProfile,
|
|
1600
1890
|
eventSchemaVersion: EVENT_SCHEMA_VERSION,
|
|
@@ -1642,7 +1932,7 @@ async function writeManifest(root, flags) {
|
|
|
1642
1932
|
sourceSurface: hub,
|
|
1643
1933
|
skillKey: safeSkillKey(claim?.skillKey || inspected.skillKey),
|
|
1644
1934
|
publicSkillId: normalizeText(flags['skill-id'] || claim?.skillId || existingRegistry.skillId, 160) || null,
|
|
1645
|
-
agentAckUrl: normalizeText(flags['agent-ack-url'] || existingRegistry.agentAckUrl, 800) ||
|
|
1935
|
+
agentAckUrl: normalizeText(flags['agent-ack-url'] || existingRegistry.agentAckUrl, 800) || `${registryPreflight.registryOrigin}/registry/agent-ack`,
|
|
1646
1936
|
creatorCardUrl,
|
|
1647
1937
|
creatorProfile
|
|
1648
1938
|
}
|
|
@@ -1667,6 +1957,7 @@ async function writeManifest(root, flags) {
|
|
|
1667
1957
|
ok: true,
|
|
1668
1958
|
manifestPath,
|
|
1669
1959
|
manifest,
|
|
1960
|
+
adoptedLegacyXiaSheManifest: inspected.manifestSource === 'legacy_xiashe',
|
|
1670
1961
|
claimPackageArtifactUpload: claim?.packageArtifactUpload ?? null,
|
|
1671
1962
|
registryPreflight
|
|
1672
1963
|
};
|
|
@@ -1948,6 +2239,10 @@ function redPublicTrackingContractBlock(inspected) {
|
|
|
1948
2239
|
`- Latest version endpoint: ${updateEndpoint}`,
|
|
1949
2240
|
'- 如果联网可用,首次使用前或每日第一次使用前检查一次。',
|
|
1950
2241
|
'- 如果 latestVersion 与 Current version 不同,先提示用户有新版本,并询问是否更新;未经确认,不要修改本地文件。',
|
|
2242
|
+
...(protectedPaidSkill ? [
|
|
2243
|
+
`- 对这个受保护包,公开 latest version endpoint 只能用于发现更新,不能用于下载。先通过 ${PRODUCT_NAME} Gateway 调用 \`xiashe.check_skill_update\`;用户明确同意后,调用 \`xiashe.get_skill_update\` 并传 \`userConfirmed=true\`。`,
|
|
2244
|
+
'- 更新交付到当前 Agent 的一次性私有地址:先保留旧包备份,在临时目录下载、校验 SHA-256、解压并阅读 SKILL.md,全部成功后再原子替换;任一步失败都保留旧包。已购账户更新不再次收费。'
|
|
2245
|
+
] : []),
|
|
1951
2246
|
'- 如果 endpoint 返回 blocked / paused / disabled,提示当前版本已停用,不要静默继续。',
|
|
1952
2247
|
'- 如果无法联网,继续完成用户任务,并简单说明更新状态未检查。',
|
|
1953
2248
|
'',
|
|
@@ -2000,6 +2295,10 @@ function skillMdRegistryBlock(inspected, hub) {
|
|
|
2000
2295
|
`- Latest version endpoint: ${updateEndpoint}`,
|
|
2001
2296
|
'- When network access is available, check the latest version endpoint before first use or once per day.',
|
|
2002
2297
|
'- If a newer latestVersion is returned, show it and ask the user before changing local files.',
|
|
2298
|
+
...(protectedPaidSkill ? [
|
|
2299
|
+
`- 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\`.`,
|
|
2300
|
+
'- 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.'
|
|
2301
|
+
] : []),
|
|
2003
2302
|
'- If the endpoint returns blocked / paused / disabled, tell the user this installed version is disabled and do not continue silently.',
|
|
2004
2303
|
'- If the network is unavailable, continue the user task and briefly state that update status was not checked.',
|
|
2005
2304
|
'',
|
|
@@ -2100,6 +2399,7 @@ function buildRuntimeYaml(inspected, hub = 'red') {
|
|
|
2100
2399
|
" when: 'before_first_use_or_daily_when_network_available'",
|
|
2101
2400
|
" noNetwork: 'continue_and_state_update_status_unchecked'",
|
|
2102
2401
|
" updateAvailable: 'show_latest_version_and_ask_user_before_changing_files'",
|
|
2402
|
+
` 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')}`,
|
|
2103
2403
|
" blocked: 'tell_user_current_version_is_disabled_and_do_not_continue_silently'",
|
|
2104
2404
|
'creator:',
|
|
2105
2405
|
` displayName: ${yamlString(creatorName)}`,
|
|
@@ -2212,6 +2512,7 @@ async function writePublicProtocolFiles(root, inspected, hub = 'red') {
|
|
|
2212
2512
|
const disclosurePath = path.join(protocolDir, 'REGISTRY_DISCLOSURE.md');
|
|
2213
2513
|
const runtimePath = path.join(protocolDir, 'runtime.yaml');
|
|
2214
2514
|
const registry = inspected.registry || {};
|
|
2515
|
+
const protectedPaidSkill = isProtectedPaidSkill(inspected);
|
|
2215
2516
|
const publicSkillId = registry.skillId || registry.publicSkillId || `<public skill id from ${PRODUCT_NAME} registry>`;
|
|
2216
2517
|
const agentAck = [
|
|
2217
2518
|
agentAckInstructionBlock(inspected, hub),
|
|
@@ -2220,7 +2521,9 @@ async function writePublicProtocolFiles(root, inspected, hub = 'red') {
|
|
|
2220
2521
|
'',
|
|
2221
2522
|
'1. Install the Skill through the target platform official flow.',
|
|
2222
2523
|
`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.`,
|
|
2223
|
-
|
|
2524
|
+
protectedPaidSkill
|
|
2525
|
+
? `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.`
|
|
2526
|
+
: '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.',
|
|
2224
2527
|
'4. Send `installed` once after installation succeeds. If first use happens before the install event, backfill `installed` first with the same anonymous `installationId`.',
|
|
2225
2528
|
'5. Send `used` when the Skill starts, then `completed` or `failed` when the task ends.',
|
|
2226
2529
|
'6. End every completed Skill turn with the localized footer from `runtime.yaml`. Use the recommendation slot only when the Ack response returns one.',
|
|
@@ -2602,7 +2905,9 @@ async function writeUnifiedHandoff(root, args) {
|
|
|
2602
2905
|
`Event schema: ${registry.eventSchemaVersion || EVENT_SCHEMA_VERSION}`,
|
|
2603
2906
|
'',
|
|
2604
2907
|
`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.`,
|
|
2605
|
-
|
|
2908
|
+
protectedPaidSkill
|
|
2909
|
+
? `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.`
|
|
2910
|
+
: `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.`,
|
|
2606
2911
|
'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.',
|
|
2607
2912
|
`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.`,
|
|
2608
2913
|
'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.',
|
|
@@ -2895,12 +3200,11 @@ function doctorCheck(id, label, status, message, fix = '') {
|
|
|
2895
3200
|
|
|
2896
3201
|
async function runDoctor(root, flags) {
|
|
2897
3202
|
const inspected = await inspectSkill(root, flags);
|
|
3203
|
+
const manifestLookup = await readSkillManifest(inspected.root, flags);
|
|
2898
3204
|
const hub = normalizeHub(flags.hub || flags.to || 'generic') || 'generic';
|
|
2899
3205
|
const redHub = hub === 'red';
|
|
2900
3206
|
const skillMdPath = path.join(inspected.root, 'SKILL.md');
|
|
2901
3207
|
const readmePath = await findReadmePath(inspected.root);
|
|
2902
|
-
const manifestPath = path.join(inspected.root, MANIFEST_FILE);
|
|
2903
|
-
const localManifestPath = path.join(inspected.root, WORK_DIR, MANIFEST_FILE);
|
|
2904
3208
|
const disclosurePath = path.join(inspected.root, WORK_DIR, 'REGISTRY_DISCLOSURE.md');
|
|
2905
3209
|
const publicAgentAckPath = path.join(inspected.root, PUBLIC_PROTOCOL_DIR, 'AGENT_ACK.md');
|
|
2906
3210
|
const publicDisclosurePath = path.join(inspected.root, PUBLIC_PROTOCOL_DIR, 'REGISTRY_DISCLOSURE.md');
|
|
@@ -2926,9 +3230,7 @@ async function runDoctor(root, flags) {
|
|
|
2926
3230
|
scannedCallbackText += `\n${text}`;
|
|
2927
3231
|
}
|
|
2928
3232
|
}
|
|
2929
|
-
const manifest = inspected.registry
|
|
2930
|
-
? await readJsonFile(manifestPath).catch(() => null) || await readJsonFile(localManifestPath).catch(() => null)
|
|
2931
|
-
: null;
|
|
3233
|
+
const manifest = inspected.registry ? manifestLookup.manifest : null;
|
|
2932
3234
|
const textBundle = `${skillMd}\n${readme}`;
|
|
2933
3235
|
const hasManifest = Boolean(manifest);
|
|
2934
3236
|
const registry = manifest?.registry || inspected.registry || {};
|
|
@@ -2949,7 +3251,15 @@ async function runDoctor(root, flags) {
|
|
|
2949
3251
|
? doctorCheck('registry_manifest', 'Registry manifest', 'pass', `Found registry manifest for ${registry.provider || REGISTRY_PROVIDER}.`)
|
|
2950
3252
|
: redHub
|
|
2951
3253
|
? 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.`)
|
|
2952
|
-
: doctorCheck(
|
|
3254
|
+
: doctorCheck(
|
|
3255
|
+
'registry_manifest',
|
|
3256
|
+
'Registry manifest',
|
|
3257
|
+
'fail',
|
|
3258
|
+
CAN_ADOPT_LEGACY_XIASHE_MANIFEST
|
|
3259
|
+
? `Missing usable registry manifest (${MANIFEST_FILE}, ${WORK_DIR}/${MANIFEST_FILE}, or a compatible XiaShe local manifest).`
|
|
3260
|
+
: `Missing usable registry manifest (${MANIFEST_FILE} or ${WORK_DIR}/${MANIFEST_FILE}).`,
|
|
3261
|
+
`Run ${COMMAND_NAME} setup . --code <dashboard-code> or ${COMMAND_NAME} attach . --public-token <token> --skill-id <id>.`
|
|
3262
|
+
),
|
|
2953
3263
|
registryBlockPresent(skillMd)
|
|
2954
3264
|
? doctorCheck('registry_block', 'Registry block', 'pass', 'SKILL.md contains the explicit registry disclosure block.')
|
|
2955
3265
|
: redHub
|
|
@@ -3138,7 +3448,8 @@ function formatVerify(result) {
|
|
|
3138
3448
|
|
|
3139
3449
|
async function setupAgentWorkflow(root, flags) {
|
|
3140
3450
|
const hubs = setupHubsFromFlags(flags);
|
|
3141
|
-
const
|
|
3451
|
+
const rootResolution = await resolveExistingSkillRoot(root, flags);
|
|
3452
|
+
const absoluteRoot = rootResolution.root;
|
|
3142
3453
|
const outDir = path.resolve(flags['out-dir'] || path.join(absoluteRoot, WORK_DIR));
|
|
3143
3454
|
await mkdir(outDir, { recursive: true });
|
|
3144
3455
|
const manifestResult = await writeManifest(absoluteRoot, {
|
|
@@ -3236,6 +3547,8 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
3236
3547
|
return {
|
|
3237
3548
|
ok: true,
|
|
3238
3549
|
hubs,
|
|
3550
|
+
linkedSkillDiscovered: rootResolution.discovered,
|
|
3551
|
+
adoptedLegacyXiaSheManifest: manifestResult.adoptedLegacyXiaSheManifest,
|
|
3239
3552
|
manifestPath: manifestResult.manifestPath,
|
|
3240
3553
|
handoffPath,
|
|
3241
3554
|
promptPaths: promptResults,
|
|
@@ -3250,6 +3563,12 @@ async function setupAgentWorkflow(root, flags) {
|
|
|
3250
3563
|
warnings,
|
|
3251
3564
|
publicProtocol,
|
|
3252
3565
|
next: [
|
|
3566
|
+
rootResolution.discovered
|
|
3567
|
+
? 'An existing local Skill connection was found automatically; no new registry entry was created.'
|
|
3568
|
+
: null,
|
|
3569
|
+
manifestResult.adoptedLegacyXiaSheManifest
|
|
3570
|
+
? 'A compatible XiaShe local connection was reused safely; a separate AgentPie local connection was created.'
|
|
3571
|
+
: null,
|
|
3253
3572
|
firstPartyPackageArtifact?.skipped
|
|
3254
3573
|
? `${PRODUCT_NAME} complete package upload was skipped (${firstPartyPackageArtifact.reason}).`
|
|
3255
3574
|
: `${PRODUCT_NAME} complete package uploaded: ${path.relative(absoluteRoot, firstPartyPackageArtifact.artifactPath)} (${firstPartyPackageArtifact.publishStatus || 'queued'}).`,
|
|
@@ -3287,8 +3606,9 @@ async function main() {
|
|
|
3287
3606
|
if (commandArgs[0] && !commandArgs[0].startsWith('--')) {
|
|
3288
3607
|
root = commandArgs.shift();
|
|
3289
3608
|
}
|
|
3290
|
-
|
|
3609
|
+
let flags = parseArgs(commandArgs);
|
|
3291
3610
|
try {
|
|
3611
|
+
flags = applyRegistryOriginFlags(flags);
|
|
3292
3612
|
if (command === 'inspect') {
|
|
3293
3613
|
const result = await inspectSkill(root, flags);
|
|
3294
3614
|
print(result, flags.json);
|
|
@@ -3312,10 +3632,8 @@ async function main() {
|
|
|
3312
3632
|
flags
|
|
3313
3633
|
);
|
|
3314
3634
|
print(flags.json ? result : [
|
|
3315
|
-
`${PRODUCT_NAME}
|
|
3316
|
-
|
|
3317
|
-
`Registry: ${result.registryOrigin}`,
|
|
3318
|
-
`Protocol: ${result.protocolVersion}`
|
|
3635
|
+
`${PRODUCT_NAME} preflight passed.`,
|
|
3636
|
+
'Connection verified.'
|
|
3319
3637
|
].join('\n'), flags.json);
|
|
3320
3638
|
return;
|
|
3321
3639
|
}
|