@robota-sdk/agent-sdk 3.0.0-beta.23 → 3.0.0-beta.25

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.
@@ -30,8 +30,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ AgentExecutor: () => AgentExecutor,
34
+ BundlePluginInstaller: () => BundlePluginInstaller,
35
+ BundlePluginLoader: () => BundlePluginLoader,
33
36
  DEFAULT_TOOL_DESCRIPTIONS: () => DEFAULT_TOOL_DESCRIPTIONS,
34
37
  FileSessionLogger: () => import_agent_sessions3.FileSessionLogger,
38
+ MarketplaceClient: () => MarketplaceClient,
39
+ PluginSettingsStore: () => PluginSettingsStore,
40
+ PromptExecutor: () => PromptExecutor,
35
41
  Session: () => import_agent_sessions2.Session,
36
42
  SessionStore: () => import_agent_sessions4.SessionStore,
37
43
  SilentSessionLogger: () => import_agent_sessions3.SilentSessionLogger,
@@ -63,6 +69,113 @@ module.exports = __toCommonJS(index_exports);
63
69
  // src/types.ts
64
70
  var import_agent_core = require("@robota-sdk/agent-core");
65
71
 
72
+ // src/hooks/prompt-executor.ts
73
+ function extractJson(raw) {
74
+ const codeBlockMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/.exec(raw);
75
+ if (codeBlockMatch) {
76
+ return codeBlockMatch[1].trim();
77
+ }
78
+ return raw.trim();
79
+ }
80
+ var PromptExecutor = class {
81
+ type = "prompt";
82
+ providerFactory;
83
+ defaultModel;
84
+ constructor(options) {
85
+ this.providerFactory = options.providerFactory;
86
+ this.defaultModel = options.defaultModel;
87
+ }
88
+ async execute(definition, input) {
89
+ const promptDef = definition;
90
+ const model = promptDef.model ?? this.defaultModel;
91
+ try {
92
+ const provider = this.providerFactory(model);
93
+ const prompt = `${promptDef.prompt}
94
+
95
+ Context:
96
+ ${JSON.stringify(input)}
97
+
98
+ Respond with JSON: { "ok": boolean, "reason"?: string }`;
99
+ const rawResponse = await provider.complete(prompt);
100
+ const jsonStr = extractJson(rawResponse);
101
+ let parsed;
102
+ try {
103
+ parsed = JSON.parse(jsonStr);
104
+ } catch {
105
+ return {
106
+ exitCode: 1,
107
+ stdout: "",
108
+ stderr: `Failed to parse AI response as JSON: ${rawResponse}`
109
+ };
110
+ }
111
+ if (parsed.ok) {
112
+ return { exitCode: 0, stdout: JSON.stringify(parsed), stderr: "" };
113
+ }
114
+ return {
115
+ exitCode: 2,
116
+ stdout: "",
117
+ stderr: parsed.reason ?? "Blocked by prompt hook"
118
+ };
119
+ } catch (err) {
120
+ const message = err instanceof Error ? err.message : String(err);
121
+ return { exitCode: 1, stdout: "", stderr: message };
122
+ }
123
+ }
124
+ };
125
+
126
+ // src/hooks/agent-executor.ts
127
+ var DEFAULT_MAX_TURNS = 50;
128
+ var DEFAULT_TIMEOUT_SECONDS = 60;
129
+ function extractJson2(raw) {
130
+ const codeBlockMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/.exec(raw);
131
+ if (codeBlockMatch) {
132
+ return codeBlockMatch[1].trim();
133
+ }
134
+ return raw.trim();
135
+ }
136
+ var AgentExecutor = class {
137
+ type = "agent";
138
+ sessionFactory;
139
+ constructor(options) {
140
+ this.sessionFactory = options.sessionFactory;
141
+ }
142
+ async execute(definition, input) {
143
+ const agentDef = definition;
144
+ const maxTurns = agentDef.maxTurns ?? DEFAULT_MAX_TURNS;
145
+ const timeout = agentDef.timeout ?? DEFAULT_TIMEOUT_SECONDS;
146
+ try {
147
+ const session = this.sessionFactory({ maxTurns, timeout });
148
+ const prompt = `Hook input:
149
+ ${JSON.stringify(input)}
150
+
151
+ Respond with JSON: { "ok": boolean, "reason"?: string }`;
152
+ const rawResponse = await session.run(prompt);
153
+ const jsonStr = extractJson2(rawResponse);
154
+ let parsed;
155
+ try {
156
+ parsed = JSON.parse(jsonStr);
157
+ } catch {
158
+ return {
159
+ exitCode: 1,
160
+ stdout: "",
161
+ stderr: `Failed to parse agent response as JSON: ${rawResponse}`
162
+ };
163
+ }
164
+ if (parsed.ok) {
165
+ return { exitCode: 0, stdout: JSON.stringify(parsed), stderr: "" };
166
+ }
167
+ return {
168
+ exitCode: 2,
169
+ stdout: "",
170
+ stderr: parsed.reason ?? "Blocked by agent hook"
171
+ };
172
+ } catch (err) {
173
+ const message = err instanceof Error ? err.message : String(err);
174
+ return { exitCode: 1, stdout: "", stderr: message };
175
+ }
176
+ }
177
+ };
178
+
66
179
  // src/assembly/create-session.ts
67
180
  var import_agent_sessions = require("@robota-sdk/agent-sessions");
68
181
 
@@ -95,6 +208,19 @@ function buildToolsSection(descriptions) {
95
208
  const lines = ["## Available Tools", ...descriptions.map((d) => `- ${d}`)];
96
209
  return lines.join("\n");
97
210
  }
211
+ function buildSkillsSection(skills) {
212
+ const invocable = skills.filter((s) => s.disableModelInvocation !== true);
213
+ if (invocable.length === 0) {
214
+ return "";
215
+ }
216
+ const lines = [
217
+ "## Skills",
218
+ "The following skills are available:",
219
+ "",
220
+ ...invocable.map((s) => `- ${s.name}: ${s.description}`)
221
+ ];
222
+ return lines.join("\n");
223
+ }
98
224
  function buildSystemPrompt(params) {
99
225
  const { agentsMd, claudeMd, toolDescriptions, trustLevel, projectInfo, cwd, language } = params;
100
226
  const sections = [];
@@ -105,7 +231,9 @@ function buildSystemPrompt(params) {
105
231
  "Always be precise, follow existing code conventions, and prefer minimal changes."
106
232
  ];
107
233
  if (language) {
108
- roleLines.push(`Always respond in ${language}. Use ${language} for all explanations and communications.`);
234
+ roleLines.push(
235
+ `Always respond in ${language}. Use ${language} for all explanations and communications.`
236
+ );
109
237
  }
110
238
  sections.push(roleLines.join("\n"));
111
239
  if (cwd) {
@@ -137,6 +265,12 @@ function buildSystemPrompt(params) {
137
265
  if (toolsSection.length > 0) {
138
266
  sections.push(toolsSection);
139
267
  }
268
+ if (params.skills !== void 0 && params.skills.length > 0) {
269
+ const skillsSection = buildSkillsSection(params.skills);
270
+ if (skillsSection.length > 0) {
271
+ sections.push(skillsSection);
272
+ }
273
+ }
140
274
  return sections.join("\n\n");
141
275
  }
142
276
 
@@ -203,6 +337,25 @@ function createSession(options) {
203
337
  allow: [...defaultAllow, ...options.config.permissions.allow ?? []],
204
338
  deny: options.config.permissions.deny ?? []
205
339
  };
340
+ const hookTypeExecutors = [];
341
+ if (options.providerFactory) {
342
+ hookTypeExecutors.push(
343
+ new PromptExecutor({
344
+ providerFactory: options.providerFactory,
345
+ defaultModel: options.config.provider.model
346
+ })
347
+ );
348
+ }
349
+ if (options.sessionFactory) {
350
+ hookTypeExecutors.push(
351
+ new AgentExecutor({
352
+ sessionFactory: options.sessionFactory
353
+ })
354
+ );
355
+ }
356
+ if (options.additionalHookExecutors) {
357
+ hookTypeExecutors.push(...options.additionalHookExecutors);
358
+ }
206
359
  return new import_agent_sessions.Session({
207
360
  tools,
208
361
  provider,
@@ -221,7 +374,8 @@ function createSession(options) {
221
374
  promptForApproval: options.promptForApproval,
222
375
  onCompact: options.onCompact,
223
376
  compactInstructions: options.compactInstructions ?? options.context.compactInstructions,
224
- sessionLogger: options.sessionLogger
377
+ sessionLogger: options.sessionLogger,
378
+ hookTypeExecutors: hookTypeExecutors.length > 0 ? hookTypeExecutors : void 0
225
379
  });
226
380
  }
227
381
 
@@ -248,10 +402,34 @@ var PermissionsSchema = import_zod.z.object({
248
402
  deny: import_zod.z.array(import_zod.z.string()).optional()
249
403
  });
250
404
  var EnvSchema = import_zod.z.record(import_zod.z.string()).optional();
251
- var HookDefinitionSchema = import_zod.z.object({
405
+ var CommandHookDefinitionSchema = import_zod.z.object({
252
406
  type: import_zod.z.literal("command"),
253
- command: import_zod.z.string()
407
+ command: import_zod.z.string(),
408
+ timeout: import_zod.z.number().optional()
409
+ });
410
+ var HttpHookDefinitionSchema = import_zod.z.object({
411
+ type: import_zod.z.literal("http"),
412
+ url: import_zod.z.string(),
413
+ headers: import_zod.z.record(import_zod.z.string()).optional(),
414
+ timeout: import_zod.z.number().optional()
415
+ });
416
+ var PromptHookDefinitionSchema = import_zod.z.object({
417
+ type: import_zod.z.literal("prompt"),
418
+ prompt: import_zod.z.string(),
419
+ model: import_zod.z.string().optional()
254
420
  });
421
+ var AgentHookDefinitionSchema = import_zod.z.object({
422
+ type: import_zod.z.literal("agent"),
423
+ agent: import_zod.z.string(),
424
+ maxTurns: import_zod.z.number().optional(),
425
+ timeout: import_zod.z.number().optional()
426
+ });
427
+ var HookDefinitionSchema = import_zod.z.discriminatedUnion("type", [
428
+ CommandHookDefinitionSchema,
429
+ HttpHookDefinitionSchema,
430
+ PromptHookDefinitionSchema,
431
+ AgentHookDefinitionSchema
432
+ ]);
255
433
  var HookGroupSchema = import_zod.z.object({
256
434
  matcher: import_zod.z.string(),
257
435
  hooks: import_zod.z.array(HookDefinitionSchema)
@@ -260,8 +438,23 @@ var HooksSchema = import_zod.z.object({
260
438
  PreToolUse: import_zod.z.array(HookGroupSchema).optional(),
261
439
  PostToolUse: import_zod.z.array(HookGroupSchema).optional(),
262
440
  SessionStart: import_zod.z.array(HookGroupSchema).optional(),
263
- Stop: import_zod.z.array(HookGroupSchema).optional()
441
+ Stop: import_zod.z.array(HookGroupSchema).optional(),
442
+ PreCompact: import_zod.z.array(HookGroupSchema).optional(),
443
+ PostCompact: import_zod.z.array(HookGroupSchema).optional(),
444
+ UserPromptSubmit: import_zod.z.array(HookGroupSchema).optional(),
445
+ Notification: import_zod.z.array(HookGroupSchema).optional()
264
446
  }).optional();
447
+ var EnabledPluginsSchema = import_zod.z.record(import_zod.z.boolean()).optional();
448
+ var MarketplaceSourceSchema = import_zod.z.object({
449
+ source: import_zod.z.object({
450
+ type: import_zod.z.enum(["github", "git", "local", "url"]),
451
+ repo: import_zod.z.string().optional(),
452
+ url: import_zod.z.string().optional(),
453
+ path: import_zod.z.string().optional(),
454
+ ref: import_zod.z.string().optional()
455
+ })
456
+ });
457
+ var ExtraKnownMarketplacesSchema = import_zod.z.record(MarketplaceSourceSchema).optional();
265
458
  var SettingsSchema = import_zod.z.object({
266
459
  /** Trust level used when no --permission-mode flag is given */
267
460
  defaultTrustLevel: import_zod.z.enum(["safe", "moderate", "full"]).optional(),
@@ -270,7 +463,11 @@ var SettingsSchema = import_zod.z.object({
270
463
  provider: ProviderSchema.optional(),
271
464
  permissions: PermissionsSchema.optional(),
272
465
  env: EnvSchema,
273
- hooks: HooksSchema
466
+ hooks: HooksSchema,
467
+ /** Plugin enablement map: plugin name -> enabled/disabled */
468
+ enabledPlugins: EnabledPluginsSchema,
469
+ /** Extra marketplace URLs for BundlePlugin discovery */
470
+ extraKnownMarketplaces: ExtraKnownMarketplacesSchema
274
471
  });
275
472
 
276
473
  // src/config/config-loader.ts
@@ -337,7 +534,9 @@ function mergeSettings(layers) {
337
534
  env: {
338
535
  ...merged.env ?? {},
339
536
  ...layer.env ?? {}
340
- }
537
+ },
538
+ enabledPlugins: merged.enabledPlugins !== void 0 || layer.enabledPlugins !== void 0 ? { ...merged.enabledPlugins ?? {}, ...layer.enabledPlugins ?? {} } : void 0,
539
+ extraKnownMarketplaces: layer.extraKnownMarketplaces ?? merged.extraKnownMarketplaces
341
540
  };
342
541
  }, {});
343
542
  }
@@ -355,25 +554,39 @@ function toResolvedConfig(merged) {
355
554
  deny: merged.permissions?.deny ?? DEFAULTS.permissions.deny
356
555
  },
357
556
  env: merged.env ?? DEFAULTS.env,
358
- hooks: merged.hooks ?? void 0
557
+ hooks: merged.hooks ?? void 0,
558
+ enabledPlugins: merged.enabledPlugins ?? void 0,
559
+ extraKnownMarketplaces: merged.extraKnownMarketplaces ?? void 0
359
560
  };
360
561
  }
562
+ function getSettingsPaths(cwd) {
563
+ const home = getHomeDir();
564
+ return [
565
+ (0, import_path.join)(home, ".robota", "settings.json"),
566
+ // 1. user (lowest)
567
+ (0, import_path.join)(cwd, ".robota", "settings.json"),
568
+ // 2. project
569
+ (0, import_path.join)(cwd, ".robota", "settings.local.json"),
570
+ // 3. project-local
571
+ (0, import_path.join)(cwd, ".claude", "settings.json"),
572
+ // 4. project, Claude Code compat
573
+ (0, import_path.join)(cwd, ".claude", "settings.local.json")
574
+ // 5. project-local (highest)
575
+ ];
576
+ }
361
577
  async function loadConfig(cwd) {
362
- const userSettingsPath = (0, import_path.join)(getHomeDir(), ".robota", "settings.json");
363
- const projectSettingsPath = (0, import_path.join)(cwd, ".robota", "settings.json");
364
- const localSettingsPath = (0, import_path.join)(cwd, ".robota", "settings.local.json");
365
- const rawLayers = [
366
- readJsonFile(userSettingsPath),
367
- readJsonFile(projectSettingsPath),
368
- readJsonFile(localSettingsPath)
369
- ].filter((v) => v !== void 0);
370
- const parsedLayers = rawLayers.map((raw, index) => {
578
+ const allPaths = getSettingsPaths(cwd);
579
+ const rawEntries = [];
580
+ for (const filePath of allPaths) {
581
+ const raw = readJsonFile(filePath);
582
+ if (raw !== void 0) {
583
+ rawEntries.push({ raw, path: filePath });
584
+ }
585
+ }
586
+ const parsedLayers = rawEntries.map(({ raw, path }) => {
371
587
  const result = SettingsSchema.safeParse(raw);
372
588
  if (!result.success) {
373
- const paths = [userSettingsPath, projectSettingsPath, localSettingsPath].filter(
374
- (_, i) => rawLayers[i] !== void 0
375
- );
376
- throw new Error(`Invalid settings in ${paths[index] ?? "unknown"}: ${result.error.message}`);
589
+ throw new Error(`Invalid settings in ${path}: ${result.error.message}`);
377
590
  }
378
591
  return resolveEnvRefs(result.data);
379
592
  });
@@ -587,6 +800,779 @@ function userPaths() {
587
800
  };
588
801
  }
589
802
 
803
+ // src/plugins/plugin-settings-store.ts
804
+ var import_node_fs = require("fs");
805
+ var import_node_path2 = require("path");
806
+ var PluginSettingsStore = class {
807
+ settingsPath;
808
+ constructor(settingsPath) {
809
+ this.settingsPath = settingsPath;
810
+ }
811
+ /** Read the full settings file from disk. */
812
+ readAll() {
813
+ if (!(0, import_node_fs.existsSync)(this.settingsPath)) {
814
+ return {};
815
+ }
816
+ try {
817
+ const raw = (0, import_node_fs.readFileSync)(this.settingsPath, "utf-8");
818
+ const data = JSON.parse(raw);
819
+ if (typeof data === "object" && data !== null) {
820
+ return data;
821
+ }
822
+ return {};
823
+ } catch {
824
+ return {};
825
+ }
826
+ }
827
+ /** Write the full settings file to disk. */
828
+ writeAll(settings) {
829
+ const dir = (0, import_node_path2.dirname)(this.settingsPath);
830
+ if (!(0, import_node_fs.existsSync)(dir)) {
831
+ (0, import_node_fs.mkdirSync)(dir, { recursive: true });
832
+ }
833
+ (0, import_node_fs.writeFileSync)(this.settingsPath, JSON.stringify(settings, null, 2), "utf-8");
834
+ }
835
+ // --- enabledPlugins ---
836
+ /** Get the enabledPlugins map. */
837
+ getEnabledPlugins() {
838
+ const settings = this.readAll();
839
+ const ep = settings.enabledPlugins;
840
+ if (typeof ep === "object" && ep !== null) {
841
+ return ep;
842
+ }
843
+ return {};
844
+ }
845
+ /** Set a single plugin's enabled state. */
846
+ setPluginEnabled(pluginId, enabled) {
847
+ const settings = this.readAll();
848
+ const ep = this.getEnabledPluginsFrom(settings);
849
+ ep[pluginId] = enabled;
850
+ settings.enabledPlugins = ep;
851
+ this.writeAll(settings);
852
+ }
853
+ /** Remove a plugin from enabledPlugins. */
854
+ removePluginEntry(pluginId) {
855
+ const settings = this.readAll();
856
+ const ep = this.getEnabledPluginsFrom(settings);
857
+ delete ep[pluginId];
858
+ settings.enabledPlugins = ep;
859
+ this.writeAll(settings);
860
+ }
861
+ // --- extraKnownMarketplaces ---
862
+ /** Get all persisted marketplace sources. */
863
+ getMarketplaceSources() {
864
+ const settings = this.readAll();
865
+ const extra = settings.extraKnownMarketplaces;
866
+ if (typeof extra === "object" && extra !== null) {
867
+ return extra;
868
+ }
869
+ return {};
870
+ }
871
+ /** Add or update a marketplace source. */
872
+ setMarketplaceSource(name, source) {
873
+ const settings = this.readAll();
874
+ const extra = this.getMarketplaceSourcesFrom(settings);
875
+ extra[name] = { source };
876
+ settings.extraKnownMarketplaces = extra;
877
+ this.writeAll(settings);
878
+ }
879
+ /** Remove a marketplace source. */
880
+ removeMarketplaceSource(name) {
881
+ const settings = this.readAll();
882
+ const extra = this.getMarketplaceSourcesFrom(settings);
883
+ delete extra[name];
884
+ settings.extraKnownMarketplaces = extra;
885
+ this.writeAll(settings);
886
+ }
887
+ // --- helpers ---
888
+ getEnabledPluginsFrom(settings) {
889
+ const ep = settings.enabledPlugins;
890
+ if (typeof ep === "object" && ep !== null) {
891
+ return ep;
892
+ }
893
+ return {};
894
+ }
895
+ getMarketplaceSourcesFrom(settings) {
896
+ const extra = settings.extraKnownMarketplaces;
897
+ if (typeof extra === "object" && extra !== null) {
898
+ return extra;
899
+ }
900
+ return {};
901
+ }
902
+ };
903
+
904
+ // src/plugins/bundle-plugin-loader.ts
905
+ var import_node_fs2 = require("fs");
906
+ var import_node_path3 = require("path");
907
+ function parseSkillFrontmatter(raw) {
908
+ const trimmed = raw.trimStart();
909
+ if (!trimmed.startsWith("---")) {
910
+ return { metadata: {}, content: raw };
911
+ }
912
+ const endIndex = trimmed.indexOf("---", 3);
913
+ if (endIndex === -1) {
914
+ return { metadata: {}, content: raw };
915
+ }
916
+ const frontmatterBlock = trimmed.slice(3, endIndex).trim();
917
+ const content = trimmed.slice(endIndex + 3).trimStart();
918
+ const metadata = {};
919
+ for (const line of frontmatterBlock.split("\n")) {
920
+ const colonIndex = line.indexOf(":");
921
+ if (colonIndex === -1) continue;
922
+ const key = line.slice(0, colonIndex).trim();
923
+ let value = line.slice(colonIndex + 1).trim();
924
+ if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) {
925
+ const inner = value.slice(1, -1);
926
+ value = inner.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
927
+ }
928
+ if (key) {
929
+ metadata[key] = value;
930
+ }
931
+ }
932
+ return { metadata, content };
933
+ }
934
+ function validateManifest(data) {
935
+ if (typeof data !== "object" || data === null) return null;
936
+ const obj = data;
937
+ if (typeof obj.name !== "string") return null;
938
+ if (typeof obj.version !== "string") return null;
939
+ if (typeof obj.description !== "string") return null;
940
+ const features = typeof obj.features === "object" && obj.features !== null ? obj.features : {};
941
+ return {
942
+ name: obj.name,
943
+ version: obj.version,
944
+ description: obj.description,
945
+ features: {
946
+ commands: features.commands === true ? true : void 0,
947
+ agents: features.agents === true ? true : void 0,
948
+ skills: features.skills === true ? true : void 0,
949
+ hooks: features.hooks === true ? true : void 0,
950
+ mcp: features.mcp === true ? true : void 0
951
+ }
952
+ };
953
+ }
954
+ function getSortedSubdirs(dirPath) {
955
+ if (!(0, import_node_fs2.existsSync)(dirPath)) return [];
956
+ try {
957
+ const entries = (0, import_node_fs2.readdirSync)(dirPath, { withFileTypes: true });
958
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
959
+ } catch {
960
+ return [];
961
+ }
962
+ }
963
+ var BundlePluginLoader = class {
964
+ pluginsDir;
965
+ enabledPlugins;
966
+ constructor(pluginsDir, enabledPlugins) {
967
+ this.pluginsDir = pluginsDir;
968
+ this.enabledPlugins = enabledPlugins ?? {};
969
+ }
970
+ /** Load all discovered and enabled bundle plugins (sync). */
971
+ loadPluginsSync() {
972
+ return this.discoverAndLoad();
973
+ }
974
+ /** Load all discovered and enabled bundle plugins (async wrapper). */
975
+ async loadAll() {
976
+ return this.discoverAndLoad();
977
+ }
978
+ /**
979
+ * Discover and load plugins from the cache directory.
980
+ *
981
+ * Directory structure: `<pluginsDir>/cache/<marketplace>/<plugin>/<version>/`
982
+ * For each marketplace/plugin pair, the latest version (lexicographically last) is loaded.
983
+ */
984
+ discoverAndLoad() {
985
+ const cacheDir = (0, import_node_path3.join)(this.pluginsDir, "cache");
986
+ if (!(0, import_node_fs2.existsSync)(cacheDir)) {
987
+ return [];
988
+ }
989
+ const results = [];
990
+ const marketplaces = getSortedSubdirs(cacheDir);
991
+ for (const marketplace of marketplaces) {
992
+ const marketplaceDir = (0, import_node_path3.join)(cacheDir, marketplace);
993
+ const plugins = getSortedSubdirs(marketplaceDir);
994
+ for (const pluginName of plugins) {
995
+ const pluginDir = (0, import_node_path3.join)(marketplaceDir, pluginName);
996
+ const versions = getSortedSubdirs(pluginDir);
997
+ if (versions.length === 0) continue;
998
+ const latestVersion = versions[versions.length - 1];
999
+ const versionDir = (0, import_node_path3.join)(pluginDir, latestVersion);
1000
+ const manifestPath = (0, import_node_path3.join)(versionDir, ".claude-plugin", "plugin.json");
1001
+ if (!(0, import_node_fs2.existsSync)(manifestPath)) continue;
1002
+ const manifest = this.readManifest(manifestPath);
1003
+ if (!manifest) continue;
1004
+ const pluginId = `${manifest.name}@${marketplace}`;
1005
+ if (this.isDisabled(pluginId, manifest.name)) continue;
1006
+ const loaded = this.loadPlugin(versionDir, manifest);
1007
+ results.push(loaded);
1008
+ }
1009
+ }
1010
+ return results;
1011
+ }
1012
+ /** Read and validate a plugin.json manifest. Returns null on failure. */
1013
+ readManifest(path) {
1014
+ try {
1015
+ const raw = (0, import_node_fs2.readFileSync)(path, "utf-8");
1016
+ const data = JSON.parse(raw);
1017
+ return validateManifest(data);
1018
+ } catch {
1019
+ return null;
1020
+ }
1021
+ }
1022
+ /**
1023
+ * Check if a plugin is explicitly disabled.
1024
+ * Checks both `name@marketplace` and `name` keys.
1025
+ * Plugins not listed in enabledPlugins are enabled by default.
1026
+ */
1027
+ isDisabled(pluginId, pluginName) {
1028
+ if (pluginId in this.enabledPlugins) {
1029
+ return this.enabledPlugins[pluginId] === false;
1030
+ }
1031
+ if (pluginName in this.enabledPlugins) {
1032
+ return this.enabledPlugins[pluginName] === false;
1033
+ }
1034
+ return false;
1035
+ }
1036
+ /** Load a single plugin's skills, hooks, agents, and MCP config. */
1037
+ loadPlugin(pluginDir, manifest) {
1038
+ return {
1039
+ manifest,
1040
+ skills: this.loadSkills(pluginDir, manifest.name),
1041
+ commands: this.loadCommands(pluginDir, manifest.name),
1042
+ hooks: this.loadHooks(pluginDir),
1043
+ mcpConfig: this.loadMcpConfig(pluginDir),
1044
+ agents: this.loadAgents(pluginDir),
1045
+ pluginDir
1046
+ };
1047
+ }
1048
+ /** Load skills from the plugin's skills/ directory. */
1049
+ loadSkills(pluginDir, pluginName) {
1050
+ const skillsDir = (0, import_node_path3.join)(pluginDir, "skills");
1051
+ if (!(0, import_node_fs2.existsSync)(skillsDir)) return [];
1052
+ const entries = (0, import_node_fs2.readdirSync)(skillsDir, { withFileTypes: true });
1053
+ const skills = [];
1054
+ for (const entry of entries) {
1055
+ if (!entry.isDirectory()) continue;
1056
+ const skillFile = (0, import_node_path3.join)(skillsDir, entry.name, "SKILL.md");
1057
+ if (!(0, import_node_fs2.existsSync)(skillFile)) continue;
1058
+ const raw = (0, import_node_fs2.readFileSync)(skillFile, "utf-8");
1059
+ const { metadata, content } = parseSkillFrontmatter(raw);
1060
+ const description = typeof metadata.description === "string" ? metadata.description : "";
1061
+ const skill = {
1062
+ name: entry.name,
1063
+ description,
1064
+ skillContent: content,
1065
+ ...metadata
1066
+ };
1067
+ skills.push(skill);
1068
+ }
1069
+ return skills;
1070
+ }
1071
+ /** Load commands from the plugin's commands/ directory (flat .md files). */
1072
+ loadCommands(pluginDir, pluginName) {
1073
+ const commandsDir = (0, import_node_path3.join)(pluginDir, "commands");
1074
+ if (!(0, import_node_fs2.existsSync)(commandsDir)) return [];
1075
+ const entries = (0, import_node_fs2.readdirSync)(commandsDir, { withFileTypes: true });
1076
+ const commands = [];
1077
+ for (const entry of entries) {
1078
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
1079
+ const raw = (0, import_node_fs2.readFileSync)((0, import_node_path3.join)(commandsDir, entry.name), "utf-8");
1080
+ const { metadata, content } = parseSkillFrontmatter(raw);
1081
+ const name = typeof metadata.name === "string" ? metadata.name : entry.name.replace(/\.md$/, "");
1082
+ const description = typeof metadata.description === "string" ? metadata.description : "";
1083
+ commands.push({
1084
+ ...metadata,
1085
+ name: `${pluginName}:${name}`,
1086
+ description,
1087
+ skillContent: content
1088
+ });
1089
+ }
1090
+ return commands;
1091
+ }
1092
+ /** Load hooks from hooks/hooks.json if present. */
1093
+ loadHooks(pluginDir) {
1094
+ const hooksPath = (0, import_node_path3.join)(pluginDir, "hooks", "hooks.json");
1095
+ if (!(0, import_node_fs2.existsSync)(hooksPath)) return {};
1096
+ try {
1097
+ const raw = (0, import_node_fs2.readFileSync)(hooksPath, "utf-8");
1098
+ const data = JSON.parse(raw);
1099
+ if (typeof data === "object" && data !== null) {
1100
+ return data;
1101
+ }
1102
+ return {};
1103
+ } catch {
1104
+ return {};
1105
+ }
1106
+ }
1107
+ /** Load MCP server configuration if present. Checks `.mcp.json` at plugin root first. */
1108
+ loadMcpConfig(pluginDir) {
1109
+ const primaryPath = (0, import_node_path3.join)(pluginDir, ".mcp.json");
1110
+ const fallbackPath = (0, import_node_path3.join)(pluginDir, ".claude-plugin", "mcp.json");
1111
+ const mcpPath = (0, import_node_fs2.existsSync)(primaryPath) ? primaryPath : fallbackPath;
1112
+ if (!(0, import_node_fs2.existsSync)(mcpPath)) return void 0;
1113
+ try {
1114
+ const raw = (0, import_node_fs2.readFileSync)(mcpPath, "utf-8");
1115
+ return JSON.parse(raw);
1116
+ } catch {
1117
+ return void 0;
1118
+ }
1119
+ }
1120
+ /** Load agent definitions from agents/ directory if present. */
1121
+ loadAgents(pluginDir) {
1122
+ const agentsDir = (0, import_node_path3.join)(pluginDir, "agents");
1123
+ if (!(0, import_node_fs2.existsSync)(agentsDir)) return [];
1124
+ try {
1125
+ const entries = (0, import_node_fs2.readdirSync)(agentsDir, { withFileTypes: true });
1126
+ return entries.filter((e) => e.isDirectory() || e.name.endsWith(".md")).map((e) => e.name.replace(/\.md$/, ""));
1127
+ } catch {
1128
+ return [];
1129
+ }
1130
+ }
1131
+ };
1132
+
1133
+ // src/plugins/bundle-plugin-installer.ts
1134
+ var import_node_child_process = require("child_process");
1135
+ var import_node_fs3 = require("fs");
1136
+ var import_node_path4 = require("path");
1137
+ var GIT_CLONE_TIMEOUT_MS = 6e4;
1138
+ var BundlePluginInstaller = class {
1139
+ pluginsDir;
1140
+ cacheDir;
1141
+ registryPath;
1142
+ settingsStore;
1143
+ marketplaceClient;
1144
+ exec;
1145
+ constructor(options) {
1146
+ this.pluginsDir = options.pluginsDir;
1147
+ this.cacheDir = (0, import_node_path4.join)(this.pluginsDir, "cache");
1148
+ this.registryPath = (0, import_node_path4.join)(this.pluginsDir, "installed_plugins.json");
1149
+ this.settingsStore = options.settingsStore;
1150
+ this.marketplaceClient = options.marketplaceClient;
1151
+ this.exec = options.exec ?? this.defaultExec;
1152
+ }
1153
+ /**
1154
+ * Install a plugin from a marketplace.
1155
+ *
1156
+ * 1. Read marketplace manifest to find the plugin entry.
1157
+ * 2. Resolve source (relative path, github, or url).
1158
+ * 3. Copy/clone to `cache/<marketplace>/<plugin>/<version>/`.
1159
+ * 4. Record in `installed_plugins.json`.
1160
+ */
1161
+ async install(pluginName, marketplaceName) {
1162
+ const manifest = this.marketplaceClient.fetchManifest(marketplaceName);
1163
+ const entry = manifest.plugins.find((p) => p.name === pluginName);
1164
+ if (!entry) {
1165
+ throw new Error(`Plugin "${pluginName}" not found in marketplace "${marketplaceName}"`);
1166
+ }
1167
+ const version = this.resolveVersion(entry, marketplaceName);
1168
+ const targetDir = (0, import_node_path4.join)(this.cacheDir, marketplaceName, pluginName, version);
1169
+ if ((0, import_node_fs3.existsSync)(targetDir)) {
1170
+ throw new Error(
1171
+ `Plugin "${pluginName}" version "${version}" is already installed from "${marketplaceName}"`
1172
+ );
1173
+ }
1174
+ this.resolveAndInstall(entry.source, marketplaceName, pluginName, targetDir);
1175
+ const pluginId = `${pluginName}@${marketplaceName}`;
1176
+ const registry = this.readRegistry();
1177
+ registry[pluginId] = {
1178
+ pluginName,
1179
+ marketplace: marketplaceName,
1180
+ version,
1181
+ installPath: targetDir,
1182
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
1183
+ };
1184
+ this.writeRegistry(registry);
1185
+ }
1186
+ /**
1187
+ * Uninstall a plugin.
1188
+ * Removes from cache and from installed_plugins.json.
1189
+ */
1190
+ async uninstall(pluginId) {
1191
+ const registry = this.readRegistry();
1192
+ const record = registry[pluginId];
1193
+ if (!record) {
1194
+ throw new Error(`Plugin "${pluginId}" is not installed`);
1195
+ }
1196
+ if ((0, import_node_fs3.existsSync)(record.installPath)) {
1197
+ (0, import_node_fs3.rmSync)(record.installPath, { recursive: true, force: true });
1198
+ }
1199
+ delete registry[pluginId];
1200
+ this.writeRegistry(registry);
1201
+ this.settingsStore.removePluginEntry(pluginId);
1202
+ }
1203
+ /** Enable a plugin by setting its enabledPlugins entry to true. */
1204
+ async enable(pluginId) {
1205
+ this.settingsStore.setPluginEnabled(pluginId, true);
1206
+ }
1207
+ /** Disable a plugin by setting its enabledPlugins entry to false. */
1208
+ async disable(pluginId) {
1209
+ this.settingsStore.setPluginEnabled(pluginId, false);
1210
+ }
1211
+ /** Get all installed plugins. */
1212
+ getInstalledPlugins() {
1213
+ return this.readRegistry();
1214
+ }
1215
+ /** Get plugins installed from a specific marketplace. */
1216
+ getPluginsByMarketplace(marketplaceName) {
1217
+ const registry = this.readRegistry();
1218
+ return Object.values(registry).filter((r) => r.marketplace === marketplaceName);
1219
+ }
1220
+ // --- Private helpers ---
1221
+ /** Resolve the version for a plugin entry. */
1222
+ resolveVersion(entry, marketplaceName) {
1223
+ const entryWithVersion = entry;
1224
+ if (typeof entryWithVersion.version === "string" && entryWithVersion.version) {
1225
+ return entryWithVersion.version;
1226
+ }
1227
+ return this.marketplaceClient.getMarketplaceSha(marketplaceName);
1228
+ }
1229
+ /** Resolve the source and install the plugin. */
1230
+ resolveAndInstall(source, marketplaceName, pluginName, targetDir) {
1231
+ (0, import_node_fs3.mkdirSync)(targetDir, { recursive: true });
1232
+ if (typeof source === "string") {
1233
+ const marketplaceDir = this.marketplaceClient.getMarketplaceDir(marketplaceName);
1234
+ const sourcePath = (0, import_node_path4.join)(marketplaceDir, source);
1235
+ if (!(0, import_node_fs3.existsSync)(sourcePath)) {
1236
+ (0, import_node_fs3.rmSync)(targetDir, { recursive: true, force: true });
1237
+ throw new Error(
1238
+ `Plugin source path "${source}" not found in marketplace "${marketplaceName}"`
1239
+ );
1240
+ }
1241
+ (0, import_node_fs3.cpSync)(sourcePath, targetDir, { recursive: true });
1242
+ } else if (source.type === "github") {
1243
+ const repoUrl = `https://github.com/${source.repo}.git`;
1244
+ this.cloneToDir(repoUrl, targetDir, pluginName);
1245
+ } else if (source.type === "url") {
1246
+ (0, import_node_fs3.rmSync)(targetDir, { recursive: true, force: true });
1247
+ throw new Error("URL source installation is not yet supported");
1248
+ }
1249
+ }
1250
+ /** Clone a git repository to the target directory. */
1251
+ cloneToDir(repoUrl, targetDir, pluginName) {
1252
+ (0, import_node_fs3.rmSync)(targetDir, { recursive: true, force: true });
1253
+ const command = `git clone --depth 1 ${repoUrl} ${targetDir}`;
1254
+ try {
1255
+ this.exec(command, { timeout: GIT_CLONE_TIMEOUT_MS, stdio: "pipe" });
1256
+ } catch (error) {
1257
+ const message = error instanceof Error ? error.message : String(error);
1258
+ throw new Error(`Failed to clone plugin "${pluginName}": ${message}`);
1259
+ }
1260
+ }
1261
+ /** Read the installed_plugins.json registry. */
1262
+ readRegistry() {
1263
+ if (!(0, import_node_fs3.existsSync)(this.registryPath)) {
1264
+ return {};
1265
+ }
1266
+ try {
1267
+ const raw = (0, import_node_fs3.readFileSync)(this.registryPath, "utf-8");
1268
+ const data = JSON.parse(raw);
1269
+ if (typeof data === "object" && data !== null) {
1270
+ return data;
1271
+ }
1272
+ return {};
1273
+ } catch {
1274
+ return {};
1275
+ }
1276
+ }
1277
+ /** Write the installed_plugins.json registry. */
1278
+ writeRegistry(registry) {
1279
+ const dir = (0, import_node_path4.dirname)(this.registryPath);
1280
+ if (!(0, import_node_fs3.existsSync)(dir)) {
1281
+ (0, import_node_fs3.mkdirSync)(dir, { recursive: true });
1282
+ }
1283
+ (0, import_node_fs3.writeFileSync)(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
1284
+ }
1285
+ /** Default exec implementation using child_process. */
1286
+ defaultExec(command, options) {
1287
+ return (0, import_node_child_process.execSync)(command, { timeout: options.timeout, stdio: "pipe" });
1288
+ }
1289
+ };
1290
+
1291
+ // src/plugins/marketplace-client.ts
1292
+ var import_node_child_process2 = require("child_process");
1293
+ var import_node_fs4 = require("fs");
1294
+ var import_node_path5 = require("path");
1295
+ var GIT_TIMEOUT_MS = 6e4;
1296
+ var MarketplaceClient = class {
1297
+ pluginsDir;
1298
+ exec;
1299
+ marketplacesDir;
1300
+ registryPath;
1301
+ constructor(options) {
1302
+ this.pluginsDir = options.pluginsDir;
1303
+ this.exec = options.exec ?? this.defaultExec;
1304
+ this.marketplacesDir = (0, import_node_path5.join)(this.pluginsDir, "marketplaces");
1305
+ this.registryPath = (0, import_node_path5.join)(this.pluginsDir, "known_marketplaces.json");
1306
+ }
1307
+ /**
1308
+ * Add a marketplace by cloning its repository.
1309
+ *
1310
+ * 1. Parse source: `owner/repo` string becomes a GitHub source.
1311
+ * 2. Shallow git clone (`--depth 1`) to `marketplaces/<name>/`.
1312
+ * 3. Read `.claude-plugin/marketplace.json` for the `name` field.
1313
+ * 4. Register in `known_marketplaces.json`.
1314
+ *
1315
+ * Returns the registered marketplace name from the manifest.
1316
+ */
1317
+ addMarketplace(source) {
1318
+ const tempName = "temp-" + Date.now().toString(36);
1319
+ const tempDir = (0, import_node_path5.join)(this.marketplacesDir, tempName);
1320
+ (0, import_node_fs4.mkdirSync)(this.marketplacesDir, { recursive: true });
1321
+ if (source.type === "local") {
1322
+ if (!(0, import_node_fs4.existsSync)(source.path)) {
1323
+ throw new Error(`Local marketplace path does not exist: ${source.path}`);
1324
+ }
1325
+ (0, import_node_fs4.cpSync)(source.path, tempDir, { recursive: true });
1326
+ } else {
1327
+ const cloneUrl = this.resolveCloneUrl(source);
1328
+ const command = `git clone --depth 1 ${cloneUrl} ${tempDir}`;
1329
+ try {
1330
+ this.exec(command, { timeout: GIT_TIMEOUT_MS, stdio: "pipe" });
1331
+ } catch (error) {
1332
+ const message = error instanceof Error ? error.message : String(error);
1333
+ throw new Error(`Failed to clone marketplace: ${message}`);
1334
+ }
1335
+ }
1336
+ const manifestPath = (0, import_node_path5.join)(tempDir, ".claude-plugin", "marketplace.json");
1337
+ if (!(0, import_node_fs4.existsSync)(manifestPath)) {
1338
+ (0, import_node_fs4.rmSync)(tempDir, { recursive: true, force: true });
1339
+ throw new Error(
1340
+ source.type === "local" ? "Local directory does not contain .claude-plugin/marketplace.json" : "Cloned repository does not contain .claude-plugin/marketplace.json"
1341
+ );
1342
+ }
1343
+ const manifest = this.readManifestFromPath(manifestPath);
1344
+ const name = manifest.name;
1345
+ if (!name) {
1346
+ (0, import_node_fs4.rmSync)(tempDir, { recursive: true, force: true });
1347
+ throw new Error('Marketplace manifest does not contain a "name" field');
1348
+ }
1349
+ const registry = this.readRegistry();
1350
+ if (registry[name]) {
1351
+ (0, import_node_fs4.rmSync)(tempDir, { recursive: true, force: true });
1352
+ throw new Error(`Marketplace "${name}" already exists`);
1353
+ }
1354
+ const finalDir = (0, import_node_path5.join)(this.marketplacesDir, name);
1355
+ (0, import_node_fs4.renameSync)(tempDir, finalDir);
1356
+ registry[name] = {
1357
+ source,
1358
+ installLocation: finalDir,
1359
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1360
+ };
1361
+ this.writeRegistry(registry);
1362
+ return name;
1363
+ }
1364
+ /**
1365
+ * Remove a marketplace.
1366
+ * Uninstalls all plugins from that marketplace, then deletes the clone directory
1367
+ * and removes from the registry.
1368
+ */
1369
+ removeMarketplace(name) {
1370
+ const registry = this.readRegistry();
1371
+ const entry = registry[name];
1372
+ if (!entry) {
1373
+ throw new Error(`Marketplace "${name}" not found`);
1374
+ }
1375
+ this.removeInstalledPluginsForMarketplace(name);
1376
+ if ((0, import_node_fs4.existsSync)(entry.installLocation)) {
1377
+ (0, import_node_fs4.rmSync)(entry.installLocation, { recursive: true, force: true });
1378
+ }
1379
+ delete registry[name];
1380
+ this.writeRegistry(registry);
1381
+ }
1382
+ /**
1383
+ * Update a marketplace by running git pull on its clone.
1384
+ * The manifest is re-read from disk on demand (via fetchManifest), so the
1385
+ * updated manifest is automatically available after pull.
1386
+ *
1387
+ * TODO: After pull, detect version changes in installed plugins and offer
1388
+ * to update them (re-install at new version).
1389
+ */
1390
+ updateMarketplace(name) {
1391
+ const registry = this.readRegistry();
1392
+ const entry = registry[name];
1393
+ if (!entry) {
1394
+ throw new Error(`Marketplace "${name}" not found`);
1395
+ }
1396
+ if (!(0, import_node_fs4.existsSync)(entry.installLocation)) {
1397
+ throw new Error(`Marketplace directory for "${name}" does not exist`);
1398
+ }
1399
+ if (entry.source.type === "local") {
1400
+ const localSource = entry.source;
1401
+ if (!(0, import_node_fs4.existsSync)(localSource.path)) {
1402
+ throw new Error(`Local marketplace path does not exist: ${localSource.path}`);
1403
+ }
1404
+ (0, import_node_fs4.rmSync)(entry.installLocation, { recursive: true, force: true });
1405
+ (0, import_node_fs4.cpSync)(localSource.path, entry.installLocation, { recursive: true });
1406
+ } else {
1407
+ const command = `git -C ${entry.installLocation} pull`;
1408
+ try {
1409
+ this.exec(command, { timeout: GIT_TIMEOUT_MS, stdio: "pipe" });
1410
+ } catch (error) {
1411
+ const message = error instanceof Error ? error.message : String(error);
1412
+ throw new Error(`Failed to update marketplace "${name}": ${message}`);
1413
+ }
1414
+ }
1415
+ entry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
1416
+ this.writeRegistry(registry);
1417
+ }
1418
+ /** List all registered marketplaces. */
1419
+ listMarketplaces() {
1420
+ const registry = this.readRegistry();
1421
+ return Object.entries(registry).map(([name, entry]) => ({
1422
+ name,
1423
+ source: entry.source,
1424
+ lastUpdated: entry.lastUpdated
1425
+ }));
1426
+ }
1427
+ /**
1428
+ * Read the marketplace manifest from a registered marketplace's clone.
1429
+ */
1430
+ fetchManifest(marketplaceName) {
1431
+ const registry = this.readRegistry();
1432
+ const entry = registry[marketplaceName];
1433
+ if (!entry) {
1434
+ throw new Error(`Marketplace "${marketplaceName}" not found`);
1435
+ }
1436
+ const manifestPath = (0, import_node_path5.join)(entry.installLocation, ".claude-plugin", "marketplace.json");
1437
+ if (!(0, import_node_fs4.existsSync)(manifestPath)) {
1438
+ throw new Error(
1439
+ `Marketplace "${marketplaceName}" does not contain .claude-plugin/marketplace.json`
1440
+ );
1441
+ }
1442
+ return this.readManifestFromPath(manifestPath);
1443
+ }
1444
+ /** Get the clone directory path for a registered marketplace. */
1445
+ getMarketplaceDir(name) {
1446
+ const registry = this.readRegistry();
1447
+ const entry = registry[name];
1448
+ if (!entry) {
1449
+ throw new Error(`Marketplace "${name}" not found`);
1450
+ }
1451
+ return entry.installLocation;
1452
+ }
1453
+ /**
1454
+ * Get the current git SHA (first 12 chars) for a marketplace clone.
1455
+ * Used as a version identifier when plugins lack explicit versions.
1456
+ */
1457
+ getMarketplaceSha(name) {
1458
+ const dir = this.getMarketplaceDir(name);
1459
+ try {
1460
+ const result = this.exec(`git -C ${dir} rev-parse HEAD`, {
1461
+ timeout: GIT_TIMEOUT_MS,
1462
+ stdio: "pipe"
1463
+ });
1464
+ return result.toString().trim().slice(0, 12);
1465
+ } catch {
1466
+ return "unknown";
1467
+ }
1468
+ }
1469
+ /** List all available plugins across all marketplaces. */
1470
+ listAvailablePlugins() {
1471
+ const results = [];
1472
+ const marketplaces = this.listMarketplaces();
1473
+ for (const { name } of marketplaces) {
1474
+ try {
1475
+ const manifest = this.fetchManifest(name);
1476
+ for (const plugin of manifest.plugins) {
1477
+ results.push({ ...plugin, marketplace: name });
1478
+ }
1479
+ } catch {
1480
+ }
1481
+ }
1482
+ return results;
1483
+ }
1484
+ // --- Private helpers ---
1485
+ /** Resolve a marketplace source to a git clone URL. */
1486
+ resolveCloneUrl(source) {
1487
+ switch (source.type) {
1488
+ case "github":
1489
+ return `https://github.com/${source.repo}.git`;
1490
+ case "git":
1491
+ return source.url;
1492
+ case "local":
1493
+ throw new Error("Local source type does not use git cloning");
1494
+ case "url":
1495
+ throw new Error("URL marketplace source is not yet supported");
1496
+ }
1497
+ }
1498
+ /**
1499
+ * Remove all installed plugins that belong to a given marketplace.
1500
+ * Reads installed_plugins.json, deletes cache directories for matching plugins,
1501
+ * and updates the registry.
1502
+ */
1503
+ removeInstalledPluginsForMarketplace(marketplaceName) {
1504
+ const installedPath = (0, import_node_path5.join)(this.pluginsDir, "installed_plugins.json");
1505
+ if (!(0, import_node_fs4.existsSync)(installedPath)) return;
1506
+ let registry;
1507
+ try {
1508
+ const raw = (0, import_node_fs4.readFileSync)(installedPath, "utf-8");
1509
+ const data = JSON.parse(raw);
1510
+ if (typeof data !== "object" || data === null) return;
1511
+ registry = data;
1512
+ } catch {
1513
+ return;
1514
+ }
1515
+ let changed = false;
1516
+ for (const [pluginId, record] of Object.entries(registry)) {
1517
+ if (record.marketplace === marketplaceName) {
1518
+ if (record.installPath && (0, import_node_fs4.existsSync)(record.installPath)) {
1519
+ (0, import_node_fs4.rmSync)(record.installPath, { recursive: true, force: true });
1520
+ }
1521
+ delete registry[pluginId];
1522
+ changed = true;
1523
+ }
1524
+ }
1525
+ if (changed) {
1526
+ const dir = (0, import_node_path5.dirname)(installedPath);
1527
+ if (!(0, import_node_fs4.existsSync)(dir)) {
1528
+ (0, import_node_fs4.mkdirSync)(dir, { recursive: true });
1529
+ }
1530
+ (0, import_node_fs4.writeFileSync)(installedPath, JSON.stringify(registry, null, 2), "utf-8");
1531
+ }
1532
+ }
1533
+ /** Read and parse a marketplace.json from a file path. */
1534
+ readManifestFromPath(path) {
1535
+ const raw = (0, import_node_fs4.readFileSync)(path, "utf-8");
1536
+ const data = JSON.parse(raw);
1537
+ if (typeof data !== "object" || data === null) {
1538
+ throw new Error("Invalid marketplace manifest: not an object");
1539
+ }
1540
+ const obj = data;
1541
+ if (typeof obj.name !== "string") {
1542
+ throw new Error('Invalid marketplace manifest: missing "name" field');
1543
+ }
1544
+ return data;
1545
+ }
1546
+ /** Read the known_marketplaces.json registry. */
1547
+ readRegistry() {
1548
+ if (!(0, import_node_fs4.existsSync)(this.registryPath)) {
1549
+ return {};
1550
+ }
1551
+ try {
1552
+ const raw = (0, import_node_fs4.readFileSync)(this.registryPath, "utf-8");
1553
+ const data = JSON.parse(raw);
1554
+ if (typeof data === "object" && data !== null) {
1555
+ return data;
1556
+ }
1557
+ return {};
1558
+ } catch {
1559
+ return {};
1560
+ }
1561
+ }
1562
+ /** Write the known_marketplaces.json registry. */
1563
+ writeRegistry(registry) {
1564
+ const dir = (0, import_node_path5.dirname)(this.registryPath);
1565
+ if (!(0, import_node_fs4.existsSync)(dir)) {
1566
+ (0, import_node_fs4.mkdirSync)(dir, { recursive: true });
1567
+ }
1568
+ (0, import_node_fs4.writeFileSync)(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
1569
+ }
1570
+ /** Default exec implementation using child_process. */
1571
+ defaultExec(command, options) {
1572
+ return (0, import_node_child_process2.execSync)(command, { timeout: options.timeout, stdio: "pipe" });
1573
+ }
1574
+ };
1575
+
590
1576
  // src/tools/agent-tool.ts
591
1577
  var import_zod2 = require("zod");
592
1578
  var import_agent_tools2 = require("@robota-sdk/agent-tools");
@@ -668,8 +1654,14 @@ var import_agent_tools7 = require("@robota-sdk/agent-tools");
668
1654
  var import_agent_tools8 = require("@robota-sdk/agent-tools");
669
1655
  // Annotate the CommonJS export names for ESM import in node:
670
1656
  0 && (module.exports = {
1657
+ AgentExecutor,
1658
+ BundlePluginInstaller,
1659
+ BundlePluginLoader,
671
1660
  DEFAULT_TOOL_DESCRIPTIONS,
672
1661
  FileSessionLogger,
1662
+ MarketplaceClient,
1663
+ PluginSettingsStore,
1664
+ PromptExecutor,
673
1665
  Session,
674
1666
  SessionStore,
675
1667
  SilentSessionLogger,