@xiashe/cli 0.1.5 → 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.
Files changed (2) hide show
  1. package/bin/xiashe.mjs +243 -1
  2. 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 {
@@ -51,7 +55,6 @@ Usage:
51
55
  ${c} whoami
52
56
 
53
57
  ${c} config path|get|set <key> <value>|use <profile>
54
- ${c} env use ${PRODUCT_ENV}
55
58
 
56
59
  ${c} connections list
57
60
  ${c} claw pair --connection <id-or-name>
@@ -63,6 +66,13 @@ Usage:
63
66
  ${c} skills install <skill> --connection <id-or-name> [--watch]
64
67
  ${c} skills jobs --connection <id-or-name>
65
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>
66
76
 
67
77
  ${c} agent issue [--name <name>] [--kind <kind>] [--json]
68
78
  ${c} agent connect --token <token> [--name <name>] [--kind <kind>] [--webhook-url <https-url>] [--webhook-token <secret>]
@@ -539,6 +549,10 @@ async function cmdSkills(sub, args, global) {
539
549
  ]);
540
550
  return;
541
551
  }
552
+ if (sub === 'publish' || sub === 'submission' || sub === 'submissions') {
553
+ await cmdSkillPublish(flags, client, global);
554
+ return;
555
+ }
542
556
  if (sub === 'claim' || sub === 'use') {
543
557
  await claimSkill(flags, client, global);
544
558
  return;
@@ -546,6 +560,106 @@ async function cmdSkills(sub, args, global) {
546
560
  fail(`Unknown skills command: ${sub}`, EXIT_USAGE);
547
561
  }
548
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
+
549
663
  async function watchSkillJob(client, connectionId, jobId, global) {
550
664
  const deadline = Date.now() + Number(global.watchTimeoutMs || 180_000);
551
665
  let latest = null;
@@ -1028,6 +1142,134 @@ function parseCsv(input) {
1028
1142
  .filter(Boolean);
1029
1143
  }
1030
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
+
1031
1273
  function osName() {
1032
1274
  return `${osPlatform()}-${process.arch}`;
1033
1275
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiashe/cli",
3
- "version": "0.1.5",
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.6"
13
+ "@xiashe/sdk": "0.1.7"
14
14
  },
15
15
  "engines": {
16
16
  "node": ">=20"