@robota-sdk/agent-sdk 3.0.0-beta.3 → 3.0.0-beta.31
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/node/index.cjs +1250 -145
- package/dist/node/index.d.cts +569 -195
- package/dist/node/index.d.ts +569 -195
- package/dist/node/index.js +1251 -139
- package/package.json +5 -5
package/dist/node/index.cjs
CHANGED
|
@@ -30,43 +30,359 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
AgentExecutor: () => AgentExecutor,
|
|
34
|
+
BundlePluginInstaller: () => BundlePluginInstaller,
|
|
35
|
+
BundlePluginLoader: () => BundlePluginLoader,
|
|
36
|
+
DEFAULT_TOOL_DESCRIPTIONS: () => DEFAULT_TOOL_DESCRIPTIONS,
|
|
37
|
+
FileSessionLogger: () => import_agent_sessions3.FileSessionLogger,
|
|
38
|
+
MarketplaceClient: () => MarketplaceClient,
|
|
39
|
+
PluginSettingsStore: () => PluginSettingsStore,
|
|
40
|
+
PromptExecutor: () => PromptExecutor,
|
|
41
|
+
Session: () => import_agent_sessions2.Session,
|
|
42
|
+
SessionStore: () => import_agent_sessions4.SessionStore,
|
|
43
|
+
SilentSessionLogger: () => import_agent_sessions3.SilentSessionLogger,
|
|
37
44
|
TRUST_TO_MODE: () => import_agent_core.TRUST_TO_MODE,
|
|
38
45
|
agentTool: () => agentTool,
|
|
39
|
-
bashTool: () =>
|
|
46
|
+
bashTool: () => import_agent_tools3.bashTool,
|
|
40
47
|
buildSystemPrompt: () => buildSystemPrompt,
|
|
48
|
+
createDefaultTools: () => createDefaultTools,
|
|
49
|
+
createProvider: () => createProvider,
|
|
50
|
+
createSession: () => createSession,
|
|
41
51
|
detectProject: () => detectProject,
|
|
42
|
-
editTool: () =>
|
|
52
|
+
editTool: () => import_agent_tools6.editTool,
|
|
43
53
|
evaluatePermission: () => import_agent_core2.evaluatePermission,
|
|
44
|
-
globTool: () =>
|
|
45
|
-
grepTool: () =>
|
|
54
|
+
globTool: () => import_agent_tools7.globTool,
|
|
55
|
+
grepTool: () => import_agent_tools8.grepTool,
|
|
46
56
|
loadConfig: () => loadConfig,
|
|
47
57
|
loadContext: () => loadContext,
|
|
48
58
|
projectPaths: () => projectPaths,
|
|
49
59
|
promptForApproval: () => promptForApproval,
|
|
50
60
|
query: () => query,
|
|
51
|
-
readTool: () =>
|
|
61
|
+
readTool: () => import_agent_tools4.readTool,
|
|
52
62
|
runHooks: () => import_agent_core3.runHooks,
|
|
53
63
|
setAgentToolDeps: () => setAgentToolDeps,
|
|
54
64
|
userPaths: () => userPaths,
|
|
55
|
-
writeTool: () =>
|
|
65
|
+
writeTool: () => import_agent_tools5.writeTool
|
|
56
66
|
});
|
|
57
67
|
module.exports = __toCommonJS(index_exports);
|
|
58
68
|
|
|
59
69
|
// src/types.ts
|
|
60
70
|
var import_agent_core = require("@robota-sdk/agent-core");
|
|
61
71
|
|
|
62
|
-
// src/
|
|
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
|
+
|
|
179
|
+
// src/assembly/create-session.ts
|
|
63
180
|
var import_agent_sessions = require("@robota-sdk/agent-sessions");
|
|
64
181
|
|
|
65
|
-
// src/
|
|
66
|
-
var
|
|
182
|
+
// src/context/system-prompt-builder.ts
|
|
183
|
+
var TRUST_LEVEL_DESCRIPTIONS = {
|
|
184
|
+
safe: "safe (read-only / plan mode \u2014 only read-access tools are available)",
|
|
185
|
+
moderate: "moderate (default mode \u2014 write and bash tools require approval)",
|
|
186
|
+
full: "full (acceptEdits mode \u2014 file writes are auto-approved; bash requires approval)"
|
|
187
|
+
};
|
|
188
|
+
function buildProjectSection(info) {
|
|
189
|
+
const lines = ["## Current Project"];
|
|
190
|
+
if (info.name !== void 0) {
|
|
191
|
+
lines.push(`- **Name:** ${info.name}`);
|
|
192
|
+
}
|
|
193
|
+
if (info.type !== "unknown") {
|
|
194
|
+
lines.push(`- **Type:** ${info.type}`);
|
|
195
|
+
}
|
|
196
|
+
if (info.language !== "unknown") {
|
|
197
|
+
lines.push(`- **Language:** ${info.language}`);
|
|
198
|
+
}
|
|
199
|
+
if (info.packageManager !== void 0) {
|
|
200
|
+
lines.push(`- **Package manager:** ${info.packageManager}`);
|
|
201
|
+
}
|
|
202
|
+
return lines.join("\n");
|
|
203
|
+
}
|
|
204
|
+
function buildToolsSection(descriptions) {
|
|
205
|
+
if (descriptions.length === 0) {
|
|
206
|
+
return "";
|
|
207
|
+
}
|
|
208
|
+
const lines = ["## Available Tools", ...descriptions.map((d) => `- ${d}`)];
|
|
209
|
+
return lines.join("\n");
|
|
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
|
+
}
|
|
224
|
+
function buildSystemPrompt(params) {
|
|
225
|
+
const { agentsMd, claudeMd, toolDescriptions, trustLevel, projectInfo, cwd, language } = params;
|
|
226
|
+
const sections = [];
|
|
227
|
+
const roleLines = [
|
|
228
|
+
"## Role",
|
|
229
|
+
"You are an AI coding assistant with access to tools that let you read and modify code.",
|
|
230
|
+
"You help developers understand, write, and improve their codebase.",
|
|
231
|
+
"Always be precise, follow existing code conventions, and prefer minimal changes."
|
|
232
|
+
];
|
|
233
|
+
if (language) {
|
|
234
|
+
roleLines.push(
|
|
235
|
+
`Always respond in ${language}. Use ${language} for all explanations and communications.`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
sections.push(roleLines.join("\n"));
|
|
239
|
+
if (cwd) {
|
|
240
|
+
sections.push(`## Working Directory
|
|
241
|
+
\`${cwd}\``);
|
|
242
|
+
}
|
|
243
|
+
sections.push(buildProjectSection(projectInfo));
|
|
244
|
+
sections.push(
|
|
245
|
+
[
|
|
246
|
+
"## Permission Mode",
|
|
247
|
+
`Your current trust level is **${TRUST_LEVEL_DESCRIPTIONS[trustLevel]}**.`
|
|
248
|
+
].join("\n")
|
|
249
|
+
);
|
|
250
|
+
if (agentsMd.trim().length > 0) {
|
|
251
|
+
sections.push(["## Agent Instructions", agentsMd].join("\n"));
|
|
252
|
+
}
|
|
253
|
+
if (claudeMd.trim().length > 0) {
|
|
254
|
+
sections.push(["## Project Notes", claudeMd].join("\n"));
|
|
255
|
+
}
|
|
256
|
+
sections.push(
|
|
257
|
+
[
|
|
258
|
+
"## Web Search",
|
|
259
|
+
"You have access to web search. When the user asks to search, look up, or find current/latest information,",
|
|
260
|
+
"you MUST use the web_search tool. Do NOT answer from training data when the user explicitly asks to search.",
|
|
261
|
+
"Always prefer web search for: news, latest versions, current events, live documentation."
|
|
262
|
+
].join("\n")
|
|
263
|
+
);
|
|
264
|
+
const toolsSection = buildToolsSection(toolDescriptions);
|
|
265
|
+
if (toolsSection.length > 0) {
|
|
266
|
+
sections.push(toolsSection);
|
|
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
|
+
}
|
|
274
|
+
return sections.join("\n\n");
|
|
275
|
+
}
|
|
67
276
|
|
|
68
|
-
// src/
|
|
277
|
+
// src/assembly/create-tools.ts
|
|
278
|
+
var import_agent_tools = require("@robota-sdk/agent-tools");
|
|
279
|
+
var DEFAULT_TOOL_DESCRIPTIONS = [
|
|
280
|
+
"Bash \u2014 execute shell commands",
|
|
281
|
+
"Read \u2014 read file contents with line numbers",
|
|
282
|
+
"Write \u2014 write content to a file",
|
|
283
|
+
"Edit \u2014 replace a string in a file",
|
|
284
|
+
"Glob \u2014 find files matching a pattern",
|
|
285
|
+
"Grep \u2014 search file contents with regex",
|
|
286
|
+
"WebSearch \u2014 search the internet (Anthropic built-in)"
|
|
287
|
+
];
|
|
288
|
+
function createDefaultTools() {
|
|
289
|
+
return [
|
|
290
|
+
import_agent_tools.bashTool,
|
|
291
|
+
import_agent_tools.readTool,
|
|
292
|
+
import_agent_tools.writeTool,
|
|
293
|
+
import_agent_tools.editTool,
|
|
294
|
+
import_agent_tools.globTool,
|
|
295
|
+
import_agent_tools.grepTool,
|
|
296
|
+
import_agent_tools.webFetchTool,
|
|
297
|
+
import_agent_tools.webSearchTool
|
|
298
|
+
];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/assembly/create-provider.ts
|
|
302
|
+
var import_agent_provider_anthropic = require("@robota-sdk/agent-provider-anthropic");
|
|
303
|
+
function createProvider(config) {
|
|
304
|
+
const apiKey = config.provider.apiKey ?? process.env["ANTHROPIC_API_KEY"];
|
|
305
|
+
if (!apiKey) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
"ANTHROPIC_API_KEY is not set. Set the environment variable or configure provider.apiKey in ~/.robota/settings.json"
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
return new import_agent_provider_anthropic.AnthropicProvider({ apiKey });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/assembly/create-session.ts
|
|
314
|
+
function createSession(options) {
|
|
315
|
+
const provider = options.provider ?? createProvider(options.config);
|
|
316
|
+
const defaultTools = createDefaultTools();
|
|
317
|
+
const tools = [...defaultTools, ...options.additionalTools ?? []];
|
|
318
|
+
const buildPrompt = options.systemPromptBuilder ?? buildSystemPrompt;
|
|
319
|
+
const systemMessage = buildPrompt({
|
|
320
|
+
agentsMd: options.context.agentsMd,
|
|
321
|
+
claudeMd: options.context.claudeMd,
|
|
322
|
+
toolDescriptions: options.toolDescriptions ?? DEFAULT_TOOL_DESCRIPTIONS,
|
|
323
|
+
trustLevel: options.config.defaultTrustLevel,
|
|
324
|
+
projectInfo: options.projectInfo ?? { type: "unknown", language: "unknown" },
|
|
325
|
+
cwd: process.cwd(),
|
|
326
|
+
language: options.config.language
|
|
327
|
+
});
|
|
328
|
+
const defaultAllow = [
|
|
329
|
+
"Read(.agents/**)",
|
|
330
|
+
"Read(.claude/**)",
|
|
331
|
+
"Read(.robota/**)",
|
|
332
|
+
"Glob(.agents/**)",
|
|
333
|
+
"Glob(.claude/**)",
|
|
334
|
+
"Glob(.robota/**)"
|
|
335
|
+
];
|
|
336
|
+
const mergedPermissions = {
|
|
337
|
+
allow: [...defaultAllow, ...options.config.permissions.allow ?? []],
|
|
338
|
+
deny: options.config.permissions.deny ?? []
|
|
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
|
+
}
|
|
359
|
+
return new import_agent_sessions.Session({
|
|
360
|
+
tools,
|
|
361
|
+
provider,
|
|
362
|
+
systemMessage,
|
|
363
|
+
terminal: options.terminal,
|
|
364
|
+
permissions: mergedPermissions,
|
|
365
|
+
hooks: options.config.hooks,
|
|
366
|
+
permissionMode: options.permissionMode,
|
|
367
|
+
defaultTrustLevel: options.config.defaultTrustLevel,
|
|
368
|
+
model: options.config.provider.model,
|
|
369
|
+
maxTurns: options.maxTurns,
|
|
370
|
+
sessionStore: options.sessionStore,
|
|
371
|
+
permissionHandler: options.permissionHandler,
|
|
372
|
+
onTextDelta: options.onTextDelta,
|
|
373
|
+
onToolExecution: options.onToolExecution,
|
|
374
|
+
promptForApproval: options.promptForApproval,
|
|
375
|
+
onCompact: options.onCompact,
|
|
376
|
+
compactInstructions: options.compactInstructions ?? options.context.compactInstructions,
|
|
377
|
+
sessionLogger: options.sessionLogger,
|
|
378
|
+
hookTypeExecutors: hookTypeExecutors.length > 0 ? hookTypeExecutors : void 0
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/index.ts
|
|
69
383
|
var import_agent_sessions2 = require("@robota-sdk/agent-sessions");
|
|
384
|
+
var import_agent_sessions3 = require("@robota-sdk/agent-sessions");
|
|
385
|
+
var import_agent_sessions4 = require("@robota-sdk/agent-sessions");
|
|
70
386
|
|
|
71
387
|
// src/config/config-loader.ts
|
|
72
388
|
var import_fs = require("fs");
|
|
@@ -86,10 +402,34 @@ var PermissionsSchema = import_zod.z.object({
|
|
|
86
402
|
deny: import_zod.z.array(import_zod.z.string()).optional()
|
|
87
403
|
});
|
|
88
404
|
var EnvSchema = import_zod.z.record(import_zod.z.string()).optional();
|
|
89
|
-
var
|
|
405
|
+
var CommandHookDefinitionSchema = import_zod.z.object({
|
|
90
406
|
type: import_zod.z.literal("command"),
|
|
91
|
-
command: import_zod.z.string()
|
|
407
|
+
command: import_zod.z.string(),
|
|
408
|
+
timeout: import_zod.z.number().optional()
|
|
92
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()
|
|
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
|
+
]);
|
|
93
433
|
var HookGroupSchema = import_zod.z.object({
|
|
94
434
|
matcher: import_zod.z.string(),
|
|
95
435
|
hooks: import_zod.z.array(HookDefinitionSchema)
|
|
@@ -98,15 +438,36 @@ var HooksSchema = import_zod.z.object({
|
|
|
98
438
|
PreToolUse: import_zod.z.array(HookGroupSchema).optional(),
|
|
99
439
|
PostToolUse: import_zod.z.array(HookGroupSchema).optional(),
|
|
100
440
|
SessionStart: import_zod.z.array(HookGroupSchema).optional(),
|
|
101
|
-
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()
|
|
102
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();
|
|
103
458
|
var SettingsSchema = import_zod.z.object({
|
|
104
459
|
/** Trust level used when no --permission-mode flag is given */
|
|
105
460
|
defaultTrustLevel: import_zod.z.enum(["safe", "moderate", "full"]).optional(),
|
|
461
|
+
/** Response language (e.g., "ko", "en", "ja"). Injected into system prompt. */
|
|
462
|
+
language: import_zod.z.string().optional(),
|
|
106
463
|
provider: ProviderSchema.optional(),
|
|
107
464
|
permissions: PermissionsSchema.optional(),
|
|
108
465
|
env: EnvSchema,
|
|
109
|
-
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
|
|
110
471
|
});
|
|
111
472
|
|
|
112
473
|
// src/config/config-loader.ts
|
|
@@ -130,8 +491,15 @@ function readJsonFile(filePath) {
|
|
|
130
491
|
if (!(0, import_fs.existsSync)(filePath)) {
|
|
131
492
|
return void 0;
|
|
132
493
|
}
|
|
133
|
-
const raw = (0, import_fs.readFileSync)(filePath, "utf-8");
|
|
134
|
-
|
|
494
|
+
const raw = (0, import_fs.readFileSync)(filePath, "utf-8").trim();
|
|
495
|
+
if (raw.length === 0) {
|
|
496
|
+
return void 0;
|
|
497
|
+
}
|
|
498
|
+
try {
|
|
499
|
+
return JSON.parse(raw);
|
|
500
|
+
} catch {
|
|
501
|
+
return void 0;
|
|
502
|
+
}
|
|
135
503
|
}
|
|
136
504
|
function resolveEnvRef(value) {
|
|
137
505
|
const ENV_PREFIX = "$ENV:";
|
|
@@ -166,13 +534,16 @@ function mergeSettings(layers) {
|
|
|
166
534
|
env: {
|
|
167
535
|
...merged.env ?? {},
|
|
168
536
|
...layer.env ?? {}
|
|
169
|
-
}
|
|
537
|
+
},
|
|
538
|
+
enabledPlugins: merged.enabledPlugins !== void 0 || layer.enabledPlugins !== void 0 ? { ...merged.enabledPlugins ?? {}, ...layer.enabledPlugins ?? {} } : void 0,
|
|
539
|
+
extraKnownMarketplaces: layer.extraKnownMarketplaces ?? merged.extraKnownMarketplaces
|
|
170
540
|
};
|
|
171
541
|
}, {});
|
|
172
542
|
}
|
|
173
543
|
function toResolvedConfig(merged) {
|
|
174
544
|
return {
|
|
175
545
|
defaultTrustLevel: merged.defaultTrustLevel ?? DEFAULTS.defaultTrustLevel,
|
|
546
|
+
language: merged.language,
|
|
176
547
|
provider: {
|
|
177
548
|
name: merged.provider?.name ?? DEFAULTS.provider.name,
|
|
178
549
|
model: merged.provider?.model ?? DEFAULTS.provider.model,
|
|
@@ -183,25 +554,39 @@ function toResolvedConfig(merged) {
|
|
|
183
554
|
deny: merged.permissions?.deny ?? DEFAULTS.permissions.deny
|
|
184
555
|
},
|
|
185
556
|
env: merged.env ?? DEFAULTS.env,
|
|
186
|
-
hooks: merged.hooks ?? void 0
|
|
557
|
+
hooks: merged.hooks ?? void 0,
|
|
558
|
+
enabledPlugins: merged.enabledPlugins ?? void 0,
|
|
559
|
+
extraKnownMarketplaces: merged.extraKnownMarketplaces ?? void 0
|
|
187
560
|
};
|
|
188
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
|
+
}
|
|
189
577
|
async function loadConfig(cwd) {
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const parsedLayers =
|
|
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 }) => {
|
|
199
587
|
const result = SettingsSchema.safeParse(raw);
|
|
200
588
|
if (!result.success) {
|
|
201
|
-
|
|
202
|
-
(_, i) => rawLayers[i] !== void 0
|
|
203
|
-
);
|
|
204
|
-
throw new Error(`Invalid settings in ${paths[index] ?? "unknown"}: ${result.error.message}`);
|
|
589
|
+
throw new Error(`Invalid settings in ${path}: ${result.error.message}`);
|
|
205
590
|
}
|
|
206
591
|
return resolveEnvRefs(result.data);
|
|
207
592
|
});
|
|
@@ -331,77 +716,6 @@ async function detectProject(cwd) {
|
|
|
331
716
|
};
|
|
332
717
|
}
|
|
333
718
|
|
|
334
|
-
// src/context/system-prompt-builder.ts
|
|
335
|
-
var TRUST_LEVEL_DESCRIPTIONS = {
|
|
336
|
-
safe: "safe (read-only / plan mode \u2014 only read-access tools are available)",
|
|
337
|
-
moderate: "moderate (default mode \u2014 write and bash tools require approval)",
|
|
338
|
-
full: "full (acceptEdits mode \u2014 file writes are auto-approved; bash requires approval)"
|
|
339
|
-
};
|
|
340
|
-
function buildProjectSection(info) {
|
|
341
|
-
const lines = ["## Current Project"];
|
|
342
|
-
if (info.name !== void 0) {
|
|
343
|
-
lines.push(`- **Name:** ${info.name}`);
|
|
344
|
-
}
|
|
345
|
-
if (info.type !== "unknown") {
|
|
346
|
-
lines.push(`- **Type:** ${info.type}`);
|
|
347
|
-
}
|
|
348
|
-
if (info.language !== "unknown") {
|
|
349
|
-
lines.push(`- **Language:** ${info.language}`);
|
|
350
|
-
}
|
|
351
|
-
if (info.packageManager !== void 0) {
|
|
352
|
-
lines.push(`- **Package manager:** ${info.packageManager}`);
|
|
353
|
-
}
|
|
354
|
-
return lines.join("\n");
|
|
355
|
-
}
|
|
356
|
-
function buildToolsSection(descriptions) {
|
|
357
|
-
if (descriptions.length === 0) {
|
|
358
|
-
return "";
|
|
359
|
-
}
|
|
360
|
-
const lines = ["## Available Tools", ...descriptions.map((d) => `- ${d}`)];
|
|
361
|
-
return lines.join("\n");
|
|
362
|
-
}
|
|
363
|
-
function buildSystemPrompt(params) {
|
|
364
|
-
const { agentsMd, claudeMd, toolDescriptions, trustLevel, projectInfo } = params;
|
|
365
|
-
const sections = [];
|
|
366
|
-
sections.push(
|
|
367
|
-
[
|
|
368
|
-
"## Role",
|
|
369
|
-
"You are an AI coding assistant with access to tools that let you read and modify code.",
|
|
370
|
-
"You help developers understand, write, and improve their codebase.",
|
|
371
|
-
"Always be precise, follow existing code conventions, and prefer minimal changes."
|
|
372
|
-
].join("\n")
|
|
373
|
-
);
|
|
374
|
-
sections.push(buildProjectSection(projectInfo));
|
|
375
|
-
sections.push(
|
|
376
|
-
[
|
|
377
|
-
"## Permission Mode",
|
|
378
|
-
`Your current trust level is **${TRUST_LEVEL_DESCRIPTIONS[trustLevel]}**.`
|
|
379
|
-
].join("\n")
|
|
380
|
-
);
|
|
381
|
-
if (agentsMd.trim().length > 0) {
|
|
382
|
-
sections.push(["## Agent Instructions", agentsMd].join("\n"));
|
|
383
|
-
}
|
|
384
|
-
if (claudeMd.trim().length > 0) {
|
|
385
|
-
sections.push(["## Project Notes", claudeMd].join("\n"));
|
|
386
|
-
}
|
|
387
|
-
sections.push(
|
|
388
|
-
[
|
|
389
|
-
"## Web Search",
|
|
390
|
-
"You have access to web search. When the user asks to search, look up, or find current/latest information,",
|
|
391
|
-
"you MUST use the web_search tool. Do NOT answer from training data when the user explicitly asks to search.",
|
|
392
|
-
"Always prefer web search for: news, latest versions, current events, live documentation."
|
|
393
|
-
].join("\n")
|
|
394
|
-
);
|
|
395
|
-
const toolsSection = buildToolsSection(toolDescriptions);
|
|
396
|
-
if (toolsSection.length > 0) {
|
|
397
|
-
sections.push(toolsSection);
|
|
398
|
-
}
|
|
399
|
-
return sections.join("\n\n");
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// src/query.ts
|
|
403
|
-
var import_agent_sessions3 = require("@robota-sdk/agent-sessions");
|
|
404
|
-
|
|
405
719
|
// src/permissions/permission-prompt.ts
|
|
406
720
|
var import_chalk = __toESM(require("chalk"), 1);
|
|
407
721
|
var PERMISSION_OPTIONS = ["Allow", "Deny"];
|
|
@@ -445,7 +759,7 @@ async function query(prompt, options) {
|
|
|
445
759
|
}, update: () => {
|
|
446
760
|
} })
|
|
447
761
|
};
|
|
448
|
-
const session =
|
|
762
|
+
const session = createSession({
|
|
449
763
|
config,
|
|
450
764
|
context,
|
|
451
765
|
terminal: noopTerminal,
|
|
@@ -457,16 +771,13 @@ async function query(prompt, options) {
|
|
|
457
771
|
onTextDelta: options?.onTextDelta,
|
|
458
772
|
onCompact: options?.onCompact,
|
|
459
773
|
compactInstructions: context.compactInstructions,
|
|
460
|
-
systemPromptBuilder: buildSystemPrompt,
|
|
461
774
|
promptForApproval
|
|
462
775
|
});
|
|
463
776
|
return session.run(prompt);
|
|
464
777
|
}
|
|
465
778
|
|
|
466
|
-
// src/
|
|
779
|
+
// src/index.ts
|
|
467
780
|
var import_agent_core2 = require("@robota-sdk/agent-core");
|
|
468
|
-
|
|
469
|
-
// src/hooks/hook-runner.ts
|
|
470
781
|
var import_agent_core3 = require("@robota-sdk/agent-core");
|
|
471
782
|
|
|
472
783
|
// src/paths.ts
|
|
@@ -489,28 +800,804 @@ function userPaths() {
|
|
|
489
800
|
};
|
|
490
801
|
}
|
|
491
802
|
|
|
492
|
-
// src/
|
|
493
|
-
var
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
+
};
|
|
500
903
|
|
|
501
|
-
// src/
|
|
502
|
-
var
|
|
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
|
+
};
|
|
503
1132
|
|
|
504
|
-
// src/
|
|
505
|
-
var
|
|
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
|
+
/**
|
|
1230
|
+
* Normalize source object — Claude Code manifests use `source` key instead of `type`.
|
|
1231
|
+
* e.g., { source: "url", url: "..." } → { type: "url", url: "..." }
|
|
1232
|
+
*/
|
|
1233
|
+
normalizeSource(source) {
|
|
1234
|
+
if (typeof source === "string") return source;
|
|
1235
|
+
const obj = source;
|
|
1236
|
+
if (!obj.type && typeof obj.source === "string") {
|
|
1237
|
+
return { ...obj, type: obj.source };
|
|
1238
|
+
}
|
|
1239
|
+
return source;
|
|
1240
|
+
}
|
|
1241
|
+
/** Resolve the source and install the plugin. */
|
|
1242
|
+
resolveAndInstall(rawSource, marketplaceName, pluginName, targetDir) {
|
|
1243
|
+
(0, import_node_fs3.mkdirSync)(targetDir, { recursive: true });
|
|
1244
|
+
const source = this.normalizeSource(rawSource);
|
|
1245
|
+
try {
|
|
1246
|
+
if (typeof source === "string") {
|
|
1247
|
+
const marketplaceDir = this.marketplaceClient.getMarketplaceDir(marketplaceName);
|
|
1248
|
+
const sourcePath = (0, import_node_path4.join)(marketplaceDir, source);
|
|
1249
|
+
if (!(0, import_node_fs3.existsSync)(sourcePath)) {
|
|
1250
|
+
throw new Error(
|
|
1251
|
+
`Plugin source path "${source}" not found in marketplace "${marketplaceName}"`
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
(0, import_node_fs3.cpSync)(sourcePath, targetDir, { recursive: true });
|
|
1255
|
+
} else if (source.type === "github") {
|
|
1256
|
+
const repoUrl = `https://github.com/${source.repo}.git`;
|
|
1257
|
+
this.cloneToDir(repoUrl, targetDir, pluginName);
|
|
1258
|
+
} else if (source.type === "url" && typeof source.url === "string" && source.url.endsWith(".git")) {
|
|
1259
|
+
this.cloneToDir(source.url, targetDir, pluginName);
|
|
1260
|
+
} else if (source.type === "url") {
|
|
1261
|
+
throw new Error(`URL source "${source.url}" is not a git repository (must end with .git)`);
|
|
1262
|
+
} else {
|
|
1263
|
+
throw new Error(`Unknown source type: ${JSON.stringify(source)}`);
|
|
1264
|
+
}
|
|
1265
|
+
} catch (err) {
|
|
1266
|
+
if ((0, import_node_fs3.existsSync)(targetDir)) {
|
|
1267
|
+
(0, import_node_fs3.rmSync)(targetDir, { recursive: true, force: true });
|
|
1268
|
+
}
|
|
1269
|
+
throw err;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
/** Clone a git repository to the target directory. */
|
|
1273
|
+
cloneToDir(repoUrl, targetDir, pluginName) {
|
|
1274
|
+
(0, import_node_fs3.rmSync)(targetDir, { recursive: true, force: true });
|
|
1275
|
+
const command = `git clone --depth 1 ${repoUrl} ${targetDir}`;
|
|
1276
|
+
try {
|
|
1277
|
+
this.exec(command, { timeout: GIT_CLONE_TIMEOUT_MS, stdio: "pipe" });
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1280
|
+
throw new Error(`Failed to clone plugin "${pluginName}": ${message}`);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
/** Read the installed_plugins.json registry. */
|
|
1284
|
+
readRegistry() {
|
|
1285
|
+
if (!(0, import_node_fs3.existsSync)(this.registryPath)) {
|
|
1286
|
+
return {};
|
|
1287
|
+
}
|
|
1288
|
+
try {
|
|
1289
|
+
const raw = (0, import_node_fs3.readFileSync)(this.registryPath, "utf-8");
|
|
1290
|
+
const data = JSON.parse(raw);
|
|
1291
|
+
if (typeof data === "object" && data !== null) {
|
|
1292
|
+
return data;
|
|
1293
|
+
}
|
|
1294
|
+
return {};
|
|
1295
|
+
} catch {
|
|
1296
|
+
return {};
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
/** Write the installed_plugins.json registry. */
|
|
1300
|
+
writeRegistry(registry) {
|
|
1301
|
+
const dir = (0, import_node_path4.dirname)(this.registryPath);
|
|
1302
|
+
if (!(0, import_node_fs3.existsSync)(dir)) {
|
|
1303
|
+
(0, import_node_fs3.mkdirSync)(dir, { recursive: true });
|
|
1304
|
+
}
|
|
1305
|
+
(0, import_node_fs3.writeFileSync)(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
|
|
1306
|
+
}
|
|
1307
|
+
/** Default exec implementation using child_process. */
|
|
1308
|
+
defaultExec(command, options) {
|
|
1309
|
+
return (0, import_node_child_process.execSync)(command, { timeout: options.timeout, stdio: "pipe" });
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
506
1312
|
|
|
507
|
-
// src/
|
|
508
|
-
var
|
|
1313
|
+
// src/plugins/marketplace-client.ts
|
|
1314
|
+
var import_node_child_process2 = require("child_process");
|
|
1315
|
+
var import_node_fs4 = require("fs");
|
|
1316
|
+
var import_node_path5 = require("path");
|
|
1317
|
+
var GIT_TIMEOUT_MS = 6e4;
|
|
1318
|
+
var MarketplaceClient = class {
|
|
1319
|
+
pluginsDir;
|
|
1320
|
+
exec;
|
|
1321
|
+
marketplacesDir;
|
|
1322
|
+
registryPath;
|
|
1323
|
+
constructor(options) {
|
|
1324
|
+
this.pluginsDir = options.pluginsDir;
|
|
1325
|
+
this.exec = options.exec ?? this.defaultExec;
|
|
1326
|
+
this.marketplacesDir = (0, import_node_path5.join)(this.pluginsDir, "marketplaces");
|
|
1327
|
+
this.registryPath = (0, import_node_path5.join)(this.pluginsDir, "known_marketplaces.json");
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Add a marketplace by cloning its repository.
|
|
1331
|
+
*
|
|
1332
|
+
* 1. Parse source: `owner/repo` string becomes a GitHub source.
|
|
1333
|
+
* 2. Shallow git clone (`--depth 1`) to `marketplaces/<name>/`.
|
|
1334
|
+
* 3. Read `.claude-plugin/marketplace.json` for the `name` field.
|
|
1335
|
+
* 4. Register in `known_marketplaces.json`.
|
|
1336
|
+
*
|
|
1337
|
+
* Returns the registered marketplace name from the manifest.
|
|
1338
|
+
*/
|
|
1339
|
+
addMarketplace(source) {
|
|
1340
|
+
const tempName = "temp-" + Date.now().toString(36);
|
|
1341
|
+
const tempDir = (0, import_node_path5.join)(this.marketplacesDir, tempName);
|
|
1342
|
+
(0, import_node_fs4.mkdirSync)(this.marketplacesDir, { recursive: true });
|
|
1343
|
+
if (source.type === "local") {
|
|
1344
|
+
if (!(0, import_node_fs4.existsSync)(source.path)) {
|
|
1345
|
+
throw new Error(`Local marketplace path does not exist: ${source.path}`);
|
|
1346
|
+
}
|
|
1347
|
+
(0, import_node_fs4.cpSync)(source.path, tempDir, { recursive: true });
|
|
1348
|
+
} else {
|
|
1349
|
+
const cloneUrl = this.resolveCloneUrl(source);
|
|
1350
|
+
const command = `git clone --depth 1 ${cloneUrl} ${tempDir}`;
|
|
1351
|
+
try {
|
|
1352
|
+
this.exec(command, { timeout: GIT_TIMEOUT_MS, stdio: "pipe" });
|
|
1353
|
+
} catch (error) {
|
|
1354
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1355
|
+
throw new Error(`Failed to clone marketplace: ${message}`);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
const manifestPath = (0, import_node_path5.join)(tempDir, ".claude-plugin", "marketplace.json");
|
|
1359
|
+
if (!(0, import_node_fs4.existsSync)(manifestPath)) {
|
|
1360
|
+
(0, import_node_fs4.rmSync)(tempDir, { recursive: true, force: true });
|
|
1361
|
+
throw new Error(
|
|
1362
|
+
source.type === "local" ? "Local directory does not contain .claude-plugin/marketplace.json" : "Cloned repository does not contain .claude-plugin/marketplace.json"
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
const manifest = this.readManifestFromPath(manifestPath);
|
|
1366
|
+
const name = manifest.name;
|
|
1367
|
+
if (!name) {
|
|
1368
|
+
(0, import_node_fs4.rmSync)(tempDir, { recursive: true, force: true });
|
|
1369
|
+
throw new Error('Marketplace manifest does not contain a "name" field');
|
|
1370
|
+
}
|
|
1371
|
+
const registry = this.readRegistry();
|
|
1372
|
+
if (registry[name]) {
|
|
1373
|
+
(0, import_node_fs4.rmSync)(tempDir, { recursive: true, force: true });
|
|
1374
|
+
throw new Error(`Marketplace "${name}" already exists`);
|
|
1375
|
+
}
|
|
1376
|
+
const finalDir = (0, import_node_path5.join)(this.marketplacesDir, name);
|
|
1377
|
+
(0, import_node_fs4.renameSync)(tempDir, finalDir);
|
|
1378
|
+
registry[name] = {
|
|
1379
|
+
source,
|
|
1380
|
+
installLocation: finalDir,
|
|
1381
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1382
|
+
};
|
|
1383
|
+
this.writeRegistry(registry);
|
|
1384
|
+
return name;
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Remove a marketplace.
|
|
1388
|
+
* Uninstalls all plugins from that marketplace, then deletes the clone directory
|
|
1389
|
+
* and removes from the registry.
|
|
1390
|
+
*/
|
|
1391
|
+
removeMarketplace(name) {
|
|
1392
|
+
const registry = this.readRegistry();
|
|
1393
|
+
const entry = registry[name];
|
|
1394
|
+
if (!entry) {
|
|
1395
|
+
throw new Error(`Marketplace "${name}" not found`);
|
|
1396
|
+
}
|
|
1397
|
+
this.removeInstalledPluginsForMarketplace(name);
|
|
1398
|
+
if ((0, import_node_fs4.existsSync)(entry.installLocation)) {
|
|
1399
|
+
(0, import_node_fs4.rmSync)(entry.installLocation, { recursive: true, force: true });
|
|
1400
|
+
}
|
|
1401
|
+
delete registry[name];
|
|
1402
|
+
this.writeRegistry(registry);
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Update a marketplace by running git pull on its clone.
|
|
1406
|
+
* The manifest is re-read from disk on demand (via fetchManifest), so the
|
|
1407
|
+
* updated manifest is automatically available after pull.
|
|
1408
|
+
*
|
|
1409
|
+
* TODO: After pull, detect version changes in installed plugins and offer
|
|
1410
|
+
* to update them (re-install at new version).
|
|
1411
|
+
*/
|
|
1412
|
+
updateMarketplace(name) {
|
|
1413
|
+
const registry = this.readRegistry();
|
|
1414
|
+
const entry = registry[name];
|
|
1415
|
+
if (!entry) {
|
|
1416
|
+
throw new Error(`Marketplace "${name}" not found`);
|
|
1417
|
+
}
|
|
1418
|
+
if (!(0, import_node_fs4.existsSync)(entry.installLocation)) {
|
|
1419
|
+
throw new Error(`Marketplace directory for "${name}" does not exist`);
|
|
1420
|
+
}
|
|
1421
|
+
if (entry.source.type === "local") {
|
|
1422
|
+
const localSource = entry.source;
|
|
1423
|
+
if (!(0, import_node_fs4.existsSync)(localSource.path)) {
|
|
1424
|
+
throw new Error(`Local marketplace path does not exist: ${localSource.path}`);
|
|
1425
|
+
}
|
|
1426
|
+
(0, import_node_fs4.rmSync)(entry.installLocation, { recursive: true, force: true });
|
|
1427
|
+
(0, import_node_fs4.cpSync)(localSource.path, entry.installLocation, { recursive: true });
|
|
1428
|
+
} else {
|
|
1429
|
+
const command = `git -C ${entry.installLocation} pull`;
|
|
1430
|
+
try {
|
|
1431
|
+
this.exec(command, { timeout: GIT_TIMEOUT_MS, stdio: "pipe" });
|
|
1432
|
+
} catch (error) {
|
|
1433
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1434
|
+
throw new Error(`Failed to update marketplace "${name}": ${message}`);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
entry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
1438
|
+
this.writeRegistry(registry);
|
|
1439
|
+
}
|
|
1440
|
+
/** List all registered marketplaces. */
|
|
1441
|
+
listMarketplaces() {
|
|
1442
|
+
const registry = this.readRegistry();
|
|
1443
|
+
return Object.entries(registry).map(([name, entry]) => ({
|
|
1444
|
+
name,
|
|
1445
|
+
source: entry.source,
|
|
1446
|
+
lastUpdated: entry.lastUpdated
|
|
1447
|
+
}));
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Read the marketplace manifest from a registered marketplace's clone.
|
|
1451
|
+
*/
|
|
1452
|
+
fetchManifest(marketplaceName) {
|
|
1453
|
+
const registry = this.readRegistry();
|
|
1454
|
+
const entry = registry[marketplaceName];
|
|
1455
|
+
if (!entry) {
|
|
1456
|
+
throw new Error(`Marketplace "${marketplaceName}" not found`);
|
|
1457
|
+
}
|
|
1458
|
+
const manifestPath = (0, import_node_path5.join)(entry.installLocation, ".claude-plugin", "marketplace.json");
|
|
1459
|
+
if (!(0, import_node_fs4.existsSync)(manifestPath)) {
|
|
1460
|
+
throw new Error(
|
|
1461
|
+
`Marketplace "${marketplaceName}" does not contain .claude-plugin/marketplace.json`
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1464
|
+
return this.readManifestFromPath(manifestPath);
|
|
1465
|
+
}
|
|
1466
|
+
/** Get the clone directory path for a registered marketplace. */
|
|
1467
|
+
getMarketplaceDir(name) {
|
|
1468
|
+
const registry = this.readRegistry();
|
|
1469
|
+
const entry = registry[name];
|
|
1470
|
+
if (!entry) {
|
|
1471
|
+
throw new Error(`Marketplace "${name}" not found`);
|
|
1472
|
+
}
|
|
1473
|
+
return entry.installLocation;
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Get the current git SHA (first 12 chars) for a marketplace clone.
|
|
1477
|
+
* Used as a version identifier when plugins lack explicit versions.
|
|
1478
|
+
*/
|
|
1479
|
+
getMarketplaceSha(name) {
|
|
1480
|
+
const dir = this.getMarketplaceDir(name);
|
|
1481
|
+
try {
|
|
1482
|
+
const result = this.exec(`git -C ${dir} rev-parse HEAD`, {
|
|
1483
|
+
timeout: GIT_TIMEOUT_MS,
|
|
1484
|
+
stdio: "pipe"
|
|
1485
|
+
});
|
|
1486
|
+
return result.toString().trim().slice(0, 12);
|
|
1487
|
+
} catch {
|
|
1488
|
+
return "unknown";
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
/** List all available plugins across all marketplaces. */
|
|
1492
|
+
listAvailablePlugins() {
|
|
1493
|
+
const results = [];
|
|
1494
|
+
const marketplaces = this.listMarketplaces();
|
|
1495
|
+
for (const { name } of marketplaces) {
|
|
1496
|
+
try {
|
|
1497
|
+
const manifest = this.fetchManifest(name);
|
|
1498
|
+
for (const plugin of manifest.plugins) {
|
|
1499
|
+
results.push({ ...plugin, marketplace: name });
|
|
1500
|
+
}
|
|
1501
|
+
} catch {
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
return results;
|
|
1505
|
+
}
|
|
1506
|
+
// --- Private helpers ---
|
|
1507
|
+
/** Resolve a marketplace source to a git clone URL. */
|
|
1508
|
+
resolveCloneUrl(source) {
|
|
1509
|
+
switch (source.type) {
|
|
1510
|
+
case "github":
|
|
1511
|
+
return `https://github.com/${source.repo}.git`;
|
|
1512
|
+
case "git":
|
|
1513
|
+
return source.url;
|
|
1514
|
+
case "local":
|
|
1515
|
+
throw new Error("Local source type does not use git cloning");
|
|
1516
|
+
case "url":
|
|
1517
|
+
throw new Error("URL marketplace source is not yet supported");
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Remove all installed plugins that belong to a given marketplace.
|
|
1522
|
+
* Reads installed_plugins.json, deletes cache directories for matching plugins,
|
|
1523
|
+
* and updates the registry.
|
|
1524
|
+
*/
|
|
1525
|
+
removeInstalledPluginsForMarketplace(marketplaceName) {
|
|
1526
|
+
const installedPath = (0, import_node_path5.join)(this.pluginsDir, "installed_plugins.json");
|
|
1527
|
+
if (!(0, import_node_fs4.existsSync)(installedPath)) return;
|
|
1528
|
+
let registry;
|
|
1529
|
+
try {
|
|
1530
|
+
const raw = (0, import_node_fs4.readFileSync)(installedPath, "utf-8");
|
|
1531
|
+
const data = JSON.parse(raw);
|
|
1532
|
+
if (typeof data !== "object" || data === null) return;
|
|
1533
|
+
registry = data;
|
|
1534
|
+
} catch {
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
let changed = false;
|
|
1538
|
+
for (const [pluginId, record] of Object.entries(registry)) {
|
|
1539
|
+
if (record.marketplace === marketplaceName) {
|
|
1540
|
+
if (record.installPath && (0, import_node_fs4.existsSync)(record.installPath)) {
|
|
1541
|
+
(0, import_node_fs4.rmSync)(record.installPath, { recursive: true, force: true });
|
|
1542
|
+
}
|
|
1543
|
+
delete registry[pluginId];
|
|
1544
|
+
changed = true;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
if (changed) {
|
|
1548
|
+
const dir = (0, import_node_path5.dirname)(installedPath);
|
|
1549
|
+
if (!(0, import_node_fs4.existsSync)(dir)) {
|
|
1550
|
+
(0, import_node_fs4.mkdirSync)(dir, { recursive: true });
|
|
1551
|
+
}
|
|
1552
|
+
(0, import_node_fs4.writeFileSync)(installedPath, JSON.stringify(registry, null, 2), "utf-8");
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
/** Read and parse a marketplace.json from a file path. */
|
|
1556
|
+
readManifestFromPath(path) {
|
|
1557
|
+
const raw = (0, import_node_fs4.readFileSync)(path, "utf-8");
|
|
1558
|
+
const data = JSON.parse(raw);
|
|
1559
|
+
if (typeof data !== "object" || data === null) {
|
|
1560
|
+
throw new Error("Invalid marketplace manifest: not an object");
|
|
1561
|
+
}
|
|
1562
|
+
const obj = data;
|
|
1563
|
+
if (typeof obj.name !== "string") {
|
|
1564
|
+
throw new Error('Invalid marketplace manifest: missing "name" field');
|
|
1565
|
+
}
|
|
1566
|
+
return data;
|
|
1567
|
+
}
|
|
1568
|
+
/** Read the known_marketplaces.json registry. */
|
|
1569
|
+
readRegistry() {
|
|
1570
|
+
if (!(0, import_node_fs4.existsSync)(this.registryPath)) {
|
|
1571
|
+
return {};
|
|
1572
|
+
}
|
|
1573
|
+
try {
|
|
1574
|
+
const raw = (0, import_node_fs4.readFileSync)(this.registryPath, "utf-8");
|
|
1575
|
+
const data = JSON.parse(raw);
|
|
1576
|
+
if (typeof data === "object" && data !== null) {
|
|
1577
|
+
return data;
|
|
1578
|
+
}
|
|
1579
|
+
return {};
|
|
1580
|
+
} catch {
|
|
1581
|
+
return {};
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
/** Write the known_marketplaces.json registry. */
|
|
1585
|
+
writeRegistry(registry) {
|
|
1586
|
+
const dir = (0, import_node_path5.dirname)(this.registryPath);
|
|
1587
|
+
if (!(0, import_node_fs4.existsSync)(dir)) {
|
|
1588
|
+
(0, import_node_fs4.mkdirSync)(dir, { recursive: true });
|
|
1589
|
+
}
|
|
1590
|
+
(0, import_node_fs4.writeFileSync)(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
|
|
1591
|
+
}
|
|
1592
|
+
/** Default exec implementation using child_process. */
|
|
1593
|
+
defaultExec(command, options) {
|
|
1594
|
+
return (0, import_node_child_process2.execSync)(command, { timeout: options.timeout, stdio: "pipe" });
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
509
1597
|
|
|
510
1598
|
// src/tools/agent-tool.ts
|
|
511
1599
|
var import_zod2 = require("zod");
|
|
512
|
-
var
|
|
513
|
-
var import_agent_sessions4 = require("@robota-sdk/agent-sessions");
|
|
1600
|
+
var import_agent_tools2 = require("@robota-sdk/agent-tools");
|
|
514
1601
|
function asZodSchema(schema) {
|
|
515
1602
|
return schema;
|
|
516
1603
|
}
|
|
@@ -531,26 +1618,26 @@ async function runAgent(args) {
|
|
|
531
1618
|
};
|
|
532
1619
|
return JSON.stringify(result);
|
|
533
1620
|
}
|
|
534
|
-
const
|
|
1621
|
+
const noopTerminal = {
|
|
1622
|
+
write: () => {
|
|
1623
|
+
},
|
|
1624
|
+
writeLine: () => {
|
|
1625
|
+
},
|
|
1626
|
+
writeMarkdown: () => {
|
|
1627
|
+
},
|
|
1628
|
+
writeError: () => {
|
|
1629
|
+
},
|
|
1630
|
+
prompt: () => Promise.resolve(""),
|
|
1631
|
+
select: () => Promise.resolve(0),
|
|
1632
|
+
spinner: () => ({ stop: () => {
|
|
1633
|
+
}, update: () => {
|
|
1634
|
+
} })
|
|
1635
|
+
};
|
|
1636
|
+
const subSession = createSession({
|
|
535
1637
|
config: agentToolDeps.config,
|
|
536
1638
|
context: agentToolDeps.context,
|
|
537
1639
|
projectInfo: agentToolDeps.projectInfo,
|
|
538
|
-
|
|
539
|
-
terminal: {
|
|
540
|
-
write: () => {
|
|
541
|
-
},
|
|
542
|
-
writeLine: () => {
|
|
543
|
-
},
|
|
544
|
-
writeMarkdown: () => {
|
|
545
|
-
},
|
|
546
|
-
writeError: () => {
|
|
547
|
-
},
|
|
548
|
-
prompt: () => Promise.resolve(""),
|
|
549
|
-
select: () => Promise.resolve(0),
|
|
550
|
-
spinner: () => ({ stop: () => {
|
|
551
|
-
}, update: () => {
|
|
552
|
-
} })
|
|
553
|
-
},
|
|
1640
|
+
terminal: noopTerminal,
|
|
554
1641
|
// Sub-agents bypass permissions — they inherit parent's trust
|
|
555
1642
|
permissionMode: "bypassPermissions"
|
|
556
1643
|
});
|
|
@@ -571,7 +1658,7 @@ async function runAgent(args) {
|
|
|
571
1658
|
return JSON.stringify(result);
|
|
572
1659
|
}
|
|
573
1660
|
}
|
|
574
|
-
var agentTool = (0,
|
|
1661
|
+
var agentTool = (0, import_agent_tools2.createZodFunctionTool)(
|
|
575
1662
|
"Agent",
|
|
576
1663
|
"Spawn a sub-agent with isolated context to handle a task. The sub-agent has its own conversation history and can use all tools.",
|
|
577
1664
|
asZodSchema(AgentSchema),
|
|
@@ -579,9 +1666,24 @@ var agentTool = (0, import_agent_tools7.createZodFunctionTool)(
|
|
|
579
1666
|
return runAgent(params);
|
|
580
1667
|
}
|
|
581
1668
|
);
|
|
1669
|
+
|
|
1670
|
+
// src/index.ts
|
|
1671
|
+
var import_agent_tools3 = require("@robota-sdk/agent-tools");
|
|
1672
|
+
var import_agent_tools4 = require("@robota-sdk/agent-tools");
|
|
1673
|
+
var import_agent_tools5 = require("@robota-sdk/agent-tools");
|
|
1674
|
+
var import_agent_tools6 = require("@robota-sdk/agent-tools");
|
|
1675
|
+
var import_agent_tools7 = require("@robota-sdk/agent-tools");
|
|
1676
|
+
var import_agent_tools8 = require("@robota-sdk/agent-tools");
|
|
582
1677
|
// Annotate the CommonJS export names for ESM import in node:
|
|
583
1678
|
0 && (module.exports = {
|
|
1679
|
+
AgentExecutor,
|
|
1680
|
+
BundlePluginInstaller,
|
|
1681
|
+
BundlePluginLoader,
|
|
1682
|
+
DEFAULT_TOOL_DESCRIPTIONS,
|
|
584
1683
|
FileSessionLogger,
|
|
1684
|
+
MarketplaceClient,
|
|
1685
|
+
PluginSettingsStore,
|
|
1686
|
+
PromptExecutor,
|
|
585
1687
|
Session,
|
|
586
1688
|
SessionStore,
|
|
587
1689
|
SilentSessionLogger,
|
|
@@ -589,6 +1691,9 @@ var agentTool = (0, import_agent_tools7.createZodFunctionTool)(
|
|
|
589
1691
|
agentTool,
|
|
590
1692
|
bashTool,
|
|
591
1693
|
buildSystemPrompt,
|
|
1694
|
+
createDefaultTools,
|
|
1695
|
+
createProvider,
|
|
1696
|
+
createSession,
|
|
592
1697
|
detectProject,
|
|
593
1698
|
editTool,
|
|
594
1699
|
evaluatePermission,
|