@hasna/prompts 0.2.1 → 0.3.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/cli/index.js +1090 -92
- package/dist/db/database.d.ts +1 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/projects.d.ts +10 -0
- package/dist/db/projects.d.ts.map +1 -0
- package/dist/db/prompts.d.ts +8 -0
- package/dist/db/prompts.d.ts.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +141 -3
- package/dist/lib/audit.d.ts +16 -0
- package/dist/lib/audit.d.ts.map +1 -0
- package/dist/lib/completion.d.ts +3 -0
- package/dist/lib/completion.d.ts.map +1 -0
- package/dist/lib/diff.d.ts +8 -0
- package/dist/lib/diff.d.ts.map +1 -0
- package/dist/lib/importer.d.ts +8 -0
- package/dist/lib/importer.d.ts.map +1 -1
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/mcp/index.js +553 -15
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +175 -4
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -4103,6 +4103,41 @@ function runMigrations(db) {
|
|
|
4103
4103
|
name: "003_pinned",
|
|
4104
4104
|
sql: `ALTER TABLE prompts ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0;`
|
|
4105
4105
|
},
|
|
4106
|
+
{
|
|
4107
|
+
name: "004_projects",
|
|
4108
|
+
sql: `
|
|
4109
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
4110
|
+
id TEXT PRIMARY KEY,
|
|
4111
|
+
name TEXT NOT NULL UNIQUE,
|
|
4112
|
+
slug TEXT NOT NULL UNIQUE,
|
|
4113
|
+
description TEXT,
|
|
4114
|
+
path TEXT,
|
|
4115
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
4116
|
+
);
|
|
4117
|
+
ALTER TABLE prompts ADD COLUMN project_id TEXT REFERENCES projects(id) ON DELETE SET NULL;
|
|
4118
|
+
CREATE INDEX IF NOT EXISTS idx_prompts_project_id ON prompts(project_id);
|
|
4119
|
+
`
|
|
4120
|
+
},
|
|
4121
|
+
{
|
|
4122
|
+
name: "005_chaining",
|
|
4123
|
+
sql: `ALTER TABLE prompts ADD COLUMN next_prompt TEXT;`
|
|
4124
|
+
},
|
|
4125
|
+
{
|
|
4126
|
+
name: "006_expiry",
|
|
4127
|
+
sql: `ALTER TABLE prompts ADD COLUMN expires_at TEXT;`
|
|
4128
|
+
},
|
|
4129
|
+
{
|
|
4130
|
+
name: "007_usage_log",
|
|
4131
|
+
sql: `
|
|
4132
|
+
CREATE TABLE IF NOT EXISTS usage_log (
|
|
4133
|
+
id TEXT PRIMARY KEY,
|
|
4134
|
+
prompt_id TEXT NOT NULL REFERENCES prompts(id) ON DELETE CASCADE,
|
|
4135
|
+
used_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
4136
|
+
);
|
|
4137
|
+
CREATE INDEX IF NOT EXISTS idx_usage_log_prompt_id ON usage_log(prompt_id);
|
|
4138
|
+
CREATE INDEX IF NOT EXISTS idx_usage_log_used_at ON usage_log(used_at);
|
|
4139
|
+
`
|
|
4140
|
+
},
|
|
4106
4141
|
{
|
|
4107
4142
|
name: "002_fts5",
|
|
4108
4143
|
sql: `
|
|
@@ -4143,6 +4178,24 @@ function runMigrations(db) {
|
|
|
4143
4178
|
db.run("INSERT INTO _migrations (name) VALUES (?)", [migration.name]);
|
|
4144
4179
|
}
|
|
4145
4180
|
}
|
|
4181
|
+
function resolveProject(db, idOrSlug) {
|
|
4182
|
+
const byId = db.query("SELECT id FROM projects WHERE id = ?").get(idOrSlug);
|
|
4183
|
+
if (byId)
|
|
4184
|
+
return byId.id;
|
|
4185
|
+
const bySlug = db.query("SELECT id FROM projects WHERE slug = ?").get(idOrSlug);
|
|
4186
|
+
if (bySlug)
|
|
4187
|
+
return bySlug.id;
|
|
4188
|
+
const byName = db.query("SELECT id FROM projects WHERE lower(name) = ?").get(idOrSlug.toLowerCase());
|
|
4189
|
+
if (byName)
|
|
4190
|
+
return byName.id;
|
|
4191
|
+
const byPrefix = db.query("SELECT id FROM projects WHERE id LIKE ? LIMIT 2").all(`${idOrSlug}%`);
|
|
4192
|
+
if (byPrefix.length === 1 && byPrefix[0])
|
|
4193
|
+
return byPrefix[0].id;
|
|
4194
|
+
const bySlugPrefix = db.query("SELECT id FROM projects WHERE slug LIKE ? LIMIT 2").all(`${idOrSlug}%`);
|
|
4195
|
+
if (bySlugPrefix.length === 1 && bySlugPrefix[0])
|
|
4196
|
+
return bySlugPrefix[0].id;
|
|
4197
|
+
return null;
|
|
4198
|
+
}
|
|
4146
4199
|
function hasFts(db) {
|
|
4147
4200
|
return db.query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='prompts_fts'").get() !== null;
|
|
4148
4201
|
}
|
|
@@ -4360,6 +4413,12 @@ class DuplicateSlugError extends Error {
|
|
|
4360
4413
|
this.name = "DuplicateSlugError";
|
|
4361
4414
|
}
|
|
4362
4415
|
}
|
|
4416
|
+
class ProjectNotFoundError extends Error {
|
|
4417
|
+
constructor(id) {
|
|
4418
|
+
super(`Project not found: ${id}`);
|
|
4419
|
+
this.name = "ProjectNotFoundError";
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4363
4422
|
|
|
4364
4423
|
// src/db/prompts.ts
|
|
4365
4424
|
function rowToPrompt(row) {
|
|
@@ -4374,6 +4433,9 @@ function rowToPrompt(row) {
|
|
|
4374
4433
|
tags: JSON.parse(row["tags"] || "[]"),
|
|
4375
4434
|
variables: JSON.parse(row["variables"] || "[]"),
|
|
4376
4435
|
pinned: Boolean(row["pinned"]),
|
|
4436
|
+
next_prompt: row["next_prompt"] ?? null,
|
|
4437
|
+
expires_at: row["expires_at"] ?? null,
|
|
4438
|
+
project_id: row["project_id"] ?? null,
|
|
4377
4439
|
is_template: Boolean(row["is_template"]),
|
|
4378
4440
|
source: row["source"],
|
|
4379
4441
|
version: row["version"],
|
|
@@ -4397,11 +4459,12 @@ function createPrompt(input) {
|
|
|
4397
4459
|
ensureCollection(collection);
|
|
4398
4460
|
const tags = JSON.stringify(input.tags || []);
|
|
4399
4461
|
const source = input.source || "manual";
|
|
4462
|
+
const project_id = input.project_id ?? null;
|
|
4400
4463
|
const vars = extractVariables(input.body);
|
|
4401
4464
|
const variables = JSON.stringify(vars.map((v) => ({ name: v, required: true })));
|
|
4402
4465
|
const is_template = vars.length > 0 ? 1 : 0;
|
|
4403
|
-
db.run(`INSERT INTO prompts (id, name, slug, title, body, description, collection, tags, variables, is_template, source)
|
|
4404
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, name, slug, input.title, input.body, input.description ?? null, collection, tags, variables, is_template, source]);
|
|
4466
|
+
db.run(`INSERT INTO prompts (id, name, slug, title, body, description, collection, tags, variables, is_template, source, project_id)
|
|
4467
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, name, slug, input.title, input.body, input.description ?? null, collection, tags, variables, is_template, source, project_id]);
|
|
4405
4468
|
db.run(`INSERT INTO prompt_versions (id, prompt_id, body, version, changed_by)
|
|
4406
4469
|
VALUES (?, ?, ?, 1, ?)`, [generateId("VER"), id, input.body, input.changed_by ?? null]);
|
|
4407
4470
|
return getPrompt(id);
|
|
@@ -4445,10 +4508,16 @@ function listPrompts(filter = {}) {
|
|
|
4445
4508
|
params.push(`%"${tag}"%`);
|
|
4446
4509
|
}
|
|
4447
4510
|
}
|
|
4511
|
+
let orderBy = "pinned DESC, use_count DESC, updated_at DESC";
|
|
4512
|
+
if (filter.project_id !== undefined && filter.project_id !== null) {
|
|
4513
|
+
conditions.push("(project_id = ? OR project_id IS NULL)");
|
|
4514
|
+
params.push(filter.project_id);
|
|
4515
|
+
orderBy = `(CASE WHEN project_id = '${filter.project_id}' THEN 0 ELSE 1 END), pinned DESC, use_count DESC, updated_at DESC`;
|
|
4516
|
+
}
|
|
4448
4517
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
4449
4518
|
const limit = filter.limit ?? 100;
|
|
4450
4519
|
const offset = filter.offset ?? 0;
|
|
4451
|
-
const rows = db.query(`SELECT * FROM prompts ${where} ORDER BY
|
|
4520
|
+
const rows = db.query(`SELECT * FROM prompts ${where} ORDER BY ${orderBy} LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
4452
4521
|
return rows.map(rowToPrompt);
|
|
4453
4522
|
}
|
|
4454
4523
|
function updatePrompt(idOrSlug, input) {
|
|
@@ -4464,6 +4533,7 @@ function updatePrompt(idOrSlug, input) {
|
|
|
4464
4533
|
description = COALESCE(?, description),
|
|
4465
4534
|
collection = COALESCE(?, collection),
|
|
4466
4535
|
tags = COALESCE(?, tags),
|
|
4536
|
+
next_prompt = CASE WHEN ? IS NOT NULL THEN ? ELSE next_prompt END,
|
|
4467
4537
|
variables = ?,
|
|
4468
4538
|
is_template = ?,
|
|
4469
4539
|
version = version + 1,
|
|
@@ -4474,6 +4544,8 @@ function updatePrompt(idOrSlug, input) {
|
|
|
4474
4544
|
input.description ?? null,
|
|
4475
4545
|
input.collection ?? null,
|
|
4476
4546
|
input.tags ? JSON.stringify(input.tags) : null,
|
|
4547
|
+
"next_prompt" in input ? input.next_prompt ?? "" : null,
|
|
4548
|
+
"next_prompt" in input ? input.next_prompt ?? null : null,
|
|
4477
4549
|
variables,
|
|
4478
4550
|
is_template,
|
|
4479
4551
|
prompt.id,
|
|
@@ -4496,6 +4568,30 @@ function usePrompt(idOrSlug) {
|
|
|
4496
4568
|
const db = getDatabase();
|
|
4497
4569
|
const prompt = requirePrompt(idOrSlug);
|
|
4498
4570
|
db.run("UPDATE prompts SET use_count = use_count + 1, last_used_at = datetime('now') WHERE id = ?", [prompt.id]);
|
|
4571
|
+
db.run("INSERT INTO usage_log (id, prompt_id) VALUES (?, ?)", [generateId("UL"), prompt.id]);
|
|
4572
|
+
return requirePrompt(prompt.id);
|
|
4573
|
+
}
|
|
4574
|
+
function getTrending(days = 7, limit = 10) {
|
|
4575
|
+
const db = getDatabase();
|
|
4576
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
4577
|
+
return db.query(`SELECT p.id, p.slug, p.title, COUNT(ul.id) as uses
|
|
4578
|
+
FROM usage_log ul
|
|
4579
|
+
JOIN prompts p ON p.id = ul.prompt_id
|
|
4580
|
+
WHERE ul.used_at >= ?
|
|
4581
|
+
GROUP BY p.id
|
|
4582
|
+
ORDER BY uses DESC
|
|
4583
|
+
LIMIT ?`).all(cutoff, limit);
|
|
4584
|
+
}
|
|
4585
|
+
function setExpiry(idOrSlug, expiresAt) {
|
|
4586
|
+
const db = getDatabase();
|
|
4587
|
+
const prompt = requirePrompt(idOrSlug);
|
|
4588
|
+
db.run("UPDATE prompts SET expires_at = ?, updated_at = datetime('now') WHERE id = ?", [expiresAt, prompt.id]);
|
|
4589
|
+
return requirePrompt(prompt.id);
|
|
4590
|
+
}
|
|
4591
|
+
function setNextPrompt(idOrSlug, nextSlug) {
|
|
4592
|
+
const db = getDatabase();
|
|
4593
|
+
const prompt = requirePrompt(idOrSlug);
|
|
4594
|
+
db.run("UPDATE prompts SET next_prompt = ?, updated_at = datetime('now') WHERE id = ?", [nextSlug, prompt.id]);
|
|
4499
4595
|
return requirePrompt(prompt.id);
|
|
4500
4596
|
}
|
|
4501
4597
|
function pinPrompt(idOrSlug, pinned) {
|
|
@@ -4606,6 +4702,52 @@ function registerAgent(name, description) {
|
|
|
4606
4702
|
return rowToAgent(db.query("SELECT * FROM agents WHERE id = ?").get(id));
|
|
4607
4703
|
}
|
|
4608
4704
|
|
|
4705
|
+
// src/db/projects.ts
|
|
4706
|
+
function rowToProject(row, promptCount) {
|
|
4707
|
+
return {
|
|
4708
|
+
id: row["id"],
|
|
4709
|
+
name: row["name"],
|
|
4710
|
+
slug: row["slug"],
|
|
4711
|
+
description: row["description"] ?? null,
|
|
4712
|
+
path: row["path"] ?? null,
|
|
4713
|
+
prompt_count: promptCount,
|
|
4714
|
+
created_at: row["created_at"]
|
|
4715
|
+
};
|
|
4716
|
+
}
|
|
4717
|
+
function createProject(input) {
|
|
4718
|
+
const db = getDatabase();
|
|
4719
|
+
const id = generateId("proj");
|
|
4720
|
+
const slug = generateSlug(input.name);
|
|
4721
|
+
db.run(`INSERT INTO projects (id, name, slug, description, path) VALUES (?, ?, ?, ?, ?)`, [id, input.name, slug, input.description ?? null, input.path ?? null]);
|
|
4722
|
+
return getProject(id);
|
|
4723
|
+
}
|
|
4724
|
+
function getProject(idOrSlug) {
|
|
4725
|
+
const db = getDatabase();
|
|
4726
|
+
const id = resolveProject(db, idOrSlug);
|
|
4727
|
+
if (!id)
|
|
4728
|
+
return null;
|
|
4729
|
+
const row = db.query("SELECT * FROM projects WHERE id = ?").get(id);
|
|
4730
|
+
if (!row)
|
|
4731
|
+
return null;
|
|
4732
|
+
const countRow = db.query("SELECT COUNT(*) as n FROM prompts WHERE project_id = ?").get(id);
|
|
4733
|
+
return rowToProject(row, countRow.n);
|
|
4734
|
+
}
|
|
4735
|
+
function listProjects() {
|
|
4736
|
+
const db = getDatabase();
|
|
4737
|
+
const rows = db.query("SELECT * FROM projects ORDER BY name ASC").all();
|
|
4738
|
+
return rows.map((row) => {
|
|
4739
|
+
const countRow = db.query("SELECT COUNT(*) as n FROM prompts WHERE project_id = ?").get(row["id"]);
|
|
4740
|
+
return rowToProject(row, countRow.n);
|
|
4741
|
+
});
|
|
4742
|
+
}
|
|
4743
|
+
function deleteProject(idOrSlug) {
|
|
4744
|
+
const db = getDatabase();
|
|
4745
|
+
const id = resolveProject(db, idOrSlug);
|
|
4746
|
+
if (!id)
|
|
4747
|
+
throw new ProjectNotFoundError(idOrSlug);
|
|
4748
|
+
db.run("DELETE FROM projects WHERE id = ?", [id]);
|
|
4749
|
+
}
|
|
4750
|
+
|
|
4609
4751
|
// src/lib/search.ts
|
|
4610
4752
|
function rowToSearchResult(row, snippet) {
|
|
4611
4753
|
return {
|
|
@@ -4620,6 +4762,9 @@ function rowToSearchResult(row, snippet) {
|
|
|
4620
4762
|
tags: JSON.parse(row["tags"] || "[]"),
|
|
4621
4763
|
variables: JSON.parse(row["variables"] || "[]"),
|
|
4622
4764
|
pinned: Boolean(row["pinned"]),
|
|
4765
|
+
next_prompt: row["next_prompt"] ?? null,
|
|
4766
|
+
expires_at: row["expires_at"] ?? null,
|
|
4767
|
+
project_id: row["project_id"] ?? null,
|
|
4623
4768
|
is_template: Boolean(row["is_template"]),
|
|
4624
4769
|
source: row["source"],
|
|
4625
4770
|
version: row["version"],
|
|
@@ -4663,6 +4808,10 @@ function searchPrompts(query, filter = {}) {
|
|
|
4663
4808
|
for (const tag of filter.tags)
|
|
4664
4809
|
params.push(`%"${tag}"%`);
|
|
4665
4810
|
}
|
|
4811
|
+
if (filter.project_id !== undefined && filter.project_id !== null) {
|
|
4812
|
+
conditions.push("(p.project_id = ? OR p.project_id IS NULL)");
|
|
4813
|
+
params.push(filter.project_id);
|
|
4814
|
+
}
|
|
4666
4815
|
const where = conditions.length > 0 ? `AND ${conditions.join(" AND ")}` : "";
|
|
4667
4816
|
const limit = filter.limit ?? 50;
|
|
4668
4817
|
const offset = filter.offset ?? 0;
|
|
@@ -4738,6 +4887,77 @@ function exportToJson(collection) {
|
|
|
4738
4887
|
const prompts = listPrompts({ collection, limit: 1e4 });
|
|
4739
4888
|
return { prompts, exported_at: new Date().toISOString(), collection };
|
|
4740
4889
|
}
|
|
4890
|
+
function markdownToImportItem(content, filename) {
|
|
4891
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n+([\s\S]*)$/);
|
|
4892
|
+
if (!frontmatterMatch) {
|
|
4893
|
+
if (!filename)
|
|
4894
|
+
return null;
|
|
4895
|
+
const title2 = filename.replace(/\.md$/, "").replace(/-/g, " ");
|
|
4896
|
+
return { title: title2, body: content.trim() };
|
|
4897
|
+
}
|
|
4898
|
+
const frontmatter = frontmatterMatch[1] ?? "";
|
|
4899
|
+
const body = (frontmatterMatch[2] ?? "").trim();
|
|
4900
|
+
const get = (key) => {
|
|
4901
|
+
const m = frontmatter.match(new RegExp(`^${key}:\\s*(.+)$`, "m"));
|
|
4902
|
+
return m ? (m[1] ?? "").trim() : null;
|
|
4903
|
+
};
|
|
4904
|
+
const title = get("title") ?? (filename?.replace(/\.md$/, "").replace(/-/g, " ") ?? "Untitled");
|
|
4905
|
+
const slug = get("slug") ?? undefined;
|
|
4906
|
+
const collection = get("collection") ?? undefined;
|
|
4907
|
+
const description = get("description") ?? undefined;
|
|
4908
|
+
const tagsStr = get("tags");
|
|
4909
|
+
let tags;
|
|
4910
|
+
if (tagsStr) {
|
|
4911
|
+
const inner = tagsStr.replace(/^\[/, "").replace(/\]$/, "");
|
|
4912
|
+
tags = inner.split(",").map((t) => t.trim()).filter(Boolean);
|
|
4913
|
+
}
|
|
4914
|
+
return { title, slug, body, collection, tags, description };
|
|
4915
|
+
}
|
|
4916
|
+
function scanAndImportSlashCommands(rootDir, changedBy) {
|
|
4917
|
+
const { existsSync: existsSync2, readdirSync, readFileSync } = __require("fs");
|
|
4918
|
+
const { join: join2 } = __require("path");
|
|
4919
|
+
const home = process.env["HOME"] ?? "~";
|
|
4920
|
+
const sources = [
|
|
4921
|
+
{ dir: join2(rootDir, ".claude", "commands"), collection: "claude-commands", tags: ["claude", "slash-command"] },
|
|
4922
|
+
{ dir: join2(home, ".claude", "commands"), collection: "claude-commands", tags: ["claude", "slash-command"] },
|
|
4923
|
+
{ dir: join2(rootDir, ".codex", "skills"), collection: "codex-skills", tags: ["codex", "skill"] },
|
|
4924
|
+
{ dir: join2(home, ".codex", "skills"), collection: "codex-skills", tags: ["codex", "skill"] },
|
|
4925
|
+
{ dir: join2(rootDir, ".gemini", "extensions"), collection: "gemini-extensions", tags: ["gemini", "extension"] },
|
|
4926
|
+
{ dir: join2(home, ".gemini", "extensions"), collection: "gemini-extensions", tags: ["gemini", "extension"] }
|
|
4927
|
+
];
|
|
4928
|
+
const files = [];
|
|
4929
|
+
const scanned = [];
|
|
4930
|
+
for (const { dir, collection, tags } of sources) {
|
|
4931
|
+
if (!existsSync2(dir))
|
|
4932
|
+
continue;
|
|
4933
|
+
let entries;
|
|
4934
|
+
try {
|
|
4935
|
+
entries = readdirSync(dir);
|
|
4936
|
+
} catch {
|
|
4937
|
+
continue;
|
|
4938
|
+
}
|
|
4939
|
+
for (const entry of entries) {
|
|
4940
|
+
if (!entry.endsWith(".md"))
|
|
4941
|
+
continue;
|
|
4942
|
+
const filePath = join2(dir, entry);
|
|
4943
|
+
try {
|
|
4944
|
+
const content = readFileSync(filePath, "utf-8");
|
|
4945
|
+
files.push({ filename: entry, content, collection, tags });
|
|
4946
|
+
scanned.push({ source: dir, file: entry });
|
|
4947
|
+
} catch {}
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
4950
|
+
const items = files.map((f) => {
|
|
4951
|
+
const base = markdownToImportItem(f.content, f.filename);
|
|
4952
|
+
if (base)
|
|
4953
|
+
return { ...base, collection: base.collection ?? f.collection, tags: base.tags ?? f.tags };
|
|
4954
|
+
const name = f.filename.replace(/\.md$/, "");
|
|
4955
|
+
const title = name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
4956
|
+
return { title, slug: name, body: f.content.trim(), collection: f.collection, tags: f.tags };
|
|
4957
|
+
});
|
|
4958
|
+
const imported = importFromJson(items, changedBy);
|
|
4959
|
+
return { scanned, imported };
|
|
4960
|
+
}
|
|
4741
4961
|
|
|
4742
4962
|
// src/lib/mementos.ts
|
|
4743
4963
|
async function maybeSaveMemento(opts) {
|
|
@@ -4763,6 +4983,48 @@ async function maybeSaveMemento(opts) {
|
|
|
4763
4983
|
} catch {}
|
|
4764
4984
|
}
|
|
4765
4985
|
|
|
4986
|
+
// src/lib/diff.ts
|
|
4987
|
+
function diffTexts(a, b) {
|
|
4988
|
+
const aLines = a.split(`
|
|
4989
|
+
`);
|
|
4990
|
+
const bLines = b.split(`
|
|
4991
|
+
`);
|
|
4992
|
+
const m = aLines.length;
|
|
4993
|
+
const n = bLines.length;
|
|
4994
|
+
const lcs = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
4995
|
+
for (let i2 = 1;i2 <= m; i2++) {
|
|
4996
|
+
for (let j2 = 1;j2 <= n; j2++) {
|
|
4997
|
+
lcs[i2][j2] = aLines[i2 - 1] === bLines[j2 - 1] ? (lcs[i2 - 1][j2 - 1] ?? 0) + 1 : Math.max(lcs[i2 - 1][j2] ?? 0, lcs[i2][j2 - 1] ?? 0);
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
5000
|
+
const trace = [];
|
|
5001
|
+
let i = m, j = n;
|
|
5002
|
+
while (i > 0 || j > 0) {
|
|
5003
|
+
if (i > 0 && j > 0 && aLines[i - 1] === bLines[j - 1]) {
|
|
5004
|
+
trace.unshift({ type: "unchanged", content: aLines[i - 1] ?? "" });
|
|
5005
|
+
i--;
|
|
5006
|
+
j--;
|
|
5007
|
+
} else if (j > 0 && (i === 0 || (lcs[i][j - 1] ?? 0) >= (lcs[i - 1][j] ?? 0))) {
|
|
5008
|
+
trace.unshift({ type: "added", content: bLines[j - 1] ?? "" });
|
|
5009
|
+
j--;
|
|
5010
|
+
} else {
|
|
5011
|
+
trace.unshift({ type: "removed", content: aLines[i - 1] ?? "" });
|
|
5012
|
+
i--;
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
return trace;
|
|
5016
|
+
}
|
|
5017
|
+
function formatDiff(lines) {
|
|
5018
|
+
return lines.map((l) => {
|
|
5019
|
+
if (l.type === "added")
|
|
5020
|
+
return `+ ${l.content}`;
|
|
5021
|
+
if (l.type === "removed")
|
|
5022
|
+
return `- ${l.content}`;
|
|
5023
|
+
return ` ${l.content}`;
|
|
5024
|
+
}).join(`
|
|
5025
|
+
`);
|
|
5026
|
+
}
|
|
5027
|
+
|
|
4766
5028
|
// src/lib/lint.ts
|
|
4767
5029
|
function lintPrompt(p) {
|
|
4768
5030
|
const issues = [];
|
|
@@ -4797,6 +5059,83 @@ function lintAll(prompts) {
|
|
|
4797
5059
|
return prompts.map((p) => ({ prompt: p, issues: lintPrompt(p) })).filter((r) => r.issues.length > 0);
|
|
4798
5060
|
}
|
|
4799
5061
|
|
|
5062
|
+
// src/lib/audit.ts
|
|
5063
|
+
function runAudit() {
|
|
5064
|
+
const db = getDatabase();
|
|
5065
|
+
const issues = [];
|
|
5066
|
+
const orphaned = db.query(`
|
|
5067
|
+
SELECT p.id, p.slug FROM prompts p
|
|
5068
|
+
WHERE p.project_id IS NOT NULL
|
|
5069
|
+
AND NOT EXISTS (SELECT 1 FROM projects pr WHERE pr.id = p.project_id)
|
|
5070
|
+
`).all();
|
|
5071
|
+
for (const p of orphaned) {
|
|
5072
|
+
issues.push({
|
|
5073
|
+
type: "orphaned-project",
|
|
5074
|
+
severity: "error",
|
|
5075
|
+
prompt_id: p.id,
|
|
5076
|
+
slug: p.slug,
|
|
5077
|
+
message: `Prompt "${p.slug}" references a deleted project`
|
|
5078
|
+
});
|
|
5079
|
+
}
|
|
5080
|
+
const emptyCollections = db.query(`
|
|
5081
|
+
SELECT c.name FROM collections c
|
|
5082
|
+
WHERE NOT EXISTS (SELECT 1 FROM prompts p WHERE p.collection = c.name)
|
|
5083
|
+
AND c.name != 'default'
|
|
5084
|
+
`).all();
|
|
5085
|
+
for (const c of emptyCollections) {
|
|
5086
|
+
issues.push({
|
|
5087
|
+
type: "empty-collection",
|
|
5088
|
+
severity: "info",
|
|
5089
|
+
message: `Collection "${c.name}" has no prompts`
|
|
5090
|
+
});
|
|
5091
|
+
}
|
|
5092
|
+
const missingHistory = db.query(`
|
|
5093
|
+
SELECT p.id, p.slug FROM prompts p
|
|
5094
|
+
WHERE NOT EXISTS (SELECT 1 FROM prompt_versions v WHERE v.prompt_id = p.id)
|
|
5095
|
+
`).all();
|
|
5096
|
+
for (const p of missingHistory) {
|
|
5097
|
+
issues.push({
|
|
5098
|
+
type: "missing-version-history",
|
|
5099
|
+
severity: "warn",
|
|
5100
|
+
prompt_id: p.id,
|
|
5101
|
+
slug: p.slug,
|
|
5102
|
+
message: `Prompt "${p.slug}" has no version history entries`
|
|
5103
|
+
});
|
|
5104
|
+
}
|
|
5105
|
+
const slugs = db.query("SELECT id, slug FROM prompts").all();
|
|
5106
|
+
const seen = new Map;
|
|
5107
|
+
for (const { id, slug } of slugs) {
|
|
5108
|
+
const base = slug.replace(/-\d+$/, "");
|
|
5109
|
+
if (seen.has(base) && seen.get(base) !== id) {
|
|
5110
|
+
issues.push({
|
|
5111
|
+
type: "near-duplicate-slug",
|
|
5112
|
+
severity: "info",
|
|
5113
|
+
slug,
|
|
5114
|
+
message: `"${slug}" looks like a duplicate of "${base}" \u2014 consider merging`
|
|
5115
|
+
});
|
|
5116
|
+
} else {
|
|
5117
|
+
seen.set(base, id);
|
|
5118
|
+
}
|
|
5119
|
+
}
|
|
5120
|
+
const now = new Date().toISOString();
|
|
5121
|
+
const expired = db.query(`
|
|
5122
|
+
SELECT id, slug FROM prompts WHERE expires_at IS NOT NULL AND expires_at < ?
|
|
5123
|
+
`).all(now);
|
|
5124
|
+
for (const p of expired) {
|
|
5125
|
+
issues.push({
|
|
5126
|
+
type: "expired",
|
|
5127
|
+
severity: "warn",
|
|
5128
|
+
prompt_id: p.id,
|
|
5129
|
+
slug: p.slug,
|
|
5130
|
+
message: `Prompt "${p.slug}" has expired`
|
|
5131
|
+
});
|
|
5132
|
+
}
|
|
5133
|
+
const errors2 = issues.filter((i) => i.severity === "error").length;
|
|
5134
|
+
const warnings = issues.filter((i) => i.severity === "warn").length;
|
|
5135
|
+
const info = issues.filter((i) => i.severity === "info").length;
|
|
5136
|
+
return { issues, errors: errors2, warnings, info, checked_at: new Date().toISOString() };
|
|
5137
|
+
}
|
|
5138
|
+
|
|
4800
5139
|
// src/mcp/index.ts
|
|
4801
5140
|
var server = new McpServer({ name: "open-prompts", version: "0.1.0" });
|
|
4802
5141
|
function ok(data) {
|
|
@@ -4816,11 +5155,19 @@ server.registerTool("prompts_save", {
|
|
|
4816
5155
|
tags: exports_external.array(exports_external.string()).optional().describe("Tags for filtering and search"),
|
|
4817
5156
|
source: exports_external.enum(["manual", "ai-session", "imported"]).optional().describe("Where this prompt came from"),
|
|
4818
5157
|
changed_by: exports_external.string().optional().describe("Agent name making this change"),
|
|
4819
|
-
force: exports_external.boolean().optional().describe("Save even if a similar prompt already exists")
|
|
5158
|
+
force: exports_external.boolean().optional().describe("Save even if a similar prompt already exists"),
|
|
5159
|
+
project: exports_external.string().optional().describe("Project name, slug, or ID to scope this prompt to")
|
|
4820
5160
|
}
|
|
4821
5161
|
}, async (args) => {
|
|
4822
5162
|
try {
|
|
4823
|
-
const { force, ...input } = args;
|
|
5163
|
+
const { force, project, ...input } = args;
|
|
5164
|
+
if (project) {
|
|
5165
|
+
const db = getDatabase();
|
|
5166
|
+
const pid = resolveProject(db, project);
|
|
5167
|
+
if (!pid)
|
|
5168
|
+
return err(`Project not found: ${project}`);
|
|
5169
|
+
input.project_id = pid;
|
|
5170
|
+
}
|
|
4824
5171
|
const { prompt, created, duplicate_warning } = upsertPrompt(input, force ?? false);
|
|
4825
5172
|
return ok({ ...prompt, _created: created, _duplicate_warning: duplicate_warning ?? null });
|
|
4826
5173
|
} catch (e) {
|
|
@@ -4844,9 +5191,19 @@ server.registerTool("prompts_list", {
|
|
|
4844
5191
|
is_template: exports_external.boolean().optional(),
|
|
4845
5192
|
source: exports_external.enum(["manual", "ai-session", "imported"]).optional(),
|
|
4846
5193
|
limit: exports_external.number().optional().default(50),
|
|
4847
|
-
offset: exports_external.number().optional().default(0)
|
|
4848
|
-
|
|
4849
|
-
}
|
|
5194
|
+
offset: exports_external.number().optional().default(0),
|
|
5195
|
+
project: exports_external.string().optional().describe("Project name, slug, or ID \u2014 shows project prompts first, then globals")
|
|
5196
|
+
}
|
|
5197
|
+
}, async ({ project, ...args }) => {
|
|
5198
|
+
if (project) {
|
|
5199
|
+
const db = getDatabase();
|
|
5200
|
+
const pid = resolveProject(db, project);
|
|
5201
|
+
if (!pid)
|
|
5202
|
+
return err(`Project not found: ${project}`);
|
|
5203
|
+
return ok(listPrompts({ ...args, project_id: pid }));
|
|
5204
|
+
}
|
|
5205
|
+
return ok(listPrompts(args));
|
|
5206
|
+
});
|
|
4850
5207
|
server.registerTool("prompts_delete", {
|
|
4851
5208
|
description: "Delete a prompt by ID or slug.",
|
|
4852
5209
|
inputSchema: { id: exports_external.string() }
|
|
@@ -4916,9 +5273,19 @@ server.registerTool("prompts_search", {
|
|
|
4916
5273
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
4917
5274
|
is_template: exports_external.boolean().optional(),
|
|
4918
5275
|
source: exports_external.enum(["manual", "ai-session", "imported"]).optional(),
|
|
4919
|
-
limit: exports_external.number().optional().default(20)
|
|
4920
|
-
|
|
4921
|
-
}
|
|
5276
|
+
limit: exports_external.number().optional().default(20),
|
|
5277
|
+
project: exports_external.string().optional().describe("Project name, slug, or ID to scope search")
|
|
5278
|
+
}
|
|
5279
|
+
}, async ({ q, project, ...filter }) => {
|
|
5280
|
+
if (project) {
|
|
5281
|
+
const db = getDatabase();
|
|
5282
|
+
const pid = resolveProject(db, project);
|
|
5283
|
+
if (!pid)
|
|
5284
|
+
return err(`Project not found: ${project}`);
|
|
5285
|
+
return ok(searchPrompts(q, { ...filter, project_id: pid }));
|
|
5286
|
+
}
|
|
5287
|
+
return ok(searchPrompts(q, filter));
|
|
5288
|
+
});
|
|
4922
5289
|
server.registerTool("prompts_similar", {
|
|
4923
5290
|
description: "Find prompts similar to a given prompt (by tag overlap and collection).",
|
|
4924
5291
|
inputSchema: {
|
|
@@ -5002,6 +5369,17 @@ server.registerTool("prompts_import", {
|
|
|
5002
5369
|
const results = importFromJson(prompts, changed_by);
|
|
5003
5370
|
return ok(results);
|
|
5004
5371
|
});
|
|
5372
|
+
server.registerTool("prompts_import_slash_commands", {
|
|
5373
|
+
description: "Auto-scan .claude/commands, .codex/skills, .gemini/extensions (both project and home dir) and import all .md files as prompts.",
|
|
5374
|
+
inputSchema: {
|
|
5375
|
+
dir: exports_external.string().optional().describe("Root directory to scan (default: cwd)"),
|
|
5376
|
+
changed_by: exports_external.string().optional()
|
|
5377
|
+
}
|
|
5378
|
+
}, async ({ dir, changed_by }) => {
|
|
5379
|
+
const rootDir = dir ?? process.cwd();
|
|
5380
|
+
const result = scanAndImportSlashCommands(rootDir, changed_by);
|
|
5381
|
+
return ok(result);
|
|
5382
|
+
});
|
|
5005
5383
|
server.registerTool("prompts_update", {
|
|
5006
5384
|
description: "Update an existing prompt's fields.",
|
|
5007
5385
|
inputSchema: {
|
|
@@ -5056,10 +5434,20 @@ server.registerTool("prompts_save_from_session", {
|
|
|
5056
5434
|
tags: exports_external.array(exports_external.string()).optional().describe("Relevant tags extracted from the prompt context"),
|
|
5057
5435
|
collection: exports_external.string().optional().describe("Collection to save into (default: 'sessions')"),
|
|
5058
5436
|
description: exports_external.string().optional().describe("One-line description of what this prompt does"),
|
|
5059
|
-
agent: exports_external.string().optional().describe("Agent name saving this prompt")
|
|
5437
|
+
agent: exports_external.string().optional().describe("Agent name saving this prompt"),
|
|
5438
|
+
project: exports_external.string().optional().describe("Project name, slug, or ID to scope this prompt to"),
|
|
5439
|
+
pin: exports_external.boolean().optional().describe("Pin the prompt immediately so it surfaces first in all lists")
|
|
5060
5440
|
}
|
|
5061
|
-
}, async ({ title, body, slug, tags, collection, description, agent }) => {
|
|
5441
|
+
}, async ({ title, body, slug, tags, collection, description, agent, project, pin }) => {
|
|
5062
5442
|
try {
|
|
5443
|
+
let project_id;
|
|
5444
|
+
if (project) {
|
|
5445
|
+
const db = getDatabase();
|
|
5446
|
+
const pid = resolveProject(db, project);
|
|
5447
|
+
if (!pid)
|
|
5448
|
+
return err(`Project not found: ${project}`);
|
|
5449
|
+
project_id = pid;
|
|
5450
|
+
}
|
|
5063
5451
|
const { prompt, created } = upsertPrompt({
|
|
5064
5452
|
title,
|
|
5065
5453
|
body,
|
|
@@ -5068,9 +5456,121 @@ server.registerTool("prompts_save_from_session", {
|
|
|
5068
5456
|
collection: collection ?? "sessions",
|
|
5069
5457
|
description,
|
|
5070
5458
|
source: "ai-session",
|
|
5071
|
-
changed_by: agent
|
|
5459
|
+
changed_by: agent,
|
|
5460
|
+
project_id
|
|
5072
5461
|
});
|
|
5073
|
-
|
|
5462
|
+
if (pin)
|
|
5463
|
+
pinPrompt(prompt.id, true);
|
|
5464
|
+
const final = pin ? { ...prompt, pinned: true } : prompt;
|
|
5465
|
+
return ok({ ...final, _created: created, _tip: created ? `Saved as "${prompt.slug}". Use prompts_use("${prompt.slug}") to retrieve it.` : `Updated existing prompt "${prompt.slug}".` });
|
|
5466
|
+
} catch (e) {
|
|
5467
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
5468
|
+
}
|
|
5469
|
+
});
|
|
5470
|
+
server.registerTool("prompts_audit", {
|
|
5471
|
+
description: "Run a full audit: orphaned project refs, empty collections, missing version history, near-duplicate slugs, expired prompts.",
|
|
5472
|
+
inputSchema: {}
|
|
5473
|
+
}, async () => ok(runAudit()));
|
|
5474
|
+
server.registerTool("prompts_unused", {
|
|
5475
|
+
description: "List prompts with use_count = 0 \u2014 never used. Good for library cleanup.",
|
|
5476
|
+
inputSchema: { collection: exports_external.string().optional(), limit: exports_external.number().optional().default(50) }
|
|
5477
|
+
}, async ({ collection, limit }) => {
|
|
5478
|
+
const all = listPrompts({ collection, limit: 1e4 });
|
|
5479
|
+
const unused = all.filter((p) => p.use_count === 0).slice(0, limit);
|
|
5480
|
+
return ok({ unused, count: unused.length });
|
|
5481
|
+
});
|
|
5482
|
+
server.registerTool("prompts_trending", {
|
|
5483
|
+
description: "Get most-used prompts in the last N days based on per-use log.",
|
|
5484
|
+
inputSchema: {
|
|
5485
|
+
days: exports_external.number().optional().default(7),
|
|
5486
|
+
limit: exports_external.number().optional().default(10)
|
|
5487
|
+
}
|
|
5488
|
+
}, async ({ days, limit }) => ok(getTrending(days, limit)));
|
|
5489
|
+
server.registerTool("prompts_set_expiry", {
|
|
5490
|
+
description: "Set or clear an expiry date on a prompt. Pass expires_at=null to clear.",
|
|
5491
|
+
inputSchema: {
|
|
5492
|
+
id: exports_external.string(),
|
|
5493
|
+
expires_at: exports_external.string().nullable().describe("ISO date string (e.g. 2026-12-31) or null to clear")
|
|
5494
|
+
}
|
|
5495
|
+
}, async ({ id, expires_at }) => {
|
|
5496
|
+
try {
|
|
5497
|
+
return ok(setExpiry(id, expires_at));
|
|
5498
|
+
} catch (e) {
|
|
5499
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
5500
|
+
}
|
|
5501
|
+
});
|
|
5502
|
+
server.registerTool("prompts_duplicate", {
|
|
5503
|
+
description: "Clone a prompt with a new slug. Copies body, tags, collection, description. Version resets to 1.",
|
|
5504
|
+
inputSchema: {
|
|
5505
|
+
id: exports_external.string(),
|
|
5506
|
+
slug: exports_external.string().optional().describe("New slug (auto-generated if omitted)"),
|
|
5507
|
+
title: exports_external.string().optional().describe("New title (defaults to 'Copy of <original>')")
|
|
5508
|
+
}
|
|
5509
|
+
}, async ({ id, slug, title }) => {
|
|
5510
|
+
try {
|
|
5511
|
+
const source = getPrompt(id);
|
|
5512
|
+
if (!source)
|
|
5513
|
+
return err(`Prompt not found: ${id}`);
|
|
5514
|
+
const { prompt } = upsertPrompt({
|
|
5515
|
+
title: title ?? `Copy of ${source.title}`,
|
|
5516
|
+
slug,
|
|
5517
|
+
body: source.body,
|
|
5518
|
+
description: source.description ?? undefined,
|
|
5519
|
+
collection: source.collection,
|
|
5520
|
+
tags: source.tags,
|
|
5521
|
+
source: "manual"
|
|
5522
|
+
});
|
|
5523
|
+
return ok(prompt);
|
|
5524
|
+
} catch (e) {
|
|
5525
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
5526
|
+
}
|
|
5527
|
+
});
|
|
5528
|
+
server.registerTool("prompts_diff", {
|
|
5529
|
+
description: "Show a line diff between two versions of a prompt body. v2 defaults to current version.",
|
|
5530
|
+
inputSchema: {
|
|
5531
|
+
id: exports_external.string(),
|
|
5532
|
+
v1: exports_external.number().describe("First version number"),
|
|
5533
|
+
v2: exports_external.number().optional().describe("Second version (default: current)")
|
|
5534
|
+
}
|
|
5535
|
+
}, async ({ id, v1, v2 }) => {
|
|
5536
|
+
try {
|
|
5537
|
+
const prompt = getPrompt(id);
|
|
5538
|
+
if (!prompt)
|
|
5539
|
+
return err(`Prompt not found: ${id}`);
|
|
5540
|
+
const versions = listVersions(prompt.id);
|
|
5541
|
+
const versionA = versions.find((v) => v.version === v1);
|
|
5542
|
+
if (!versionA)
|
|
5543
|
+
return err(`Version ${v1} not found`);
|
|
5544
|
+
const bodyB = v2 ? versions.find((v) => v.version === v2)?.body ?? null : prompt.body;
|
|
5545
|
+
if (bodyB === null)
|
|
5546
|
+
return err(`Version ${v2} not found`);
|
|
5547
|
+
const lines = diffTexts(versionA.body, bodyB);
|
|
5548
|
+
return ok({ lines, formatted: formatDiff(lines), v1, v2: v2 ?? prompt.version });
|
|
5549
|
+
} catch (e) {
|
|
5550
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
5551
|
+
}
|
|
5552
|
+
});
|
|
5553
|
+
server.registerTool("prompts_chain", {
|
|
5554
|
+
description: "Set or get the next prompt in a chain. After using prompt A, the agent is suggested prompt B. Pass next_prompt=null to clear.",
|
|
5555
|
+
inputSchema: {
|
|
5556
|
+
id: exports_external.string().describe("Prompt ID or slug"),
|
|
5557
|
+
next_prompt: exports_external.string().nullable().optional().describe("Slug of the next prompt in the chain, or null to clear")
|
|
5558
|
+
}
|
|
5559
|
+
}, async ({ id, next_prompt }) => {
|
|
5560
|
+
try {
|
|
5561
|
+
if (next_prompt !== undefined) {
|
|
5562
|
+
const p = setNextPrompt(id, next_prompt ?? null);
|
|
5563
|
+
return ok(p);
|
|
5564
|
+
}
|
|
5565
|
+
const chain = [];
|
|
5566
|
+
let cur = getPrompt(id);
|
|
5567
|
+
const seen = new Set;
|
|
5568
|
+
while (cur && !seen.has(cur.id)) {
|
|
5569
|
+
chain.push({ id: cur.id, slug: cur.slug, title: cur.title });
|
|
5570
|
+
seen.add(cur.id);
|
|
5571
|
+
cur = cur.next_prompt ? getPrompt(cur.next_prompt) : null;
|
|
5572
|
+
}
|
|
5573
|
+
return ok(chain);
|
|
5074
5574
|
} catch (e) {
|
|
5075
5575
|
return err(e instanceof Error ? e.message : String(e));
|
|
5076
5576
|
}
|
|
@@ -5131,5 +5631,43 @@ server.registerTool("prompts_stats", {
|
|
|
5131
5631
|
description: "Get usage statistics: most used prompts, recently used, counts by collection and source.",
|
|
5132
5632
|
inputSchema: {}
|
|
5133
5633
|
}, async () => ok(getPromptStats()));
|
|
5634
|
+
server.registerTool("prompts_project_create", {
|
|
5635
|
+
description: "Create a new project to scope prompts.",
|
|
5636
|
+
inputSchema: {
|
|
5637
|
+
name: exports_external.string().describe("Project name"),
|
|
5638
|
+
description: exports_external.string().optional().describe("Short description"),
|
|
5639
|
+
path: exports_external.string().optional().describe("Optional filesystem path this project maps to")
|
|
5640
|
+
}
|
|
5641
|
+
}, async ({ name, description, path }) => {
|
|
5642
|
+
try {
|
|
5643
|
+
return ok(createProject({ name, description, path }));
|
|
5644
|
+
} catch (e) {
|
|
5645
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
5646
|
+
}
|
|
5647
|
+
});
|
|
5648
|
+
server.registerTool("prompts_project_list", {
|
|
5649
|
+
description: "List all projects with prompt counts.",
|
|
5650
|
+
inputSchema: {}
|
|
5651
|
+
}, async () => ok(listProjects()));
|
|
5652
|
+
server.registerTool("prompts_project_get", {
|
|
5653
|
+
description: "Get a project by ID, slug, or name.",
|
|
5654
|
+
inputSchema: { id: exports_external.string().describe("Project ID, slug, or name") }
|
|
5655
|
+
}, async ({ id }) => {
|
|
5656
|
+
const project = getProject(id);
|
|
5657
|
+
if (!project)
|
|
5658
|
+
return err(`Project not found: ${id}`);
|
|
5659
|
+
return ok(project);
|
|
5660
|
+
});
|
|
5661
|
+
server.registerTool("prompts_project_delete", {
|
|
5662
|
+
description: "Delete a project. Prompts in the project become global (project_id set to null).",
|
|
5663
|
+
inputSchema: { id: exports_external.string().describe("Project ID, slug, or name") }
|
|
5664
|
+
}, async ({ id }) => {
|
|
5665
|
+
try {
|
|
5666
|
+
deleteProject(id);
|
|
5667
|
+
return ok({ deleted: true, id });
|
|
5668
|
+
} catch (e) {
|
|
5669
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
5670
|
+
}
|
|
5671
|
+
});
|
|
5134
5672
|
var transport = new StdioServerTransport;
|
|
5135
5673
|
await server.connect(transport);
|