@burdenoff/vibe-plugin-ai 2.1.0 → 3.0.0
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/index.js +532 -14
- package/package.json +10 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,420 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// src/index.ts
|
|
3
|
-
import { mkdirSync } from "fs";
|
|
3
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
4
|
+
import { join as join2 } from "path";
|
|
5
|
+
import { Elysia as Elysia2 } from "elysia";
|
|
6
|
+
|
|
7
|
+
// src/db/prompts.ts
|
|
8
|
+
import { Database } from "bun:sqlite";
|
|
4
9
|
import { join } from "path";
|
|
10
|
+
import os from "os";
|
|
11
|
+
import { existsSync, mkdirSync } from "fs";
|
|
12
|
+
var VALID_CATEGORIES = [
|
|
13
|
+
"GENERAL",
|
|
14
|
+
"CODING",
|
|
15
|
+
"DEBUGGING",
|
|
16
|
+
"REVIEW",
|
|
17
|
+
"DOCUMENTATION",
|
|
18
|
+
"TESTING",
|
|
19
|
+
"DEPLOYMENT",
|
|
20
|
+
"CUSTOM"
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
class PromptDatabase {
|
|
24
|
+
db;
|
|
25
|
+
constructor(dbPath) {
|
|
26
|
+
const dir = join(os.homedir(), ".vibecontrols");
|
|
27
|
+
if (!existsSync(dir)) {
|
|
28
|
+
mkdirSync(dir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
const path = dbPath || join(dir, "ai-prompts.db");
|
|
31
|
+
this.db = new Database(path);
|
|
32
|
+
this.db.exec("PRAGMA journal_mode = WAL");
|
|
33
|
+
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
34
|
+
this.db.exec("PRAGMA synchronous = NORMAL");
|
|
35
|
+
this.initializeSchema();
|
|
36
|
+
}
|
|
37
|
+
initializeSchema() {
|
|
38
|
+
this.db.exec(`
|
|
39
|
+
CREATE TABLE IF NOT EXISTS prompts (
|
|
40
|
+
id TEXT PRIMARY KEY,
|
|
41
|
+
name TEXT NOT NULL,
|
|
42
|
+
content TEXT NOT NULL,
|
|
43
|
+
category TEXT CHECK(category IS NULL OR category IN ('GENERAL','CODING','DEBUGGING','REVIEW','DOCUMENTATION','TESTING','DEPLOYMENT','CUSTOM')),
|
|
44
|
+
tags TEXT DEFAULT '[]',
|
|
45
|
+
variables TEXT DEFAULT '[]',
|
|
46
|
+
is_shared INTEGER DEFAULT 0,
|
|
47
|
+
created_by TEXT NOT NULL DEFAULT 'local',
|
|
48
|
+
usage_count INTEGER DEFAULT 0,
|
|
49
|
+
last_used TEXT,
|
|
50
|
+
metadata TEXT DEFAULT '{}',
|
|
51
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
52
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
53
|
+
deleted_at TEXT
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_prompts_category ON prompts(category);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_prompts_is_shared ON prompts(is_shared);
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_prompts_created_by ON prompts(created_by);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_prompts_deleted_at ON prompts(deleted_at);
|
|
60
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_prompts_name ON prompts(name) WHERE deleted_at IS NULL;
|
|
61
|
+
`);
|
|
62
|
+
}
|
|
63
|
+
generateId() {
|
|
64
|
+
return crypto.randomUUID();
|
|
65
|
+
}
|
|
66
|
+
rowToPrompt(row) {
|
|
67
|
+
return {
|
|
68
|
+
id: row.id,
|
|
69
|
+
name: row.name,
|
|
70
|
+
content: row.content,
|
|
71
|
+
category: row.category,
|
|
72
|
+
tags: JSON.parse(row.tags || "[]"),
|
|
73
|
+
variables: JSON.parse(row.variables || "[]"),
|
|
74
|
+
isShared: row.is_shared === 1,
|
|
75
|
+
createdBy: row.created_by,
|
|
76
|
+
usageCount: row.usage_count,
|
|
77
|
+
lastUsed: row.last_used,
|
|
78
|
+
metadata: JSON.parse(row.metadata || "{}"),
|
|
79
|
+
createdAt: row.created_at,
|
|
80
|
+
updatedAt: row.updated_at,
|
|
81
|
+
deletedAt: row.deleted_at
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
extractVariables(content) {
|
|
85
|
+
const variableRegex = /\{\{(\w+)\}\}/g;
|
|
86
|
+
const variables = [];
|
|
87
|
+
let match;
|
|
88
|
+
while ((match = variableRegex.exec(content)) !== null) {
|
|
89
|
+
if (match[1] && !variables.includes(match[1])) {
|
|
90
|
+
variables.push(match[1]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return variables;
|
|
94
|
+
}
|
|
95
|
+
create(input) {
|
|
96
|
+
const id = this.generateId();
|
|
97
|
+
const now = new Date().toISOString();
|
|
98
|
+
const variables = input.variables || this.extractVariables(input.content);
|
|
99
|
+
const category = input.category && VALID_CATEGORIES.includes(input.category) ? input.category : null;
|
|
100
|
+
const stmt = this.db.prepare(`
|
|
101
|
+
INSERT INTO prompts (id, name, content, category, tags, variables, is_shared, created_by, usage_count, metadata, created_at, updated_at)
|
|
102
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'local', 0, ?, ?, ?)
|
|
103
|
+
`);
|
|
104
|
+
stmt.run(id, input.name, input.content, category, JSON.stringify(input.tags || []), JSON.stringify(variables), input.isShared ? 1 : 0, JSON.stringify(input.metadata || {}), now, now);
|
|
105
|
+
return this.getById(id);
|
|
106
|
+
}
|
|
107
|
+
getById(id) {
|
|
108
|
+
const row = this.db.prepare("SELECT * FROM prompts WHERE id = ? AND deleted_at IS NULL").get(id);
|
|
109
|
+
return row ? this.rowToPrompt(row) : null;
|
|
110
|
+
}
|
|
111
|
+
list(filter, pagination) {
|
|
112
|
+
const conditions = ["deleted_at IS NULL"];
|
|
113
|
+
const params = [];
|
|
114
|
+
if (filter?.category) {
|
|
115
|
+
conditions.push("category = ?");
|
|
116
|
+
params.push(filter.category);
|
|
117
|
+
}
|
|
118
|
+
if (filter?.isShared !== undefined) {
|
|
119
|
+
conditions.push("is_shared = ?");
|
|
120
|
+
params.push(filter.isShared ? 1 : 0);
|
|
121
|
+
}
|
|
122
|
+
if (filter?.createdBy) {
|
|
123
|
+
conditions.push("created_by = ?");
|
|
124
|
+
params.push(filter.createdBy);
|
|
125
|
+
}
|
|
126
|
+
const whereClause = conditions.join(" AND ");
|
|
127
|
+
const limit = pagination?.limit || 50;
|
|
128
|
+
const offset = pagination?.offset || 0;
|
|
129
|
+
const countRow = this.db.prepare(`SELECT COUNT(*) as count FROM prompts WHERE ${whereClause}`).get(...params);
|
|
130
|
+
const rows = this.db.prepare(`SELECT * FROM prompts WHERE ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
131
|
+
let items = rows.map((r) => this.rowToPrompt(r));
|
|
132
|
+
let total = countRow.count;
|
|
133
|
+
if (filter?.tags && filter.tags.length > 0) {
|
|
134
|
+
items = items.filter((prompt) => filter.tags.some((tag) => prompt.tags.includes(tag)));
|
|
135
|
+
total = items.length;
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
items,
|
|
139
|
+
total,
|
|
140
|
+
hasMore: offset + items.length < total
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
search(query, category, limit) {
|
|
144
|
+
const conditions = [
|
|
145
|
+
"deleted_at IS NULL",
|
|
146
|
+
"(LOWER(name) LIKE ? OR LOWER(content) LIKE ?)"
|
|
147
|
+
];
|
|
148
|
+
const pattern = `%${query.toLowerCase()}%`;
|
|
149
|
+
const params = [pattern, pattern];
|
|
150
|
+
if (category) {
|
|
151
|
+
conditions.push("category = ?");
|
|
152
|
+
params.push(category);
|
|
153
|
+
}
|
|
154
|
+
const rows = this.db.prepare(`SELECT * FROM prompts WHERE ${conditions.join(" AND ")} ORDER BY usage_count DESC LIMIT ?`).all(...params, limit || 20);
|
|
155
|
+
return rows.map((r) => this.rowToPrompt(r));
|
|
156
|
+
}
|
|
157
|
+
getPopular(limit) {
|
|
158
|
+
const rows = this.db.prepare("SELECT * FROM prompts WHERE deleted_at IS NULL ORDER BY usage_count DESC LIMIT ?").all(limit || 10);
|
|
159
|
+
return rows.map((r) => this.rowToPrompt(r));
|
|
160
|
+
}
|
|
161
|
+
update(id, input) {
|
|
162
|
+
const existing = this.getById(id);
|
|
163
|
+
if (!existing)
|
|
164
|
+
return null;
|
|
165
|
+
const sets = ["updated_at = ?"];
|
|
166
|
+
const now = new Date().toISOString();
|
|
167
|
+
const params = [now];
|
|
168
|
+
if (input.name !== undefined) {
|
|
169
|
+
sets.push("name = ?");
|
|
170
|
+
params.push(input.name);
|
|
171
|
+
}
|
|
172
|
+
if (input.content !== undefined) {
|
|
173
|
+
sets.push("content = ?");
|
|
174
|
+
params.push(input.content);
|
|
175
|
+
const variables = input.variables || this.extractVariables(input.content);
|
|
176
|
+
sets.push("variables = ?");
|
|
177
|
+
params.push(JSON.stringify(variables));
|
|
178
|
+
}
|
|
179
|
+
if (input.category !== undefined) {
|
|
180
|
+
sets.push("category = ?");
|
|
181
|
+
params.push(input.category);
|
|
182
|
+
}
|
|
183
|
+
if (input.tags !== undefined) {
|
|
184
|
+
sets.push("tags = ?");
|
|
185
|
+
params.push(JSON.stringify(input.tags));
|
|
186
|
+
}
|
|
187
|
+
if (input.variables !== undefined && input.content === undefined) {
|
|
188
|
+
sets.push("variables = ?");
|
|
189
|
+
params.push(JSON.stringify(input.variables));
|
|
190
|
+
}
|
|
191
|
+
if (input.isShared !== undefined) {
|
|
192
|
+
sets.push("is_shared = ?");
|
|
193
|
+
params.push(input.isShared ? 1 : 0);
|
|
194
|
+
}
|
|
195
|
+
if (input.metadata !== undefined) {
|
|
196
|
+
sets.push("metadata = ?");
|
|
197
|
+
params.push(JSON.stringify(input.metadata));
|
|
198
|
+
}
|
|
199
|
+
params.push(id);
|
|
200
|
+
this.db.prepare(`UPDATE prompts SET ${sets.join(", ")} WHERE id = ? AND deleted_at IS NULL`).run(...params);
|
|
201
|
+
return this.getById(id);
|
|
202
|
+
}
|
|
203
|
+
delete(id) {
|
|
204
|
+
const result = this.db.prepare("UPDATE prompts SET deleted_at = datetime('now'), updated_at = datetime('now') WHERE id = ? AND deleted_at IS NULL").run(id);
|
|
205
|
+
return result.changes > 0;
|
|
206
|
+
}
|
|
207
|
+
use(id) {
|
|
208
|
+
const existing = this.getById(id);
|
|
209
|
+
if (!existing)
|
|
210
|
+
return null;
|
|
211
|
+
this.db.prepare("UPDATE prompts SET usage_count = usage_count + 1, last_used = datetime('now'), updated_at = datetime('now') WHERE id = ?").run(id);
|
|
212
|
+
return this.getById(id);
|
|
213
|
+
}
|
|
214
|
+
duplicate(id, newName) {
|
|
215
|
+
const original = this.getById(id);
|
|
216
|
+
if (!original)
|
|
217
|
+
return null;
|
|
218
|
+
return this.create({
|
|
219
|
+
name: newName,
|
|
220
|
+
content: original.content,
|
|
221
|
+
category: original.category || undefined,
|
|
222
|
+
tags: original.tags,
|
|
223
|
+
variables: original.variables,
|
|
224
|
+
isShared: original.isShared,
|
|
225
|
+
metadata: original.metadata
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
renderPrompt(content, variables) {
|
|
229
|
+
let rendered = content;
|
|
230
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
231
|
+
const placeholder = `{{${key}}}`;
|
|
232
|
+
while (rendered.includes(placeholder)) {
|
|
233
|
+
rendered = rendered.replace(placeholder, String(value));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return rendered;
|
|
237
|
+
}
|
|
238
|
+
renderById(id, variables) {
|
|
239
|
+
const prompt = this.getById(id);
|
|
240
|
+
if (!prompt)
|
|
241
|
+
return null;
|
|
242
|
+
return this.renderPrompt(prompt.content, variables);
|
|
243
|
+
}
|
|
244
|
+
close() {
|
|
245
|
+
this.db.close();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/routes/prompts.ts
|
|
250
|
+
import { Elysia, t } from "elysia";
|
|
251
|
+
var PromptCategoryEnum = t.Optional(t.Union([
|
|
252
|
+
t.Literal("GENERAL"),
|
|
253
|
+
t.Literal("CODING"),
|
|
254
|
+
t.Literal("DEBUGGING"),
|
|
255
|
+
t.Literal("REVIEW"),
|
|
256
|
+
t.Literal("DOCUMENTATION"),
|
|
257
|
+
t.Literal("TESTING"),
|
|
258
|
+
t.Literal("DEPLOYMENT"),
|
|
259
|
+
t.Literal("CUSTOM")
|
|
260
|
+
]));
|
|
261
|
+
function createPromptRoutes(promptDb) {
|
|
262
|
+
return new Elysia({ prefix: "/prompts" }).get("/", ({ query }) => {
|
|
263
|
+
const filter = {};
|
|
264
|
+
if (query.category) {
|
|
265
|
+
filter.category = query.category;
|
|
266
|
+
}
|
|
267
|
+
if (query.tags) {
|
|
268
|
+
filter.tags = query.tags.split(",").map((t2) => t2.trim());
|
|
269
|
+
}
|
|
270
|
+
if (query.isShared !== undefined) {
|
|
271
|
+
filter.isShared = query.isShared === "true";
|
|
272
|
+
}
|
|
273
|
+
if (query.createdBy) {
|
|
274
|
+
filter.createdBy = query.createdBy;
|
|
275
|
+
}
|
|
276
|
+
const pagination = {
|
|
277
|
+
limit: query.limit ? parseInt(query.limit, 10) : 50,
|
|
278
|
+
offset: query.offset ? parseInt(query.offset, 10) : 0
|
|
279
|
+
};
|
|
280
|
+
return promptDb.list(Object.keys(filter).length > 0 ? filter : undefined, pagination);
|
|
281
|
+
}, {
|
|
282
|
+
query: t.Object({
|
|
283
|
+
category: t.Optional(t.String()),
|
|
284
|
+
tags: t.Optional(t.String()),
|
|
285
|
+
isShared: t.Optional(t.String()),
|
|
286
|
+
createdBy: t.Optional(t.String()),
|
|
287
|
+
limit: t.Optional(t.String()),
|
|
288
|
+
offset: t.Optional(t.String())
|
|
289
|
+
})
|
|
290
|
+
}).get("/search", ({ query }) => {
|
|
291
|
+
const results = promptDb.search(query.q, query.category, query.limit ? parseInt(query.limit, 10) : undefined);
|
|
292
|
+
return { items: results, total: results.length };
|
|
293
|
+
}, {
|
|
294
|
+
query: t.Object({
|
|
295
|
+
q: t.String(),
|
|
296
|
+
category: t.Optional(t.String()),
|
|
297
|
+
limit: t.Optional(t.String())
|
|
298
|
+
})
|
|
299
|
+
}).get("/popular", ({ query }) => {
|
|
300
|
+
const results = promptDb.getPopular(query.limit ? parseInt(query.limit, 10) : undefined);
|
|
301
|
+
return { items: results, total: results.length };
|
|
302
|
+
}, {
|
|
303
|
+
query: t.Object({
|
|
304
|
+
limit: t.Optional(t.String())
|
|
305
|
+
})
|
|
306
|
+
}).get("/:id", ({ params, set }) => {
|
|
307
|
+
const prompt = promptDb.getById(params.id);
|
|
308
|
+
if (!prompt) {
|
|
309
|
+
set.status = 404;
|
|
310
|
+
return { error: "Prompt not found" };
|
|
311
|
+
}
|
|
312
|
+
return prompt;
|
|
313
|
+
}, {
|
|
314
|
+
params: t.Object({ id: t.String() })
|
|
315
|
+
}).post("/", ({ body, set }) => {
|
|
316
|
+
try {
|
|
317
|
+
const prompt = promptDb.create(body);
|
|
318
|
+
set.status = 201;
|
|
319
|
+
return prompt;
|
|
320
|
+
} catch (err) {
|
|
321
|
+
set.status = 400;
|
|
322
|
+
return {
|
|
323
|
+
error: err instanceof Error ? err.message : "Failed to create prompt"
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}, {
|
|
327
|
+
body: t.Object({
|
|
328
|
+
name: t.String({ minLength: 1 }),
|
|
329
|
+
content: t.String({ minLength: 1 }),
|
|
330
|
+
category: PromptCategoryEnum,
|
|
331
|
+
tags: t.Optional(t.Array(t.String())),
|
|
332
|
+
variables: t.Optional(t.Array(t.String())),
|
|
333
|
+
isShared: t.Optional(t.Boolean()),
|
|
334
|
+
metadata: t.Optional(t.Record(t.String(), t.Any()))
|
|
335
|
+
})
|
|
336
|
+
}).put("/:id", ({ params, body, set }) => {
|
|
337
|
+
const updated = promptDb.update(params.id, body);
|
|
338
|
+
if (!updated) {
|
|
339
|
+
set.status = 404;
|
|
340
|
+
return { error: "Prompt not found" };
|
|
341
|
+
}
|
|
342
|
+
return updated;
|
|
343
|
+
}, {
|
|
344
|
+
params: t.Object({ id: t.String() }),
|
|
345
|
+
body: t.Object({
|
|
346
|
+
name: t.Optional(t.String({ minLength: 1 })),
|
|
347
|
+
content: t.Optional(t.String({ minLength: 1 })),
|
|
348
|
+
category: t.Optional(t.Union([
|
|
349
|
+
t.Literal("GENERAL"),
|
|
350
|
+
t.Literal("CODING"),
|
|
351
|
+
t.Literal("DEBUGGING"),
|
|
352
|
+
t.Literal("REVIEW"),
|
|
353
|
+
t.Literal("DOCUMENTATION"),
|
|
354
|
+
t.Literal("TESTING"),
|
|
355
|
+
t.Literal("DEPLOYMENT"),
|
|
356
|
+
t.Literal("CUSTOM"),
|
|
357
|
+
t.Null()
|
|
358
|
+
])),
|
|
359
|
+
tags: t.Optional(t.Array(t.String())),
|
|
360
|
+
variables: t.Optional(t.Array(t.String())),
|
|
361
|
+
isShared: t.Optional(t.Boolean()),
|
|
362
|
+
metadata: t.Optional(t.Record(t.String(), t.Any()))
|
|
363
|
+
})
|
|
364
|
+
}).delete("/:id", ({ params, set }) => {
|
|
365
|
+
const deleted = promptDb.delete(params.id);
|
|
366
|
+
if (!deleted) {
|
|
367
|
+
set.status = 404;
|
|
368
|
+
return { error: "Prompt not found" };
|
|
369
|
+
}
|
|
370
|
+
return { success: true };
|
|
371
|
+
}, {
|
|
372
|
+
params: t.Object({ id: t.String() })
|
|
373
|
+
}).post("/:id/use", ({ params, set }) => {
|
|
374
|
+
const prompt = promptDb.use(params.id);
|
|
375
|
+
if (!prompt) {
|
|
376
|
+
set.status = 404;
|
|
377
|
+
return { error: "Prompt not found" };
|
|
378
|
+
}
|
|
379
|
+
return prompt;
|
|
380
|
+
}, {
|
|
381
|
+
params: t.Object({ id: t.String() })
|
|
382
|
+
}).post("/:id/duplicate", ({ params, body, set }) => {
|
|
383
|
+
try {
|
|
384
|
+
const prompt = promptDb.duplicate(params.id, body.name);
|
|
385
|
+
if (!prompt) {
|
|
386
|
+
set.status = 404;
|
|
387
|
+
return { error: "Prompt not found" };
|
|
388
|
+
}
|
|
389
|
+
set.status = 201;
|
|
390
|
+
return prompt;
|
|
391
|
+
} catch (err) {
|
|
392
|
+
set.status = 400;
|
|
393
|
+
return {
|
|
394
|
+
error: err instanceof Error ? err.message : "Failed to duplicate prompt"
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
}, {
|
|
398
|
+
params: t.Object({ id: t.String() }),
|
|
399
|
+
body: t.Object({
|
|
400
|
+
name: t.String({ minLength: 1 })
|
|
401
|
+
})
|
|
402
|
+
}).post("/:id/render", ({ params, body, set }) => {
|
|
403
|
+
const rendered = promptDb.renderById(params.id, body.variables);
|
|
404
|
+
if (rendered === null) {
|
|
405
|
+
set.status = 404;
|
|
406
|
+
return { error: "Prompt not found" };
|
|
407
|
+
}
|
|
408
|
+
return { rendered };
|
|
409
|
+
}, {
|
|
410
|
+
params: t.Object({ id: t.String() }),
|
|
411
|
+
body: t.Object({
|
|
412
|
+
variables: t.Record(t.String(), t.Any())
|
|
413
|
+
})
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/index.ts
|
|
5
418
|
var AI_TOOLS = [
|
|
6
419
|
{
|
|
7
420
|
name: "claude-code",
|
|
@@ -71,20 +484,60 @@ function runInstallCommand(command) {
|
|
|
71
484
|
function findConfigFiles(tool, directory) {
|
|
72
485
|
return tool.configFiles.filter((f) => {
|
|
73
486
|
try {
|
|
74
|
-
return Bun.file(
|
|
487
|
+
return Bun.file(join2(directory, f)).size > 0;
|
|
75
488
|
} catch {
|
|
76
489
|
return false;
|
|
77
490
|
}
|
|
78
491
|
});
|
|
79
492
|
}
|
|
493
|
+
var promptDb = null;
|
|
494
|
+
function getPromptDb() {
|
|
495
|
+
if (!promptDb) {
|
|
496
|
+
promptDb = new PromptDatabase;
|
|
497
|
+
}
|
|
498
|
+
return promptDb;
|
|
499
|
+
}
|
|
80
500
|
var vibePlugin = {
|
|
81
501
|
name: "ai",
|
|
82
|
-
version: "
|
|
83
|
-
description: "AI tool management and integration",
|
|
84
|
-
tags: ["cli", "integration"],
|
|
502
|
+
version: "3.0.0",
|
|
503
|
+
description: "AI tool management, prompt templates, and integration",
|
|
504
|
+
tags: ["backend", "cli", "integration"],
|
|
85
505
|
cliCommand: "ai",
|
|
506
|
+
apiPrefix: "/api/ai",
|
|
507
|
+
createRoutes() {
|
|
508
|
+
const db = getPromptDb();
|
|
509
|
+
return new Elysia2().get("/tools", () => {
|
|
510
|
+
return {
|
|
511
|
+
tools: AI_TOOLS.map((tool) => {
|
|
512
|
+
const { installed, version } = isToolInstalled(tool);
|
|
513
|
+
const configs = findConfigFiles(tool, process.cwd());
|
|
514
|
+
return {
|
|
515
|
+
name: tool.name,
|
|
516
|
+
displayName: tool.displayName,
|
|
517
|
+
description: tool.description,
|
|
518
|
+
installed,
|
|
519
|
+
version: installed ? version.split(`
|
|
520
|
+
`)[0] : "",
|
|
521
|
+
configFiles: tool.configFiles,
|
|
522
|
+
foundConfigs: configs,
|
|
523
|
+
installCommand: tool.installCommand
|
|
524
|
+
};
|
|
525
|
+
})
|
|
526
|
+
};
|
|
527
|
+
}).use(createPromptRoutes(db));
|
|
528
|
+
},
|
|
529
|
+
onServerStart(_app, hostServices) {
|
|
530
|
+
getPromptDb();
|
|
531
|
+
hostServices?.logger?.info("ai-plugin", "AI plugin started \u2014 prompt database initialized");
|
|
532
|
+
},
|
|
533
|
+
onServerStop() {
|
|
534
|
+
if (promptDb) {
|
|
535
|
+
promptDb.close();
|
|
536
|
+
promptDb = null;
|
|
537
|
+
}
|
|
538
|
+
},
|
|
86
539
|
onCliSetup(program, _hostServices) {
|
|
87
|
-
const aiCmd = program.command("ai").description("Manage AI coding tools and
|
|
540
|
+
const aiCmd = program.command("ai").description("Manage AI coding tools and prompt templates");
|
|
88
541
|
aiCmd.command("list").description("List all supported AI tools and their status").option("--cwd <dir>", "Project directory to check configs", process.cwd()).action((options) => {
|
|
89
542
|
console.log(`
|
|
90
543
|
\x1B[1m\u2500\u2500 AI Tools \u2500\u2500\x1B[0m
|
|
@@ -105,10 +558,10 @@ var vibePlugin = {
|
|
|
105
558
|
console.log();
|
|
106
559
|
}
|
|
107
560
|
});
|
|
108
|
-
aiCmd.command("install").description("Install an AI tool").argument("<tool>", `Tool name (${AI_TOOLS.map((
|
|
109
|
-
const tool = AI_TOOLS.find((
|
|
561
|
+
aiCmd.command("install").description("Install an AI tool").argument("<tool>", `Tool name (${AI_TOOLS.map((t2) => t2.name).join(", ")})`).action((toolName) => {
|
|
562
|
+
const tool = AI_TOOLS.find((t2) => t2.name === toolName);
|
|
110
563
|
if (!tool) {
|
|
111
|
-
console.error(`\x1B[31mError:\x1B[0m Unknown tool '${toolName}'. Available: ${AI_TOOLS.map((
|
|
564
|
+
console.error(`\x1B[31mError:\x1B[0m Unknown tool '${toolName}'. Available: ${AI_TOOLS.map((t2) => t2.name).join(", ")}`);
|
|
112
565
|
process.exit(1);
|
|
113
566
|
}
|
|
114
567
|
if (!tool.installCommand) {
|
|
@@ -135,14 +588,14 @@ var vibePlugin = {
|
|
|
135
588
|
}
|
|
136
589
|
});
|
|
137
590
|
aiCmd.command("init").description("Initialize AI tool config in the current project").argument("<tool>", "Tool name").option("--cwd <dir>", "Project directory", process.cwd()).action(async (toolName, options) => {
|
|
138
|
-
const tool = AI_TOOLS.find((
|
|
591
|
+
const tool = AI_TOOLS.find((t2) => t2.name === toolName);
|
|
139
592
|
if (!tool) {
|
|
140
|
-
console.error(`\x1B[31mError:\x1B[0m Unknown tool '${toolName}'. Available: ${AI_TOOLS.map((
|
|
593
|
+
console.error(`\x1B[31mError:\x1B[0m Unknown tool '${toolName}'. Available: ${AI_TOOLS.map((t2) => t2.name).join(", ")}`);
|
|
141
594
|
process.exit(1);
|
|
142
595
|
}
|
|
143
596
|
const dir = options.cwd;
|
|
144
597
|
const primaryConfig = tool.configFiles[0];
|
|
145
|
-
const configPath =
|
|
598
|
+
const configPath = join2(dir, primaryConfig);
|
|
146
599
|
try {
|
|
147
600
|
if (Bun.file(configPath).size >= 0) {
|
|
148
601
|
console.log(` \x1B[33m\u26A0\x1B[0m ${primaryConfig} already exists in ${dir}`);
|
|
@@ -151,8 +604,8 @@ var vibePlugin = {
|
|
|
151
604
|
} catch {}
|
|
152
605
|
const segments = primaryConfig.split("/");
|
|
153
606
|
if (segments.length > 1) {
|
|
154
|
-
const parentDir =
|
|
155
|
-
|
|
607
|
+
const parentDir = join2(dir, ...segments.slice(0, -1));
|
|
608
|
+
mkdirSync2(parentDir, { recursive: true });
|
|
156
609
|
}
|
|
157
610
|
const content = generateStarterConfig(tool);
|
|
158
611
|
await Bun.write(configPath, content);
|
|
@@ -179,6 +632,71 @@ var vibePlugin = {
|
|
|
179
632
|
`);
|
|
180
633
|
}
|
|
181
634
|
});
|
|
635
|
+
const promptsCmd = aiCmd.command("prompts").description("Manage prompt templates");
|
|
636
|
+
promptsCmd.command("list").description("List all prompt templates").option("--shared", "Show only shared prompts").option("--category <cat>", "Filter by category").option("--limit <n>", "Max results", "20").action((options) => {
|
|
637
|
+
const db = getPromptDb();
|
|
638
|
+
const result = db.list({
|
|
639
|
+
isShared: options.shared !== undefined ? options.shared : undefined,
|
|
640
|
+
category: options.category
|
|
641
|
+
}, { limit: parseInt(options.limit, 10) });
|
|
642
|
+
if (result.items.length === 0) {
|
|
643
|
+
console.log(`
|
|
644
|
+
No prompts found.
|
|
645
|
+
`);
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
console.log(`
|
|
649
|
+
\x1B[1m\u2500\u2500 Prompts (${result.total}) \u2500\u2500\x1B[0m
|
|
650
|
+
`);
|
|
651
|
+
for (const prompt of result.items) {
|
|
652
|
+
const shared = prompt.isShared ? " \x1B[36m[shared]\x1B[0m" : "";
|
|
653
|
+
const tags = prompt.tags.length > 0 ? ` \x1B[33m[${prompt.tags.join(", ")}]\x1B[0m` : "";
|
|
654
|
+
const uses = prompt.usageCount > 0 ? ` \x1B[90m(${prompt.usageCount} uses)\x1B[0m` : "";
|
|
655
|
+
console.log(` \x1B[1m${prompt.name}\x1B[0m${shared}${tags}${uses}`);
|
|
656
|
+
const preview = prompt.content.replace(/\n/g, " ").slice(0, 60).trim();
|
|
657
|
+
console.log(` ${preview}${prompt.content.length > 60 ? "..." : ""}`);
|
|
658
|
+
console.log();
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
promptsCmd.command("search").description("Search prompts by name or content").argument("<query>", "Search query").option("--limit <n>", "Max results", "10").action((query, options) => {
|
|
662
|
+
const db = getPromptDb();
|
|
663
|
+
const results = db.search(query, undefined, parseInt(options.limit, 10));
|
|
664
|
+
if (results.length === 0) {
|
|
665
|
+
console.log(`
|
|
666
|
+
No prompts matching "${query}".
|
|
667
|
+
`);
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
console.log(`
|
|
671
|
+
\x1B[1m\u2500\u2500 Search: "${query}" (${results.length} results) \u2500\u2500\x1B[0m
|
|
672
|
+
`);
|
|
673
|
+
for (const prompt of results) {
|
|
674
|
+
console.log(` \x1B[1m${prompt.name}\x1B[0m \x1B[90m(${prompt.usageCount} uses)\x1B[0m`);
|
|
675
|
+
const preview = prompt.content.replace(/\n/g, " ").slice(0, 60).trim();
|
|
676
|
+
console.log(` ${preview}${prompt.content.length > 60 ? "..." : ""}`);
|
|
677
|
+
console.log();
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
promptsCmd.command("show").description("Show a prompt by ID").argument("<id>", "Prompt ID").action((id) => {
|
|
681
|
+
const db = getPromptDb();
|
|
682
|
+
const prompt = db.getById(id);
|
|
683
|
+
if (!prompt) {
|
|
684
|
+
console.error(`\x1B[31mError:\x1B[0m Prompt not found: ${id}`);
|
|
685
|
+
process.exit(1);
|
|
686
|
+
}
|
|
687
|
+
console.log(`
|
|
688
|
+
\x1B[1m${prompt.name}\x1B[0m`);
|
|
689
|
+
if (prompt.tags.length > 0) {
|
|
690
|
+
console.log(` Tags: ${prompt.tags.join(", ")}`);
|
|
691
|
+
}
|
|
692
|
+
if (prompt.variables.length > 0) {
|
|
693
|
+
console.log(` Variables: ${prompt.variables.map((v) => `{{${v}}}`).join(", ")}`);
|
|
694
|
+
}
|
|
695
|
+
console.log(` Shared: ${prompt.isShared ? "yes" : "no"} | Uses: ${prompt.usageCount}`);
|
|
696
|
+
console.log(`
|
|
697
|
+
${prompt.content}
|
|
698
|
+
`);
|
|
699
|
+
});
|
|
182
700
|
}
|
|
183
701
|
};
|
|
184
702
|
function generateStarterConfig(tool) {
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/vibe-plugin-ai",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"bun": ">=1.3.0"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"build": "bun build ./src/index.ts --outdir ./dist --target bun",
|
|
10
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun --external elysia",
|
|
11
11
|
"lint": "eslint ./src",
|
|
12
12
|
"format": "bunx prettier . --write",
|
|
13
13
|
"format:check": "bunx prettier . --check",
|
|
@@ -35,15 +35,21 @@
|
|
|
35
35
|
"url": "https://github.com/tvvignesh"
|
|
36
36
|
},
|
|
37
37
|
"license": "SEE LICENSE IN LICENSE",
|
|
38
|
-
"description": "AI tool management plugin for VibeControls Agent — manage
|
|
38
|
+
"description": "AI tool management and prompt templates plugin for VibeControls Agent — manage AI tools, prompt templates with variables (Bun runtime)",
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"elysia": "^1.3.0"
|
|
41
|
+
},
|
|
39
42
|
"devDependencies": {
|
|
43
|
+
"@eslint/js": "^10.0.1",
|
|
40
44
|
"@types/bun": "^1.2.16",
|
|
41
45
|
"bun-types": "^1.3.9",
|
|
42
46
|
"commander": "^14.0.3",
|
|
43
47
|
"eslint": "^9.30.1",
|
|
48
|
+
"globals": "^17.3.0",
|
|
44
49
|
"prettier": "^3.6.2",
|
|
45
50
|
"rimraf": "^6.0.1",
|
|
46
|
-
"typescript": "^5.8.3"
|
|
51
|
+
"typescript": "^5.8.3",
|
|
52
|
+
"typescript-eslint": "^8.56.0"
|
|
47
53
|
},
|
|
48
54
|
"peerDependencies": {
|
|
49
55
|
"@burdenoff/vibe-agent": ">=2.0.0"
|