nextclaw 0.9.26 → 0.9.28
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/dist/cli/index.js +81 -12
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -199,20 +199,20 @@ async function installMarketplaceSkill(options) {
|
|
|
199
199
|
const dirName = options.dir?.trim() || "skills";
|
|
200
200
|
const destinationDir = isAbsolute(dirName) ? resolve2(dirName, slug) : resolve2(workdir, dirName, slug);
|
|
201
201
|
const skillFile = join(destinationDir, "SKILL.md");
|
|
202
|
-
if (!options.force && existsSync2(destinationDir)) {
|
|
203
|
-
if (existsSync2(skillFile)) {
|
|
204
|
-
return {
|
|
205
|
-
slug,
|
|
206
|
-
destinationDir,
|
|
207
|
-
alreadyInstalled: true,
|
|
208
|
-
source: "marketplace"
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
throw new Error(`Skill directory already exists: ${destinationDir} (use --force)`);
|
|
212
|
-
}
|
|
213
202
|
const apiBase = resolveMarketplaceApiBase(options.apiBaseUrl);
|
|
214
203
|
const item = await fetchMarketplaceSkillItem(apiBase, slug);
|
|
215
204
|
if (item.install.kind === "builtin") {
|
|
205
|
+
if (!options.force && existsSync2(destinationDir)) {
|
|
206
|
+
if (existsSync2(skillFile)) {
|
|
207
|
+
return {
|
|
208
|
+
slug,
|
|
209
|
+
destinationDir,
|
|
210
|
+
alreadyInstalled: true,
|
|
211
|
+
source: "builtin"
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
throw new Error(`Skill directory already exists: ${destinationDir} (use --force)`);
|
|
215
|
+
}
|
|
216
216
|
if (existsSync2(destinationDir) && options.force) {
|
|
217
217
|
rmSync2(destinationDir, { recursive: true, force: true });
|
|
218
218
|
}
|
|
@@ -224,6 +224,22 @@ async function installMarketplaceSkill(options) {
|
|
|
224
224
|
};
|
|
225
225
|
}
|
|
226
226
|
const filesPayload = await fetchMarketplaceSkillFiles(apiBase, slug);
|
|
227
|
+
if (!options.force && existsSync2(destinationDir)) {
|
|
228
|
+
const existingDirState = inspectMarketplaceSkillDirectory(destinationDir, filesPayload.files);
|
|
229
|
+
if (existingDirState === "installed") {
|
|
230
|
+
return {
|
|
231
|
+
slug,
|
|
232
|
+
destinationDir,
|
|
233
|
+
alreadyInstalled: true,
|
|
234
|
+
source: "marketplace"
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (existingDirState === "recoverable") {
|
|
238
|
+
rmSync2(destinationDir, { recursive: true, force: true });
|
|
239
|
+
} else {
|
|
240
|
+
throw new Error(`Skill directory already exists: ${destinationDir} (use --force)`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
227
243
|
if (existsSync2(destinationDir) && options.force) {
|
|
228
244
|
rmSync2(destinationDir, { recursive: true, force: true });
|
|
229
245
|
}
|
|
@@ -235,7 +251,7 @@ async function installMarketplaceSkill(options) {
|
|
|
235
251
|
throw new Error(`Invalid marketplace file path: ${file.path}`);
|
|
236
252
|
}
|
|
237
253
|
mkdirSync2(dirname(targetPath), { recursive: true });
|
|
238
|
-
const bytes = await fetchMarketplaceSkillFileBlob(apiBase, slug, file);
|
|
254
|
+
const bytes = file.contentBase64 ? decodeMarketplaceFileContent(file.path, file.contentBase64) : await fetchMarketplaceSkillFileBlob(apiBase, slug, file);
|
|
239
255
|
writeFileSync2(targetPath, bytes);
|
|
240
256
|
}
|
|
241
257
|
if (!existsSync2(join(destinationDir, "SKILL.md"))) {
|
|
@@ -247,6 +263,49 @@ async function installMarketplaceSkill(options) {
|
|
|
247
263
|
source: "marketplace"
|
|
248
264
|
};
|
|
249
265
|
}
|
|
266
|
+
function inspectMarketplaceSkillDirectory(destinationDir, files) {
|
|
267
|
+
if (existsSync2(join(destinationDir, "SKILL.md"))) {
|
|
268
|
+
return "installed";
|
|
269
|
+
}
|
|
270
|
+
const discoveredFiles = collectRelativeFiles(destinationDir);
|
|
271
|
+
if (discoveredFiles === null) {
|
|
272
|
+
return "conflict";
|
|
273
|
+
}
|
|
274
|
+
const relevantFiles = discoveredFiles.filter((file) => !isIgnorableMarketplaceResidue(file));
|
|
275
|
+
if (relevantFiles.length === 0) {
|
|
276
|
+
return "recoverable";
|
|
277
|
+
}
|
|
278
|
+
const manifestPaths = new Set(files.map((file) => normalizeMarketplaceRelativePath(file.path)));
|
|
279
|
+
return relevantFiles.every((file) => manifestPaths.has(normalizeMarketplaceRelativePath(file))) ? "recoverable" : "conflict";
|
|
280
|
+
}
|
|
281
|
+
function collectRelativeFiles(rootDir) {
|
|
282
|
+
const output = [];
|
|
283
|
+
const walk = (dir) => {
|
|
284
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
285
|
+
for (const entry of entries) {
|
|
286
|
+
const absolute = join(dir, entry.name);
|
|
287
|
+
if (entry.isDirectory()) {
|
|
288
|
+
if (!walk(absolute)) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (!entry.isFile()) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
const relativePath = relative(rootDir, absolute);
|
|
297
|
+
output.push(normalizeMarketplaceRelativePath(relativePath));
|
|
298
|
+
}
|
|
299
|
+
return true;
|
|
300
|
+
};
|
|
301
|
+
return walk(rootDir) ? output : null;
|
|
302
|
+
}
|
|
303
|
+
function normalizeMarketplaceRelativePath(path) {
|
|
304
|
+
return path.replace(/\\/g, "/");
|
|
305
|
+
}
|
|
306
|
+
function isIgnorableMarketplaceResidue(path) {
|
|
307
|
+
return path === ".DS_Store";
|
|
308
|
+
}
|
|
250
309
|
async function publishMarketplaceSkill(options) {
|
|
251
310
|
const skillDir = resolve2(options.skillDir);
|
|
252
311
|
if (!existsSync2(skillDir)) {
|
|
@@ -376,6 +435,9 @@ async function fetchMarketplaceSkillFiles(apiBase, slug) {
|
|
|
376
435
|
if (typeof entry.downloadPath === "string" && entry.downloadPath.trim().length > 0) {
|
|
377
436
|
normalized.downloadPath = entry.downloadPath.trim();
|
|
378
437
|
}
|
|
438
|
+
if (typeof entry.contentBase64 === "string" && entry.contentBase64.trim().length > 0) {
|
|
439
|
+
normalized.contentBase64 = entry.contentBase64.trim();
|
|
440
|
+
}
|
|
379
441
|
return normalized;
|
|
380
442
|
});
|
|
381
443
|
return { files };
|
|
@@ -394,6 +456,13 @@ async function fetchMarketplaceSkillFileBlob(apiBase, slug, file) {
|
|
|
394
456
|
const arrayBuffer = await response.arrayBuffer();
|
|
395
457
|
return Buffer.from(arrayBuffer);
|
|
396
458
|
}
|
|
459
|
+
function decodeMarketplaceFileContent(path, contentBase64) {
|
|
460
|
+
const normalized = contentBase64.replace(/\s+/g, "");
|
|
461
|
+
if (!normalized || normalized.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(normalized)) {
|
|
462
|
+
throw new Error(`Invalid marketplace file contentBase64 for path: ${path}`);
|
|
463
|
+
}
|
|
464
|
+
return Buffer.from(normalized, "base64");
|
|
465
|
+
}
|
|
397
466
|
function resolveSkillFileDownloadUrl(apiBase, slug, file) {
|
|
398
467
|
const fallback = `${apiBase}/api/v1/skills/items/${encodeURIComponent(slug)}/files/blob?path=${encodeURIComponent(file.path)}`;
|
|
399
468
|
if (!file.downloadPath) {
|