@xiashe/cli 0.1.6 → 0.1.7
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/bin/xiashe.mjs +243 -0
- package/package.json +2 -2
package/bin/xiashe.mjs
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
3
6
|
import os from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
4
8
|
|
|
5
9
|
let sdk;
|
|
6
10
|
try {
|
|
@@ -62,6 +66,13 @@ Usage:
|
|
|
62
66
|
${c} skills install <skill> --connection <id-or-name> [--watch]
|
|
63
67
|
${c} skills jobs --connection <id-or-name>
|
|
64
68
|
${c} skills claim <one-time-token>
|
|
69
|
+
${c} skills publish draft [skill-dir] --name <name> --entry <entry> --install-id <id> --source <source>
|
|
70
|
+
${c} skills publish update <submission-id> [skill-dir] [--name <name>] [--entry <entry>]
|
|
71
|
+
${c} skills publish doctor <submission-id>
|
|
72
|
+
${c} skills publish submit <submission-id>
|
|
73
|
+
${c} skills publish list
|
|
74
|
+
${c} skills publish review <submission-id> --status <in_review|changes_requested|approved|rejected> [--notes <text>]
|
|
75
|
+
${c} skills publish release <submission-id>
|
|
65
76
|
|
|
66
77
|
${c} agent issue [--name <name>] [--kind <kind>] [--json]
|
|
67
78
|
${c} agent connect --token <token> [--name <name>] [--kind <kind>] [--webhook-url <https-url>] [--webhook-token <secret>]
|
|
@@ -538,6 +549,10 @@ async function cmdSkills(sub, args, global) {
|
|
|
538
549
|
]);
|
|
539
550
|
return;
|
|
540
551
|
}
|
|
552
|
+
if (sub === 'publish' || sub === 'submission' || sub === 'submissions') {
|
|
553
|
+
await cmdSkillPublish(flags, client, global);
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
541
556
|
if (sub === 'claim' || sub === 'use') {
|
|
542
557
|
await claimSkill(flags, client, global);
|
|
543
558
|
return;
|
|
@@ -545,6 +560,106 @@ async function cmdSkills(sub, args, global) {
|
|
|
545
560
|
fail(`Unknown skills command: ${sub}`, EXIT_USAGE);
|
|
546
561
|
}
|
|
547
562
|
|
|
563
|
+
async function cmdSkillPublish(flags, client, global) {
|
|
564
|
+
const action = flags._[0] || 'list';
|
|
565
|
+
if (action === 'list' || action === 'ls') {
|
|
566
|
+
const rows = await client.listSkillSubmissions(Number(flags.limit || 20));
|
|
567
|
+
if (global.json) {
|
|
568
|
+
print(rows, global);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
table(rows, [
|
|
572
|
+
{ label: 'id', value: (row) => row._id },
|
|
573
|
+
{ label: 'status', value: (row) => row.status },
|
|
574
|
+
{ label: 'market', value: (row) => row.market },
|
|
575
|
+
{ label: 'name', value: (row) => row.name },
|
|
576
|
+
{ label: 'updated', value: (row) => nowIso(row.updatedAt).slice(0, 19) }
|
|
577
|
+
]);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
if (action === 'draft' || action === 'create') {
|
|
581
|
+
const skillDir = flags._[1] && !String(flags._[1]).startsWith('--') ? flags._[1] : '.';
|
|
582
|
+
const draft = await inferPublishDraft(skillDir, flags, client);
|
|
583
|
+
const result = await client.createSkillSubmissionDraft(draft);
|
|
584
|
+
if (global.json) {
|
|
585
|
+
print({ ok: true, ...result, draft }, global);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
print(
|
|
589
|
+
[
|
|
590
|
+
`Created Skill submission draft: ${result.submissionId}`,
|
|
591
|
+
`Name: ${draft.name}`,
|
|
592
|
+
`Market: ${draft.market}`,
|
|
593
|
+
`Source fingerprint: ${draft.packageSha256}`,
|
|
594
|
+
'',
|
|
595
|
+
`Next: ${COMMAND_NAME} skills publish doctor ${result.submissionId}`,
|
|
596
|
+
`Then: ${COMMAND_NAME} skills publish submit ${result.submissionId}`
|
|
597
|
+
].join('\n'),
|
|
598
|
+
global
|
|
599
|
+
);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
if (action === 'update') {
|
|
603
|
+
const submissionId = requireArg(flags._[1], 'submission-id');
|
|
604
|
+
const skillDir = flags._[2] && !String(flags._[2]).startsWith('--') ? flags._[2] : '.';
|
|
605
|
+
const patch = await inferPublishDraft(skillDir, flags, client);
|
|
606
|
+
const result = await client.updateSkillSubmissionDraft(submissionId, patch);
|
|
607
|
+
print(global.json ? { ok: true, ...result, patch } : `Updated Skill submission draft: ${result.submissionId}`, global);
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
if (action === 'doctor' || action === 'check') {
|
|
611
|
+
const submissionId = requireArg(flags._[1], 'submission-id');
|
|
612
|
+
const report = await client.runSkillSubmissionSafetyCheck(submissionId);
|
|
613
|
+
printSafetyReport(report, global);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
if (action === 'submit') {
|
|
617
|
+
const submissionId = requireArg(flags._[1], 'submission-id');
|
|
618
|
+
const result = await client.submitSkillSubmissionForReview(submissionId);
|
|
619
|
+
if (global.json) {
|
|
620
|
+
print(result, global);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
print(`Submitted Skill for review: ${submissionId}`, global);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (action === 'admin-list') {
|
|
627
|
+
const rows = await client.adminListSkillSubmissions({ status: flags.status || undefined, limit: Number(flags.limit || 50) });
|
|
628
|
+
if (global.json) {
|
|
629
|
+
print(rows, global);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
table(rows, [
|
|
633
|
+
{ label: 'id', value: (row) => row._id },
|
|
634
|
+
{ label: 'status', value: (row) => row.status },
|
|
635
|
+
{ label: 'market', value: (row) => row.market },
|
|
636
|
+
{ label: 'name', value: (row) => row.name },
|
|
637
|
+
{ label: 'updated', value: (row) => nowIso(row.updatedAt).slice(0, 19) }
|
|
638
|
+
]);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
if (action === 'review' || action === 'approve' || action === 'reject' || action === 'changes') {
|
|
642
|
+
const submissionId = requireArg(flags._[1], 'submission-id');
|
|
643
|
+
const status = action === 'approve'
|
|
644
|
+
? 'approved'
|
|
645
|
+
: action === 'reject'
|
|
646
|
+
? 'rejected'
|
|
647
|
+
: action === 'changes'
|
|
648
|
+
? 'changes_requested'
|
|
649
|
+
: requireArg(flags.status, '--status');
|
|
650
|
+
const result = await client.adminReviewSkillSubmission(submissionId, status, flags.notes || flags.reviewNotes || '');
|
|
651
|
+
print(result, global);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (action === 'release' || action === 'publish') {
|
|
655
|
+
const submissionId = requireArg(flags._[1], 'submission-id');
|
|
656
|
+
const result = await client.publishSkillSubmission(submissionId, flags.status || 'released');
|
|
657
|
+
print(result, global);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
fail(`Unknown skills publish command: ${action}`, EXIT_USAGE);
|
|
661
|
+
}
|
|
662
|
+
|
|
548
663
|
async function watchSkillJob(client, connectionId, jobId, global) {
|
|
549
664
|
const deadline = Date.now() + Number(global.watchTimeoutMs || 180_000);
|
|
550
665
|
let latest = null;
|
|
@@ -1027,6 +1142,134 @@ function parseCsv(input) {
|
|
|
1027
1142
|
.filter(Boolean);
|
|
1028
1143
|
}
|
|
1029
1144
|
|
|
1145
|
+
const PUBLISH_IGNORE = new Set(['.git', 'node_modules', '.DS_Store', '.env', '.env.local', 'dist', 'build', 'coverage']);
|
|
1146
|
+
const PUBLISH_MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
1147
|
+
|
|
1148
|
+
async function readSmallText(filePath) {
|
|
1149
|
+
try {
|
|
1150
|
+
const info = await stat(filePath);
|
|
1151
|
+
if (!info.isFile() || info.size > PUBLISH_MAX_FILE_BYTES) return '';
|
|
1152
|
+
return await readFile(filePath, 'utf8');
|
|
1153
|
+
} catch {
|
|
1154
|
+
return '';
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
async function readJson(filePath) {
|
|
1159
|
+
const text = await readSmallText(filePath);
|
|
1160
|
+
if (!text) return null;
|
|
1161
|
+
try {
|
|
1162
|
+
return JSON.parse(text);
|
|
1163
|
+
} catch {
|
|
1164
|
+
return null;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
async function walkPublishFiles(root, current = root, out = []) {
|
|
1169
|
+
let entries = [];
|
|
1170
|
+
try {
|
|
1171
|
+
entries = await readdir(current, { withFileTypes: true });
|
|
1172
|
+
} catch {
|
|
1173
|
+
return out;
|
|
1174
|
+
}
|
|
1175
|
+
for (const entry of entries) {
|
|
1176
|
+
if (PUBLISH_IGNORE.has(entry.name) || entry.name.endsWith('.log') || entry.name.endsWith('.tmp')) continue;
|
|
1177
|
+
const absolute = path.join(current, entry.name);
|
|
1178
|
+
const relative = path.relative(root, absolute).replaceAll(path.sep, '/');
|
|
1179
|
+
if (entry.isDirectory()) {
|
|
1180
|
+
await walkPublishFiles(root, absolute, out);
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1183
|
+
if (!entry.isFile()) continue;
|
|
1184
|
+
const info = await stat(absolute).catch(() => null);
|
|
1185
|
+
if (!info || info.size > PUBLISH_MAX_FILE_BYTES) continue;
|
|
1186
|
+
out.push({ absolute, relative, size: info.size });
|
|
1187
|
+
}
|
|
1188
|
+
return out.sort((a, b) => a.relative.localeCompare(b.relative));
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
async function hashPublishDirectory(root) {
|
|
1192
|
+
const files = await walkPublishFiles(root);
|
|
1193
|
+
const hash = createHash('sha256');
|
|
1194
|
+
for (const file of files) {
|
|
1195
|
+
const bytes = await readFile(file.absolute);
|
|
1196
|
+
hash.update(file.relative);
|
|
1197
|
+
hash.update('\0');
|
|
1198
|
+
hash.update(bytes);
|
|
1199
|
+
hash.update('\0');
|
|
1200
|
+
}
|
|
1201
|
+
return { sha256: hash.digest('hex'), fileCount: files.length, totalBytes: files.reduce((sum, file) => sum + file.size, 0) };
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
function firstHeading(text) {
|
|
1205
|
+
return /^#\s+(.+)$/m.exec(text || '')?.[1]?.trim() || '';
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
function firstParagraph(text) {
|
|
1209
|
+
return String(text || '')
|
|
1210
|
+
.split(/\n{2,}/)
|
|
1211
|
+
.map((item) => item.trim())
|
|
1212
|
+
.find((item) => item && !item.startsWith('#') && !item.startsWith('---')) || '';
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
async function inferPublishDraft(skillDir, flags, client) {
|
|
1216
|
+
const root = path.resolve(skillDir || '.');
|
|
1217
|
+
const info = await stat(root).catch(() => null);
|
|
1218
|
+
if (!info?.isDirectory()) {
|
|
1219
|
+
throw new Error(`Skill directory not found: ${root}`);
|
|
1220
|
+
}
|
|
1221
|
+
const packageJson = await readJson(path.join(root, 'package.json'));
|
|
1222
|
+
const skillMd = await readSmallText(path.join(root, 'SKILL.md'));
|
|
1223
|
+
const readme = await readSmallText(path.join(root, 'README.md'));
|
|
1224
|
+
const text = `${skillMd}\n\n${readme}`;
|
|
1225
|
+
const digest = await hashPublishDirectory(root);
|
|
1226
|
+
const name = flags.name || packageJson?.name || firstHeading(text) || path.basename(root);
|
|
1227
|
+
const intro = flags.intro || flags.summary || packageJson?.description || firstParagraph(text);
|
|
1228
|
+
const entrypoint =
|
|
1229
|
+
flags.entry ||
|
|
1230
|
+
flags.entrypoint ||
|
|
1231
|
+
packageJson?.bin && (typeof packageJson.bin === 'string' ? packageJson.bin : Object.values(packageJson.bin)[0]) ||
|
|
1232
|
+
packageJson?.main ||
|
|
1233
|
+
'SKILL.md';
|
|
1234
|
+
const source = flags.source || (COMMAND_NAME === 'agentpie' ? 'agentpie' : 'xiashe');
|
|
1235
|
+
const installId = flags['install-id'] || flags.installId || packageJson?.name || path.basename(root);
|
|
1236
|
+
return {
|
|
1237
|
+
market: flags.market || client.profile.market || (COMMAND_NAME === 'agentpie' ? 'intl' : 'cn'),
|
|
1238
|
+
name,
|
|
1239
|
+
summary: intro,
|
|
1240
|
+
summaryZh: flags['summary-zh'] || undefined,
|
|
1241
|
+
description: flags.description || intro,
|
|
1242
|
+
descriptionZh: flags['description-zh'] || undefined,
|
|
1243
|
+
entrypoint,
|
|
1244
|
+
installId,
|
|
1245
|
+
source,
|
|
1246
|
+
sourceUrl: flags['source-url'] || flags.repo || undefined,
|
|
1247
|
+
iconImageUrl: flags['icon-url'] || undefined,
|
|
1248
|
+
coverImageUrl: flags['cover-url'] || undefined,
|
|
1249
|
+
previewImageUrls: parseCsv(flags['preview-url'] || flags['preview-urls']),
|
|
1250
|
+
videoUrls: parseCsv(flags['video-url'] || flags['video-urls']),
|
|
1251
|
+
category: flags.category || undefined,
|
|
1252
|
+
tags: parseCsv(flags.tags),
|
|
1253
|
+
registryEntryId: flags['registry-entry'] || flags.registryEntryId || undefined,
|
|
1254
|
+
packageSha256: digest.sha256
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function printSafetyReport(report, global) {
|
|
1259
|
+
if (global.json) {
|
|
1260
|
+
print(report, global);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
const lines = [
|
|
1264
|
+
`Safety: ${report.status} (${report.score}/100)`,
|
|
1265
|
+
''
|
|
1266
|
+
];
|
|
1267
|
+
for (const check of report.checks || []) {
|
|
1268
|
+
lines.push(`${String(check.status).toUpperCase()} ${check.label}: ${check.message}`);
|
|
1269
|
+
}
|
|
1270
|
+
print(lines.join('\n'), global);
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1030
1273
|
function osName() {
|
|
1031
1274
|
return `${osPlatform()}-${process.arch}`;
|
|
1032
1275
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiashe/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"xiashe": "bin/xiashe.mjs"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"README.md"
|
|
11
11
|
],
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@xiashe/sdk": "0.1.
|
|
13
|
+
"@xiashe/sdk": "0.1.7"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=20"
|