wave-agent-sdk 0.14.0 → 0.14.1

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 (32) hide show
  1. package/dist/core/plugin.d.ts +2 -2
  2. package/dist/core/plugin.d.ts.map +1 -1
  3. package/dist/core/plugin.js +7 -7
  4. package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
  5. package/dist/managers/backgroundTaskManager.js +0 -12
  6. package/dist/managers/pluginManager.d.ts.map +1 -1
  7. package/dist/managers/pluginManager.js +1 -1
  8. package/dist/services/MarketplaceService.d.ts +53 -12
  9. package/dist/services/MarketplaceService.d.ts.map +1 -1
  10. package/dist/services/MarketplaceService.js +311 -123
  11. package/dist/services/configurationService.d.ts +17 -1
  12. package/dist/services/configurationService.d.ts.map +1 -1
  13. package/dist/services/configurationService.js +104 -0
  14. package/dist/services/pluginLoader.d.ts +6 -0
  15. package/dist/services/pluginLoader.d.ts.map +1 -1
  16. package/dist/services/pluginLoader.js +52 -7
  17. package/dist/types/configuration.d.ts +7 -0
  18. package/dist/types/configuration.d.ts.map +1 -1
  19. package/dist/types/marketplace.d.ts +28 -1
  20. package/dist/types/marketplace.d.ts.map +1 -1
  21. package/dist/types/plugins.d.ts +13 -1
  22. package/dist/types/plugins.d.ts.map +1 -1
  23. package/package.json +1 -1
  24. package/src/core/plugin.ts +13 -7
  25. package/src/managers/backgroundTaskManager.ts +1 -20
  26. package/src/managers/pluginManager.ts +4 -1
  27. package/src/services/MarketplaceService.ts +425 -134
  28. package/src/services/configurationService.ts +131 -0
  29. package/src/services/pluginLoader.ts +66 -7
  30. package/src/types/configuration.ts +8 -0
  31. package/src/types/marketplace.ts +26 -1
  32. package/src/types/plugins.ts +13 -1
@@ -15,6 +15,7 @@ import type {
15
15
  ConfigurationPaths,
16
16
  WaveConfiguration,
17
17
  Scope,
18
+ MarketplaceConfig,
18
19
  } from "../types/configuration.js";
19
20
  import {
20
21
  getAllConfigPaths,
@@ -796,6 +797,124 @@ export class ConfigurationService {
796
797
  await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
797
798
  }
798
799
 
800
+ /**
801
+ * Get merged marketplaces from all scopes
802
+ */
803
+ getMergedMarketplaces(workdir: string): Record<string, MarketplaceConfig> {
804
+ const mergedConfig = loadMergedWaveConfig(workdir);
805
+ return mergedConfig?.marketplaces || {};
806
+ }
807
+
808
+ /**
809
+ * Get marketplaces at a specific scope
810
+ */
811
+ getScopedMarketplaces(
812
+ workdir: string,
813
+ scope: Scope,
814
+ ): Record<string, MarketplaceConfig> {
815
+ let configPath: string;
816
+ if (scope === "user") {
817
+ configPath = getUserConfigPaths()[0];
818
+ } else if (scope === "project") {
819
+ configPath = getProjectConfigPaths(workdir)[1];
820
+ } else {
821
+ configPath = getProjectConfigPaths(workdir)[0];
822
+ }
823
+ const config = loadWaveConfigFromFile(configPath);
824
+ return config?.marketplaces || {};
825
+ }
826
+
827
+ /**
828
+ * Add a marketplace to the specified scope
829
+ */
830
+ async addMarketplaceToScope(
831
+ workdir: string,
832
+ scope: Scope,
833
+ name: string,
834
+ config: MarketplaceConfig,
835
+ ): Promise<void> {
836
+ if (scope !== "user" && !existsSync(workdir)) {
837
+ throw new Error(`Working directory does not exist: ${workdir}`);
838
+ }
839
+
840
+ let configPath: string;
841
+ if (scope === "user") {
842
+ configPath = getUserConfigPaths()[0];
843
+ } else if (scope === "project") {
844
+ configPath = getProjectConfigPaths(workdir)[1];
845
+ } else {
846
+ configPath = getProjectConfigPaths(workdir)[0];
847
+ }
848
+
849
+ const configDir = path.dirname(configPath);
850
+ if (!existsSync(configDir)) {
851
+ await fs.mkdir(configDir, { recursive: true });
852
+ }
853
+
854
+ let fileConfig: WaveConfiguration = {};
855
+ if (existsSync(configPath)) {
856
+ try {
857
+ const content = await fs.readFile(configPath, "utf-8");
858
+ fileConfig = JSON.parse(content);
859
+ } catch {
860
+ // Start with empty config if file is corrupted
861
+ }
862
+ }
863
+
864
+ if (!fileConfig.marketplaces) {
865
+ fileConfig.marketplaces = {};
866
+ }
867
+ fileConfig.marketplaces[name] = config;
868
+
869
+ await fs.writeFile(
870
+ configPath,
871
+ JSON.stringify(fileConfig, null, 2),
872
+ "utf-8",
873
+ );
874
+ }
875
+
876
+ /**
877
+ * Remove a marketplace from the specified scope
878
+ */
879
+ async removeMarketplaceFromScope(
880
+ workdir: string,
881
+ scope: Scope,
882
+ name: string,
883
+ ): Promise<void> {
884
+ if (scope !== "user" && !existsSync(workdir)) {
885
+ throw new Error(`Working directory does not exist: ${workdir}`);
886
+ }
887
+
888
+ let configPath: string;
889
+ if (scope === "user") {
890
+ configPath = getUserConfigPaths()[0];
891
+ } else if (scope === "project") {
892
+ configPath = getProjectConfigPaths(workdir)[1];
893
+ } else {
894
+ configPath = getProjectConfigPaths(workdir)[0];
895
+ }
896
+
897
+ if (!existsSync(configPath)) {
898
+ return;
899
+ }
900
+
901
+ try {
902
+ const content = await fs.readFile(configPath, "utf-8");
903
+ const fileConfig: WaveConfiguration = JSON.parse(content);
904
+
905
+ if (fileConfig.marketplaces && name in fileConfig.marketplaces) {
906
+ delete fileConfig.marketplaces[name];
907
+ await fs.writeFile(
908
+ configPath,
909
+ JSON.stringify(fileConfig, null, 2),
910
+ "utf-8",
911
+ );
912
+ }
913
+ } catch {
914
+ // Ignore errors for corrupted or non-existent files
915
+ }
916
+ }
917
+
799
918
  /**
800
919
  * Remove a plugin from the enabled plugins in the specified scope
801
920
  */
@@ -991,6 +1110,7 @@ export function loadWaveConfigFromFile(
991
1110
  ? config.autoMemoryEnabled
992
1111
  : undefined,
993
1112
  models: config.models || undefined,
1113
+ marketplaces: config.marketplaces || undefined,
994
1114
  };
995
1115
  } catch (error) {
996
1116
  if (error instanceof SyntaxError) {
@@ -1124,6 +1244,12 @@ export function loadMergedWaveConfig(
1124
1244
  mergedConfig.autoMemoryFrequency = config.autoMemoryFrequency;
1125
1245
  }
1126
1246
 
1247
+ // Merge marketplaces (last one wins for same key)
1248
+ if (config.marketplaces) {
1249
+ if (!mergedConfig.marketplaces) mergedConfig.marketplaces = {};
1250
+ Object.assign(mergedConfig.marketplaces, config.marketplaces);
1251
+ }
1252
+
1127
1253
  // Merge models
1128
1254
  if (config.models) {
1129
1255
  if (!mergedConfig.models) mergedConfig.models = {};
@@ -1157,6 +1283,11 @@ export function loadMergedWaveConfig(
1157
1283
  : undefined,
1158
1284
  language: mergedConfig.language,
1159
1285
  autoMemoryEnabled: mergedConfig.autoMemoryEnabled,
1286
+ marketplaces:
1287
+ mergedConfig.marketplaces &&
1288
+ Object.keys(mergedConfig.marketplaces).length > 0
1289
+ ? mergedConfig.marketplaces
1290
+ : undefined,
1160
1291
  models:
1161
1292
  mergedConfig.models && Object.keys(mergedConfig.models).length > 0
1162
1293
  ? mergedConfig.models
@@ -1,4 +1,5 @@
1
1
  import * as fs from "fs/promises";
2
+ import * as fsSync from "fs";
2
3
  import * as path from "path";
3
4
  import {
4
5
  PluginManifest,
@@ -13,21 +14,79 @@ import { parseSkillFile } from "../utils/skillParser.js";
13
14
  import { resolveMcpConfig } from "../managers/mcpManager.js";
14
15
 
15
16
  export class PluginLoader {
17
+ /**
18
+ * Finds the first existing plugin manifest path.
19
+ * Prefers .wave-plugin/ for backward compatibility, falls back to .claude-plugin/.
20
+ * Returns null if neither exists.
21
+ */
22
+ private static findPluginManifestPath(pluginPath: string): string | null {
23
+ const waveManifestPath = path.join(
24
+ pluginPath,
25
+ ".wave-plugin",
26
+ "plugin.json",
27
+ );
28
+ const claudeManifestPath = path.join(
29
+ pluginPath,
30
+ ".claude-plugin",
31
+ "plugin.json",
32
+ );
33
+
34
+ // Check .wave-plugin first for backward compatibility
35
+ try {
36
+ const waveStat = fsSync.statSync(waveManifestPath);
37
+ if (waveStat.isFile()) {
38
+ return waveManifestPath;
39
+ }
40
+ } catch {
41
+ // .wave-plugin/plugin.json doesn't exist
42
+ }
43
+
44
+ try {
45
+ const claudeStat = fsSync.statSync(claudeManifestPath);
46
+ if (claudeStat.isFile()) {
47
+ return claudeManifestPath;
48
+ }
49
+ } catch {
50
+ // .claude-plugin/plugin.json doesn't exist
51
+ }
52
+
53
+ return null;
54
+ }
55
+
16
56
  /**
17
57
  * Load and validate a plugin manifest from a directory
18
58
  * @param pluginPath Absolute path to the plugin directory
19
59
  */
20
60
  static async loadManifest(pluginPath: string): Promise<PluginManifest> {
21
- const dotWavePluginPath = path.join(pluginPath, ".wave-plugin");
22
- const manifestPath = path.join(dotWavePluginPath, "plugin.json");
61
+ const manifestPath = this.findPluginManifestPath(pluginPath);
62
+ if (!manifestPath) {
63
+ throw new Error(
64
+ `Plugin manifest not found at ${pluginPath}. Neither .wave-plugin/plugin.json nor .claude-plugin/plugin.json exists.`,
65
+ );
66
+ }
23
67
 
24
- // T018: Ensure plugin.json is the only file in .wave-plugin/
68
+ // Determine which directory is being used for validation
69
+ const pluginDirName = manifestPath.includes(".claude-plugin")
70
+ ? ".claude-plugin"
71
+ : ".wave-plugin";
72
+ const pluginDirPath = path.join(pluginPath, pluginDirName);
73
+
74
+ // T018: Ensure plugin.json is the only file in the manifest directory
75
+ // For .claude-plugin/, marketplace.json is also allowed (Claude Code convention)
25
76
  try {
26
- const entries = await fs.readdir(dotWavePluginPath);
27
- const misplaced = entries.filter((e) => e !== "plugin.json");
77
+ const entries = await fs.readdir(pluginDirPath);
78
+ const allowedFiles = ["plugin.json"];
79
+ if (pluginDirName === ".claude-plugin") {
80
+ allowedFiles.push("marketplace.json");
81
+ }
82
+ const misplaced = entries.filter((e) => !allowedFiles.includes(e));
28
83
  if (misplaced.length > 0) {
84
+ const allowedMsg =
85
+ pluginDirName === ".claude-plugin"
86
+ ? "Only plugin.json and marketplace.json should be in this directory."
87
+ : "Only plugin.json should be in this directory.";
29
88
  throw new Error(
30
- `Misplaced files/directories in .wave-plugin/: ${misplaced.join(", ")}. Only plugin.json should be in this directory.`,
89
+ `Misplaced files/directories in ${pluginDirName}/: ${misplaced.join(", ")}. ${allowedMsg}`,
31
90
  );
32
91
  }
33
92
  } catch (error) {
@@ -36,7 +95,7 @@ export class PluginLoader {
36
95
  (error as { code?: string }).code === "ENOENT"
37
96
  ) {
38
97
  throw new Error(
39
- `Plugin manifest directory not found at ${dotWavePluginPath}`,
98
+ `Plugin manifest directory not found at ${pluginDirPath}`,
40
99
  );
41
100
  }
42
101
  throw error;
@@ -9,9 +9,15 @@
9
9
  import type { HookEvent, HookEventConfig } from "./hooks.js";
10
10
  import type { PermissionMode } from "./permissions.js";
11
11
  import type { ModelConfig } from "./config.js";
12
+ import type { MarketplaceSource } from "./marketplace.js";
12
13
 
13
14
  export type Scope = "user" | "project" | "local";
14
15
 
16
+ export interface MarketplaceConfig {
17
+ source: MarketplaceSource;
18
+ autoUpdate?: boolean;
19
+ }
20
+
15
21
  /**
16
22
  * Root configuration structure for all Wave Agent settings including hooks and environment variables
17
23
  */
@@ -39,6 +45,8 @@ export interface WaveConfiguration {
39
45
  autoMemoryFrequency?: number;
40
46
  /** Model-specific configuration overrides */
41
47
  models?: Record<string, Partial<ModelConfig>>;
48
+ /** Scoped marketplace declarations */
49
+ marketplaces?: Record<string, MarketplaceConfig>;
42
50
  }
43
51
 
44
52
  /**
@@ -7,8 +7,24 @@ export interface MarketplaceOwner {
7
7
 
8
8
  export interface MarketplacePluginEntry {
9
9
  name: string;
10
- source: string;
10
+ source: string | MarketplaceSource;
11
11
  description: string;
12
+ /** Claude Code compatibility: plugin category */
13
+ category?: string;
14
+ /** Claude Code compatibility: plugin tags */
15
+ tags?: string[];
16
+ /** Claude Code compatibility: when false, plugin.json is optional */
17
+ strict?: boolean;
18
+ /** Claude Code compatibility: inline manifest fields */
19
+ version?: string;
20
+ author?: { name: string; url?: string };
21
+ homepage?: string;
22
+ repository?: string;
23
+ license?: string;
24
+ keywords?: string[];
25
+ commands?: string;
26
+ skills?: string;
27
+ agents?: string;
12
28
  }
13
29
 
14
30
  export interface MarketplacePluginStatus extends MarketplacePluginEntry {
@@ -24,6 +40,8 @@ export interface MarketplaceManifest {
24
40
  name: string;
25
41
  owner: MarketplaceOwner;
26
42
  plugins: MarketplacePluginEntry[];
43
+ /** Claude Code compatibility: additional metadata */
44
+ metadata?: Record<string, unknown>;
27
45
  }
28
46
 
29
47
  export type MarketplaceSource =
@@ -40,6 +58,11 @@ export type MarketplaceSource =
40
58
  source: "git";
41
59
  url: string;
42
60
  ref?: string;
61
+ }
62
+ | {
63
+ source: "url";
64
+ url: string;
65
+ ref?: string;
43
66
  };
44
67
 
45
68
  export interface KnownMarketplace {
@@ -48,6 +71,8 @@ export interface KnownMarketplace {
48
71
  isBuiltin?: boolean;
49
72
  autoUpdate?: boolean;
50
73
  lastUpdated?: string;
74
+ /** The scope where this marketplace was declared (user, project, local, or builtin) */
75
+ declaredScope?: "user" | "project" | "local" | "builtin";
51
76
  }
52
77
 
53
78
  export interface KnownMarketplacesRegistry {
@@ -5,7 +5,7 @@ import { McpConfig } from "./mcp.js";
5
5
  import { PartialHookConfiguration } from "./configuration.js";
6
6
 
7
7
  /**
8
- * Plugin manifest structure (.wave-plugin/plugin.json)
8
+ * Plugin manifest structure (.wave-plugin/plugin.json or .claude-plugin/plugin.json)
9
9
  */
10
10
  export interface PluginManifest {
11
11
  name: string;
@@ -14,6 +14,18 @@ export interface PluginManifest {
14
14
  author?: {
15
15
  name: string;
16
16
  };
17
+ /** Claude Code compatibility: plugin keywords */
18
+ keywords?: string[];
19
+ /** Claude Code compatibility: plugin homepage URL */
20
+ homepage?: string;
21
+ /** Claude Code compatibility: repository info */
22
+ repository?: string;
23
+ /** Claude Code compatibility: license info */
24
+ license?: string;
25
+ /** Claude Code compatibility: plugin dependencies */
26
+ dependencies?: Record<string, string>;
27
+ /** Claude Code compatibility: user configuration schema */
28
+ userConfig?: Record<string, unknown>;
17
29
  }
18
30
 
19
31
  /**