nextclaw 0.9.27 → 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.
Files changed (2) hide show
  1. package/dist/cli/index.js +70 -11
  2. package/package.json +2 -2
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
  }
@@ -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)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextclaw",
3
- "version": "0.9.27",
3
+ "version": "0.9.28",
4
4
  "description": "Lightweight personal AI assistant with CLI, multi-provider routing, and channel integrations.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -39,8 +39,8 @@
39
39
  "chokidar": "^3.6.0",
40
40
  "commander": "^12.1.0",
41
41
  "@nextclaw/core": "0.7.7",
42
- "@nextclaw/server": "0.6.13",
43
42
  "@nextclaw/runtime": "0.1.6",
43
+ "@nextclaw/server": "0.6.13",
44
44
  "@nextclaw/openclaw-compat": "0.2.6"
45
45
  },
46
46
  "devDependencies": {