wave-agent-sdk 0.17.4 → 0.17.6

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 (42) hide show
  1. package/dist/managers/MemoryRuleManager.d.ts.map +1 -1
  2. package/dist/managers/MemoryRuleManager.js +30 -13
  3. package/dist/managers/hookManager.d.ts.map +1 -1
  4. package/dist/managers/hookManager.js +3 -1
  5. package/dist/managers/lspManager.d.ts.map +1 -1
  6. package/dist/managers/lspManager.js +12 -4
  7. package/dist/managers/mcpManager.d.ts.map +1 -1
  8. package/dist/managers/mcpManager.js +13 -6
  9. package/dist/managers/skillManager.d.ts +3 -0
  10. package/dist/managers/skillManager.d.ts.map +1 -1
  11. package/dist/managers/skillManager.js +69 -54
  12. package/dist/services/MarketplaceService.d.ts +15 -0
  13. package/dist/services/MarketplaceService.d.ts.map +1 -1
  14. package/dist/services/MarketplaceService.js +95 -6
  15. package/dist/services/memory.d.ts.map +1 -1
  16. package/dist/services/memory.js +39 -5
  17. package/dist/services/pluginLoader.d.ts.map +1 -1
  18. package/dist/services/pluginLoader.js +30 -7
  19. package/dist/types/marketplace.d.ts +1 -0
  20. package/dist/types/marketplace.d.ts.map +1 -1
  21. package/dist/types/skills.d.ts +1 -0
  22. package/dist/types/skills.d.ts.map +1 -1
  23. package/dist/utils/customCommands.d.ts.map +1 -1
  24. package/dist/utils/customCommands.js +11 -9
  25. package/dist/utils/skillParser.d.ts.map +1 -1
  26. package/dist/utils/skillParser.js +3 -1
  27. package/dist/utils/subagentParser.d.ts.map +1 -1
  28. package/dist/utils/subagentParser.js +18 -7
  29. package/package.json +1 -1
  30. package/src/managers/MemoryRuleManager.ts +29 -14
  31. package/src/managers/hookManager.ts +6 -1
  32. package/src/managers/lspManager.ts +23 -5
  33. package/src/managers/mcpManager.ts +24 -7
  34. package/src/managers/skillManager.ts +90 -57
  35. package/src/services/MarketplaceService.ts +152 -6
  36. package/src/services/memory.ts +43 -6
  37. package/src/services/pluginLoader.ts +35 -7
  38. package/src/types/marketplace.ts +1 -0
  39. package/src/types/skills.ts +1 -0
  40. package/src/utils/customCommands.ts +17 -12
  41. package/src/utils/skillParser.ts +3 -1
  42. package/src/utils/subagentParser.ts +22 -8
@@ -29,6 +29,7 @@ import { logger } from "../utils/globalLogger.js";
29
29
  */
30
30
  export class SkillManager extends EventEmitter {
31
31
  private personalSkillsPath: string;
32
+ private personalClaudeSkillsPath: string;
32
33
  private scanTimeout: number;
33
34
  private workdir: string;
34
35
 
@@ -47,6 +48,8 @@ export class SkillManager extends EventEmitter {
47
48
  super();
48
49
  this.personalSkillsPath =
49
50
  options.personalSkillsPath || join(homedir(), ".wave", "skills");
51
+ this.personalClaudeSkillsPath =
52
+ options.personalClaudeSkillsPath || join(homedir(), ".claude", "skills");
50
53
  this.scanTimeout = options.scanTimeout || 5000;
51
54
  this.workdir = options.workdir || process.cwd();
52
55
  this.watchEnabled = options.watch ?? false;
@@ -127,7 +130,9 @@ export class SkillManager extends EventEmitter {
127
130
 
128
131
  const pathsToWatch = [
129
132
  this.personalSkillsPath,
133
+ this.personalClaudeSkillsPath,
130
134
  join(this.workdir, ".wave", "skills"),
135
+ join(this.workdir, ".claude", "skills"),
131
136
  ];
132
137
 
133
138
  logger?.debug(`Setting up skill watcher for: ${pathsToWatch.join(", ")}`);
@@ -220,6 +225,11 @@ export class SkillManager extends EventEmitter {
220
225
  "builtin",
221
226
  );
222
227
 
228
+ const personalClaudeCollection = await this.discoverSkillCollection(
229
+ this.personalClaudeSkillsPath,
230
+ "personal",
231
+ );
232
+
223
233
  const personalCollection = await this.discoverSkillCollection(
224
234
  this.personalSkillsPath,
225
235
  "personal",
@@ -232,10 +242,14 @@ export class SkillManager extends EventEmitter {
232
242
 
233
243
  return {
234
244
  builtinSkills: builtinCollection.skills,
235
- personalSkills: personalCollection.skills,
245
+ personalSkills: new Map([
246
+ ...personalClaudeCollection.skills,
247
+ ...personalCollection.skills, // .wave overrides .claude
248
+ ]),
236
249
  projectSkills: projectCollection.skills,
237
250
  errors: [
238
251
  ...builtinCollection.errors,
252
+ ...personalClaudeCollection.errors,
239
253
  ...personalCollection.errors,
240
254
  ...projectCollection.errors,
241
255
  ],
@@ -256,77 +270,88 @@ export class SkillManager extends EventEmitter {
256
270
  errors: [],
257
271
  };
258
272
 
259
- let skillsPath: string;
260
- if (type === "personal") {
261
- skillsPath = basePath;
262
- } else if (type === "builtin") {
263
- skillsPath = basePath;
273
+ if (type === "project") {
274
+ // Scan .claude/skills first, then .wave/skills (wave overrides)
275
+ const claudePath = join(basePath, ".claude", "skills");
276
+ const wavePath = join(basePath, ".wave", "skills");
277
+ await this.scanSkillPath(claudePath, collection);
278
+ await this.scanSkillPath(wavePath, collection);
264
279
  } else {
265
- skillsPath = join(basePath, ".wave", "skills");
280
+ await this.scanSkillPath(basePath, collection);
266
281
  }
267
282
 
283
+ return collection;
284
+ }
285
+
286
+ private async scanSkillPath(
287
+ skillsPath: string,
288
+ collection: SkillCollection,
289
+ ): Promise<void> {
268
290
  try {
269
291
  const skillDirs = await this.findSkillDirectories(skillsPath);
270
292
  logger?.debug(
271
293
  `Found ${skillDirs.length} potential skill directories in ${skillsPath}`,
272
294
  );
295
+ await this.processSkillDirs(skillDirs, collection);
296
+ } catch (error) {
297
+ logger?.debug(
298
+ `Could not scan ${skillsPath}: ${error instanceof Error ? error.message : String(error)}`,
299
+ );
300
+ }
301
+ }
273
302
 
274
- for (const skillDir of skillDirs) {
275
- try {
276
- const skillFilePath = join(skillDir, "SKILL.md");
303
+ private async processSkillDirs(
304
+ skillDirs: string[],
305
+ collection: SkillCollection,
306
+ ): Promise<void> {
307
+ for (const skillDir of skillDirs) {
308
+ try {
309
+ const skillFilePath = join(skillDir, "SKILL.md");
277
310
 
278
- // Check if SKILL.md exists
279
- try {
280
- await stat(skillFilePath);
281
- } catch {
282
- continue; // Skip directories without SKILL.md
283
- }
284
-
285
- const parsed = parseSkillFile(skillFilePath, {
286
- basePath: skillDir,
287
- validateMetadata: true,
288
- });
311
+ // Check if SKILL.md exists
312
+ try {
313
+ await stat(skillFilePath);
314
+ } catch {
315
+ continue; // Skip directories without SKILL.md
316
+ }
289
317
 
290
- if (parsed.isValid) {
291
- // Override the skill type with the collection type
292
- const skillMetadata: SkillMetadata = {
293
- ...parsed.skillMetadata,
294
- type,
295
- };
296
-
297
- // Create full skill object with content
298
- const skill: Skill = {
299
- ...skillMetadata,
300
- content: parsed.content,
301
- frontmatter: parsed.frontmatter,
302
- isValid: parsed.isValid,
303
- errors: parsed.validationErrors,
304
- };
305
-
306
- collection.skills.set(skillMetadata.name, skillMetadata);
307
- // Store the full skill content in the manager's skillContent map
308
- this.skillContent.set(skillMetadata.name, skill);
309
- } else {
310
- collection.errors.push({
311
- skillPath: skillDir,
312
- message: parsed.validationErrors.join("; "),
313
- });
314
- }
315
- } catch (error) {
318
+ const parsed = parseSkillFile(skillFilePath, {
319
+ basePath: skillDir,
320
+ validateMetadata: true,
321
+ });
322
+
323
+ if (parsed.isValid) {
324
+ // Override the skill type with the collection type
325
+ const skillMetadata: SkillMetadata = {
326
+ ...parsed.skillMetadata,
327
+ type: collection.type,
328
+ };
329
+
330
+ // Create full skill object with content
331
+ const skill: Skill = {
332
+ ...skillMetadata,
333
+ content: parsed.content,
334
+ frontmatter: parsed.frontmatter,
335
+ isValid: parsed.isValid,
336
+ errors: parsed.validationErrors,
337
+ };
338
+
339
+ collection.skills.set(skillMetadata.name, skillMetadata);
340
+ // Store the full skill content in the manager's skillContent map
341
+ this.skillContent.set(skillMetadata.name, skill);
342
+ } else {
316
343
  collection.errors.push({
317
344
  skillPath: skillDir,
318
- message: `Failed to process skill: ${error instanceof Error ? error.message : String(error)}`,
345
+ message: parsed.validationErrors.join("; "),
319
346
  });
320
347
  }
348
+ } catch (error) {
349
+ collection.errors.push({
350
+ skillPath: skillDir,
351
+ message: `Failed to process skill: ${error instanceof Error ? error.message : String(error)}`,
352
+ });
321
353
  }
322
- } catch (error) {
323
- logger?.debug(
324
- `Could not scan ${skillsPath}: ${error instanceof Error ? error.message : String(error)}`,
325
- );
326
- // Not an error - the directory might not exist yet
327
354
  }
328
-
329
- return collection;
330
355
  }
331
356
 
332
357
  /**
@@ -450,15 +475,23 @@ export class SkillManager extends EventEmitter {
450
475
  // 1. Substitute parameters ($1, $ARGUMENTS, etc.)
451
476
  mainContent = substituteCommandParameters(mainContent, argsString);
452
477
 
453
- // 2. Substitute ${WAVE_SKILL_DIR} with the skill's directory path
478
+ // 2. Substitute ${WAVE_SKILL_DIR} and ${CLAUDE_SKILL_DIR}
454
479
  mainContent = mainContent.replace(/\$\{WAVE_SKILL_DIR\}/g, skill.skillPath);
480
+ mainContent = mainContent.replace(
481
+ /\$\{CLAUDE_SKILL_DIR\}/g,
482
+ skill.skillPath,
483
+ );
455
484
 
456
- // 3. Substitute ${WAVE_PLUGIN_ROOT} with the skill's plugin root path
485
+ // 3. Substitute ${WAVE_PLUGIN_ROOT} and ${CLAUDE_PLUGIN_ROOT}
457
486
  if (skill.pluginRoot) {
458
487
  mainContent = mainContent.replace(
459
488
  /\$\{WAVE_PLUGIN_ROOT\}/g,
460
489
  skill.pluginRoot,
461
490
  );
491
+ mainContent = mainContent.replace(
492
+ /\$\{CLAUDE_PLUGIN_ROOT\}/g,
493
+ skill.pluginRoot,
494
+ );
462
495
  }
463
496
 
464
497
  return skillPath + mainContent;
@@ -68,6 +68,9 @@ export class MarketplaceService {
68
68
 
69
69
  this.ensureDirectoryStructure();
70
70
  this.runMigration();
71
+ // Stored dynamically to avoid widening keyof MarketplaceService
72
+ (this as Record<string, unknown>)._seedComplete =
73
+ this.seedBuiltinMarketplace();
71
74
  }
72
75
 
73
76
  /**
@@ -121,6 +124,63 @@ export class MarketplaceService {
121
124
  }
122
125
  }
123
126
 
127
+ /**
128
+ * Seeds the builtin marketplace on first startup.
129
+ * Adds it to user-scope settings and marks the cache as seeded.
130
+ */
131
+ private async seedBuiltinMarketplace(): Promise<void> {
132
+ try {
133
+ const cache = await this.getCacheRegistry();
134
+ if (cache?.builtinSeeded) return;
135
+
136
+ const builtin = MarketplaceService.BUILTIN_MARKETPLACE;
137
+
138
+ // Add to user-scope settings if not already in any scope
139
+ const scoped = this.configurationService.getMergedMarketplaces(
140
+ this.workdir,
141
+ );
142
+ if (!scoped[builtin.name]) {
143
+ await this.configurationService.addMarketplaceToScope(
144
+ this.workdir,
145
+ "user",
146
+ builtin.name,
147
+ { source: builtin.source, autoUpdate: builtin.autoUpdate },
148
+ );
149
+ }
150
+
151
+ // Update cache: add entry + set builtinSeeded flag
152
+ await this.updateCacheMarketplace(builtin.name, {
153
+ source: builtin.source,
154
+ autoUpdate: builtin.autoUpdate,
155
+ isBuiltin: true,
156
+ declaredScope: "builtin",
157
+ });
158
+ await this.setBuiltinSeeded(true);
159
+ } catch {
160
+ // Seeding failure should not block startup
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Sets the builtinSeeded flag in the cache registry, preserving all other fields.
166
+ */
167
+ private async setBuiltinSeeded(value: boolean): Promise<void> {
168
+ const registry = await this.getCacheRegistry();
169
+ const tmpPath = `${this.knownMarketplacesPath}.tmp`;
170
+ await fs.writeFile(
171
+ tmpPath,
172
+ JSON.stringify(
173
+ {
174
+ builtinSeeded: value,
175
+ marketplaces: registry?.marketplaces ?? [],
176
+ },
177
+ null,
178
+ 2,
179
+ ),
180
+ );
181
+ await fs.rename(tmpPath, this.knownMarketplacesPath);
182
+ }
183
+
124
184
  /**
125
185
  * Check if a lock file is stale by reading its PID and checking if the process is alive.
126
186
  * Returns true if the lock is stale and safe to remove.
@@ -258,7 +318,17 @@ export class MarketplaceService {
258
318
  });
259
319
  }
260
320
  const tmpPath = `${this.knownMarketplacesPath}.tmp`;
261
- await fs.writeFile(tmpPath, JSON.stringify({ marketplaces }, null, 2));
321
+ await fs.writeFile(
322
+ tmpPath,
323
+ JSON.stringify(
324
+ {
325
+ builtinSeeded: registry?.builtinSeeded ?? false,
326
+ marketplaces,
327
+ },
328
+ null,
329
+ 2,
330
+ ),
331
+ );
262
332
  await fs.rename(tmpPath, this.knownMarketplacesPath);
263
333
  }
264
334
 
@@ -272,7 +342,14 @@ export class MarketplaceService {
272
342
  const tmpPath = `${this.knownMarketplacesPath}.tmp`;
273
343
  await fs.writeFile(
274
344
  tmpPath,
275
- JSON.stringify({ marketplaces: filtered }, null, 2),
345
+ JSON.stringify(
346
+ {
347
+ builtinSeeded: registry?.builtinSeeded ?? false,
348
+ marketplaces: filtered,
349
+ },
350
+ null,
351
+ 2,
352
+ ),
276
353
  );
277
354
  await fs.rename(tmpPath, this.knownMarketplacesPath);
278
355
  }
@@ -313,13 +390,21 @@ export class MarketplaceService {
313
390
  async loadMarketplaceManifest(
314
391
  marketplacePath: string,
315
392
  ): Promise<MarketplaceManifest> {
316
- const manifestPath = path.join(
393
+ let manifestPath = path.join(
317
394
  marketplacePath,
318
395
  ".wave-plugin",
319
396
  "marketplace.json",
320
397
  );
321
398
  if (!existsSync(manifestPath)) {
322
- throw new Error(`Marketplace manifest not found at ${manifestPath}`);
399
+ // Fallback to .claude-plugin/marketplace.json
400
+ manifestPath = path.join(
401
+ marketplacePath,
402
+ ".claude-plugin",
403
+ "marketplace.json",
404
+ );
405
+ if (!existsSync(manifestPath)) {
406
+ throw new Error(`Marketplace manifest not found at ${manifestPath}`);
407
+ }
323
408
  }
324
409
  const content = await fs.readFile(manifestPath, "utf-8");
325
410
  const manifest = JSON.parse(content);
@@ -536,6 +621,9 @@ export class MarketplaceService {
536
621
  const targetScope =
537
622
  scope || this.getMarketplaceDeclaringSource(name) || "user";
538
623
 
624
+ // Uninstall all plugins from this marketplace
625
+ await this.uninstallPluginsByMarketplace(name);
626
+
539
627
  if (targetScope !== "builtin") {
540
628
  await this.configurationService.removeMarketplaceFromScope(
541
629
  this.workdir,
@@ -549,6 +637,54 @@ export class MarketplaceService {
549
637
  });
550
638
  }
551
639
 
640
+ /**
641
+ * Uninstalls all plugins belonging to a given marketplace.
642
+ * Removes them from installed_plugins.json, deletes cache files,
643
+ * and cleans up enabledPlugins entries from all scopes.
644
+ */
645
+ private async uninstallPluginsByMarketplace(
646
+ marketplaceName: string,
647
+ ): Promise<void> {
648
+ const installedRegistry = await this.getInstalledPlugins();
649
+ const pluginsFromMarketplace = installedRegistry.plugins.filter(
650
+ (p) => p.marketplace === marketplaceName,
651
+ );
652
+
653
+ if (pluginsFromMarketplace.length === 0) return;
654
+
655
+ // Remove all plugins from this marketplace from the registry
656
+ installedRegistry.plugins = installedRegistry.plugins.filter(
657
+ (p) => p.marketplace !== marketplaceName,
658
+ );
659
+ await this.saveInstalledPlugins(installedRegistry);
660
+
661
+ // Delete cache files that are no longer referenced
662
+ const remainingCachePaths = new Set(
663
+ installedRegistry.plugins.map((p) => p.cachePath),
664
+ );
665
+ for (const plugin of pluginsFromMarketplace) {
666
+ if (
667
+ !remainingCachePaths.has(plugin.cachePath) &&
668
+ existsSync(plugin.cachePath)
669
+ ) {
670
+ await fs.rm(plugin.cachePath, { recursive: true, force: true });
671
+ }
672
+ }
673
+
674
+ // Remove enabledPlugins entries from all scopes
675
+ const scopes: Scope[] = ["user", "project", "local"];
676
+ for (const plugin of pluginsFromMarketplace) {
677
+ const pluginId = `${plugin.name}@${plugin.marketplace}`;
678
+ for (const s of scopes) {
679
+ await this.configurationService.removeEnabledPlugin(
680
+ this.workdir,
681
+ s,
682
+ pluginId,
683
+ );
684
+ }
685
+ }
686
+ }
687
+
552
688
  /**
553
689
  * Updates a specific marketplace or all marketplaces
554
690
  */
@@ -771,13 +907,23 @@ export class MarketplaceService {
771
907
  pluginSrcPath = path.resolve(marketplacePath, pluginEntry.source);
772
908
  }
773
909
 
774
- const pluginManifestPath = path.join(
910
+ let pluginManifestPath = path.join(
775
911
  pluginSrcPath,
776
912
  ".wave-plugin",
777
913
  "plugin.json",
778
914
  );
779
915
  if (!existsSync(pluginManifestPath)) {
780
- throw new Error(`Plugin manifest not found at ${pluginManifestPath}`);
916
+ // Fallback to .claude-plugin/plugin.json
917
+ pluginManifestPath = path.join(
918
+ pluginSrcPath,
919
+ ".claude-plugin",
920
+ "plugin.json",
921
+ );
922
+ if (!existsSync(pluginManifestPath)) {
923
+ throw new Error(
924
+ `Plugin manifest not found at ${pluginManifestPath}`,
925
+ );
926
+ }
781
927
  }
782
928
 
783
929
  const pluginManifestContent = await fs.readFile(
@@ -130,6 +130,27 @@ export class MemoryService {
130
130
  userMemoryFile: USER_MEMORY_FILE,
131
131
  contentLength: content.length,
132
132
  });
133
+
134
+ // If the file is still the default empty template, fallback to ~/.claude/AGENTS.md
135
+ const defaultTemplate =
136
+ "# User Memory\n\nThis is the user-level memory file, recording important information and context across projects.\n\n";
137
+ if (content === defaultTemplate) {
138
+ const claudeMemoryPath = path.join(homedir(), ".claude", "AGENTS.md");
139
+ try {
140
+ const claudeContent = await fs.readFile(claudeMemoryPath, "utf-8");
141
+ logger.debug(
142
+ "User memory content read from ~/.claude/AGENTS.md fallback",
143
+ {
144
+ claudeMemoryPath,
145
+ contentLength: claudeContent.length,
146
+ },
147
+ );
148
+ return claudeContent;
149
+ } catch {
150
+ // CLAUDE.md doesn't exist or can't be read, return the default template
151
+ }
152
+ }
153
+
133
154
  return content;
134
155
  } catch (error) {
135
156
  logger.error("Failed to read user memory:", error);
@@ -139,8 +160,6 @@ export class MemoryService {
139
160
 
140
161
  async readMemoryFile(workdir: string): Promise<string> {
141
162
  const memoryFilePath = path.join(workdir, "AGENTS.md");
142
-
143
- // Direct file access
144
163
  try {
145
164
  const content = await fs.readFile(memoryFilePath, "utf-8");
146
165
  logger.debug("Memory file read successfully via direct file access", {
@@ -150,10 +169,28 @@ export class MemoryService {
150
169
  return content;
151
170
  } catch (error) {
152
171
  if ((error as NodeJS.ErrnoException).code === "ENOENT") {
153
- logger.debug("Memory file does not exist, returning empty content", {
154
- memoryFilePath,
155
- });
156
- return "";
172
+ // Fallback to CLAUDE.md
173
+ const claudeMemoryPath = path.join(workdir, "CLAUDE.md");
174
+ try {
175
+ const content = await fs.readFile(claudeMemoryPath, "utf-8");
176
+ logger.debug("Memory file read from CLAUDE.md fallback", {
177
+ memoryFilePath: claudeMemoryPath,
178
+ contentLength: content.length,
179
+ });
180
+ return content;
181
+ } catch (claudeError) {
182
+ if ((claudeError as NodeJS.ErrnoException).code === "ENOENT") {
183
+ logger.debug("Neither AGENTS.md nor CLAUDE.md found", {
184
+ memoryFilePath,
185
+ });
186
+ return "";
187
+ }
188
+ logger.error("Failed to read CLAUDE.md fallback", {
189
+ memoryFilePath: claudeMemoryPath,
190
+ error: claudeError,
191
+ });
192
+ return "";
193
+ }
157
194
  }
158
195
  logger.error("Failed to read memory file", { memoryFilePath, error });
159
196
  return "";
@@ -23,11 +23,13 @@ export class PluginLoader {
23
23
  */
24
24
  static async loadManifest(pluginPath: string): Promise<PluginManifest> {
25
25
  const dotWavePluginPath = path.join(pluginPath, ".wave-plugin");
26
- const manifestPath = path.join(dotWavePluginPath, "plugin.json");
27
26
 
28
- // T018: Ensure plugin.json is the only file in .wave-plugin/
27
+ // Check if .wave-plugin/ directory exists
28
+ let wavePluginDirExists = false;
29
29
  try {
30
30
  const entries = await fs.readdir(dotWavePluginPath);
31
+ wavePluginDirExists = true;
32
+ // T018: Ensure plugin.json is the only file in .wave-plugin/
31
33
  const misplaced = entries.filter((e) => e !== "plugin.json");
32
34
  if (misplaced.length > 0) {
33
35
  throw new Error(
@@ -39,15 +41,39 @@ export class PluginLoader {
39
41
  error instanceof Error &&
40
42
  (error as { code?: string }).code === "ENOENT"
41
43
  ) {
44
+ // .wave-plugin/ doesn't exist, will try .claude-plugin/ fallback below
45
+ } else {
46
+ throw error;
47
+ }
48
+ }
49
+
50
+ if (wavePluginDirExists) {
51
+ const manifestPath = path.join(dotWavePluginPath, "plugin.json");
52
+ try {
53
+ const content = await fs.readFile(manifestPath, "utf-8");
54
+ const manifest = JSON.parse(content) as PluginManifest;
55
+ this.validateManifest(manifest);
56
+ return manifest;
57
+ } catch (error) {
58
+ if (
59
+ error instanceof Error &&
60
+ (error as { code?: string }).code === "ENOENT"
61
+ ) {
62
+ throw new Error(`Plugin manifest not found at ${manifestPath}`);
63
+ }
42
64
  throw new Error(
43
- `Plugin manifest directory not found at ${dotWavePluginPath}`,
65
+ `Failed to load plugin manifest at ${manifestPath}: ${
66
+ error instanceof Error ? error.message : String(error)
67
+ }`,
44
68
  );
45
69
  }
46
- throw error;
47
70
  }
48
71
 
72
+ // Fallback to .claude-plugin/
73
+ const dotClaudePluginPath = path.join(pluginPath, ".claude-plugin");
74
+ const claudeManifestPath = path.join(dotClaudePluginPath, "plugin.json");
49
75
  try {
50
- const content = await fs.readFile(manifestPath, "utf-8");
76
+ const content = await fs.readFile(claudeManifestPath, "utf-8");
51
77
  const manifest = JSON.parse(content) as PluginManifest;
52
78
  this.validateManifest(manifest);
53
79
  return manifest;
@@ -56,10 +82,12 @@ export class PluginLoader {
56
82
  error instanceof Error &&
57
83
  (error as { code?: string }).code === "ENOENT"
58
84
  ) {
59
- throw new Error(`Plugin manifest not found at ${manifestPath}`);
85
+ throw new Error(
86
+ `Plugin manifest not found at ${dotWavePluginPath} or ${dotClaudePluginPath}`,
87
+ );
60
88
  }
61
89
  throw new Error(
62
- `Failed to load plugin manifest at ${manifestPath}: ${
90
+ `Failed to load plugin manifest at ${claudeManifestPath}: ${
63
91
  error instanceof Error ? error.message : String(error)
64
92
  }`,
65
93
  );
@@ -53,6 +53,7 @@ export interface KnownMarketplace {
53
53
  }
54
54
 
55
55
  export interface KnownMarketplacesRegistry {
56
+ builtinSeeded?: boolean;
56
57
  marketplaces: KnownMarketplace[];
57
58
  }
58
59
 
@@ -73,6 +73,7 @@ export interface SkillToolArgs {
73
73
 
74
74
  export interface SkillManagerOptions {
75
75
  personalSkillsPath?: string;
76
+ personalClaudeSkillsPath?: string;
76
77
  scanTimeout?: number;
77
78
  workdir?: string;
78
79
  watch?: boolean;
@@ -70,21 +70,26 @@ export function scanCommandsDirectory(dirPath: string): CustomSlashCommand[] {
70
70
  * Load all custom slash commands from both project and user directories
71
71
  */
72
72
  export function loadCustomSlashCommands(workdir: string): CustomSlashCommand[] {
73
- const projectCommands = scanCommandsDirectory(getProjectCommandsDir(workdir));
74
- const userCommands = scanCommandsDirectory(getUserCommandsDir());
73
+ const userClaudeCommands = scanCommandsDirectory(
74
+ join(homedir(), ".claude", "commands"),
75
+ );
76
+ const userWaveCommands = scanCommandsDirectory(getUserCommandsDir());
77
+ const projectClaudeCommands = scanCommandsDirectory(
78
+ join(workdir, ".claude", "commands"),
79
+ );
80
+ const projectWaveCommands = scanCommandsDirectory(
81
+ getProjectCommandsDir(workdir),
82
+ );
75
83
 
76
- // Project commands take precedence over user commands with the same name
77
84
  const commandMap = new Map<string, CustomSlashCommand>();
78
-
79
- // Add user commands first
80
- for (const command of userCommands) {
85
+ // Write in priority order: lowest first, highest last (overwrites)
86
+ for (const command of [
87
+ ...userClaudeCommands,
88
+ ...userWaveCommands,
89
+ ...projectClaudeCommands,
90
+ ...projectWaveCommands,
91
+ ]) {
81
92
  commandMap.set(command.id, command);
82
93
  }
83
-
84
- // Add project commands (will overwrite user commands with same name)
85
- for (const command of projectCommands) {
86
- commandMap.set(command.id, command);
87
- }
88
-
89
94
  return Array.from(commandMap.values());
90
95
  }
@@ -55,7 +55,9 @@ export function parseSkillFile(
55
55
  const skillPath = basePath || dirname(filePath);
56
56
  const skillType =
57
57
  skillPath.includes("/.wave/skills") ||
58
- skillPath.includes("\\.wave\\skills")
58
+ skillPath.includes("\\.wave\\skills") ||
59
+ skillPath.includes("/.claude/skills") ||
60
+ skillPath.includes("\\.claude\\skills")
59
61
  ? "project"
60
62
  : "personal";
61
63