@hasna/prompts 0.2.0 → 0.2.2
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 +205 -22
- 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.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +120 -18
- package/dist/lib/ids.d.ts.map +1 -1
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/mcp/index.js +202 -29
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +161 -19
- package/dist/types/index.d.ts +15 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -4103,6 +4103,21 @@ 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
|
+
},
|
|
4106
4121
|
{
|
|
4107
4122
|
name: "002_fts5",
|
|
4108
4123
|
sql: `
|
|
@@ -4143,6 +4158,24 @@ function runMigrations(db) {
|
|
|
4143
4158
|
db.run("INSERT INTO _migrations (name) VALUES (?)", [migration.name]);
|
|
4144
4159
|
}
|
|
4145
4160
|
}
|
|
4161
|
+
function resolveProject(db, idOrSlug) {
|
|
4162
|
+
const byId = db.query("SELECT id FROM projects WHERE id = ?").get(idOrSlug);
|
|
4163
|
+
if (byId)
|
|
4164
|
+
return byId.id;
|
|
4165
|
+
const bySlug = db.query("SELECT id FROM projects WHERE slug = ?").get(idOrSlug);
|
|
4166
|
+
if (bySlug)
|
|
4167
|
+
return bySlug.id;
|
|
4168
|
+
const byName = db.query("SELECT id FROM projects WHERE lower(name) = ?").get(idOrSlug.toLowerCase());
|
|
4169
|
+
if (byName)
|
|
4170
|
+
return byName.id;
|
|
4171
|
+
const byPrefix = db.query("SELECT id FROM projects WHERE id LIKE ? LIMIT 2").all(`${idOrSlug}%`);
|
|
4172
|
+
if (byPrefix.length === 1 && byPrefix[0])
|
|
4173
|
+
return byPrefix[0].id;
|
|
4174
|
+
const bySlugPrefix = db.query("SELECT id FROM projects WHERE slug LIKE ? LIMIT 2").all(`${idOrSlug}%`);
|
|
4175
|
+
if (bySlugPrefix.length === 1 && bySlugPrefix[0])
|
|
4176
|
+
return bySlugPrefix[0].id;
|
|
4177
|
+
return null;
|
|
4178
|
+
}
|
|
4146
4179
|
function hasFts(db) {
|
|
4147
4180
|
return db.query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='prompts_fts'").get() !== null;
|
|
4148
4181
|
}
|
|
@@ -4182,25 +4215,24 @@ function uniqueSlug(baseSlug) {
|
|
|
4182
4215
|
}
|
|
4183
4216
|
return slug;
|
|
4184
4217
|
}
|
|
4218
|
+
var CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
4219
|
+
function nanoid(len) {
|
|
4220
|
+
let id = "";
|
|
4221
|
+
for (let i = 0;i < len; i++) {
|
|
4222
|
+
id += CHARS[Math.floor(Math.random() * CHARS.length)];
|
|
4223
|
+
}
|
|
4224
|
+
return id;
|
|
4225
|
+
}
|
|
4185
4226
|
function generatePromptId() {
|
|
4186
4227
|
const db = getDatabase();
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
next = parseInt(match[1], 10) + 1;
|
|
4193
|
-
}
|
|
4194
|
-
}
|
|
4195
|
-
return `PRMT-${String(next).padStart(5, "0")}`;
|
|
4228
|
+
let id;
|
|
4229
|
+
do {
|
|
4230
|
+
id = `prmt-${nanoid(8)}`;
|
|
4231
|
+
} while (db.query("SELECT 1 FROM prompts WHERE id = ?").get(id));
|
|
4232
|
+
return id;
|
|
4196
4233
|
}
|
|
4197
4234
|
function generateId(prefix) {
|
|
4198
|
-
|
|
4199
|
-
let id = prefix + "-";
|
|
4200
|
-
for (let i = 0;i < 8; i++) {
|
|
4201
|
-
id += chars[Math.floor(Math.random() * chars.length)];
|
|
4202
|
-
}
|
|
4203
|
-
return id;
|
|
4235
|
+
return `${prefix}-${nanoid(8)}`;
|
|
4204
4236
|
}
|
|
4205
4237
|
|
|
4206
4238
|
// src/db/collections.ts
|
|
@@ -4361,6 +4393,12 @@ class DuplicateSlugError extends Error {
|
|
|
4361
4393
|
this.name = "DuplicateSlugError";
|
|
4362
4394
|
}
|
|
4363
4395
|
}
|
|
4396
|
+
class ProjectNotFoundError extends Error {
|
|
4397
|
+
constructor(id) {
|
|
4398
|
+
super(`Project not found: ${id}`);
|
|
4399
|
+
this.name = "ProjectNotFoundError";
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4364
4402
|
|
|
4365
4403
|
// src/db/prompts.ts
|
|
4366
4404
|
function rowToPrompt(row) {
|
|
@@ -4375,6 +4413,7 @@ function rowToPrompt(row) {
|
|
|
4375
4413
|
tags: JSON.parse(row["tags"] || "[]"),
|
|
4376
4414
|
variables: JSON.parse(row["variables"] || "[]"),
|
|
4377
4415
|
pinned: Boolean(row["pinned"]),
|
|
4416
|
+
project_id: row["project_id"] ?? null,
|
|
4378
4417
|
is_template: Boolean(row["is_template"]),
|
|
4379
4418
|
source: row["source"],
|
|
4380
4419
|
version: row["version"],
|
|
@@ -4398,11 +4437,12 @@ function createPrompt(input) {
|
|
|
4398
4437
|
ensureCollection(collection);
|
|
4399
4438
|
const tags = JSON.stringify(input.tags || []);
|
|
4400
4439
|
const source = input.source || "manual";
|
|
4440
|
+
const project_id = input.project_id ?? null;
|
|
4401
4441
|
const vars = extractVariables(input.body);
|
|
4402
4442
|
const variables = JSON.stringify(vars.map((v) => ({ name: v, required: true })));
|
|
4403
4443
|
const is_template = vars.length > 0 ? 1 : 0;
|
|
4404
|
-
db.run(`INSERT INTO prompts (id, name, slug, title, body, description, collection, tags, variables, is_template, source)
|
|
4405
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, name, slug, input.title, input.body, input.description ?? null, collection, tags, variables, is_template, source]);
|
|
4444
|
+
db.run(`INSERT INTO prompts (id, name, slug, title, body, description, collection, tags, variables, is_template, source, project_id)
|
|
4445
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, name, slug, input.title, input.body, input.description ?? null, collection, tags, variables, is_template, source, project_id]);
|
|
4406
4446
|
db.run(`INSERT INTO prompt_versions (id, prompt_id, body, version, changed_by)
|
|
4407
4447
|
VALUES (?, ?, ?, 1, ?)`, [generateId("VER"), id, input.body, input.changed_by ?? null]);
|
|
4408
4448
|
return getPrompt(id);
|
|
@@ -4446,10 +4486,16 @@ function listPrompts(filter = {}) {
|
|
|
4446
4486
|
params.push(`%"${tag}"%`);
|
|
4447
4487
|
}
|
|
4448
4488
|
}
|
|
4489
|
+
let orderBy = "pinned DESC, use_count DESC, updated_at DESC";
|
|
4490
|
+
if (filter.project_id !== undefined && filter.project_id !== null) {
|
|
4491
|
+
conditions.push("(project_id = ? OR project_id IS NULL)");
|
|
4492
|
+
params.push(filter.project_id);
|
|
4493
|
+
orderBy = `(CASE WHEN project_id = '${filter.project_id}' THEN 0 ELSE 1 END), pinned DESC, use_count DESC, updated_at DESC`;
|
|
4494
|
+
}
|
|
4449
4495
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
4450
4496
|
const limit = filter.limit ?? 100;
|
|
4451
4497
|
const offset = filter.offset ?? 0;
|
|
4452
|
-
const rows = db.query(`SELECT * FROM prompts ${where} ORDER BY
|
|
4498
|
+
const rows = db.query(`SELECT * FROM prompts ${where} ORDER BY ${orderBy} LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
4453
4499
|
return rows.map(rowToPrompt);
|
|
4454
4500
|
}
|
|
4455
4501
|
function updatePrompt(idOrSlug, input) {
|
|
@@ -4607,6 +4653,52 @@ function registerAgent(name, description) {
|
|
|
4607
4653
|
return rowToAgent(db.query("SELECT * FROM agents WHERE id = ?").get(id));
|
|
4608
4654
|
}
|
|
4609
4655
|
|
|
4656
|
+
// src/db/projects.ts
|
|
4657
|
+
function rowToProject(row, promptCount) {
|
|
4658
|
+
return {
|
|
4659
|
+
id: row["id"],
|
|
4660
|
+
name: row["name"],
|
|
4661
|
+
slug: row["slug"],
|
|
4662
|
+
description: row["description"] ?? null,
|
|
4663
|
+
path: row["path"] ?? null,
|
|
4664
|
+
prompt_count: promptCount,
|
|
4665
|
+
created_at: row["created_at"]
|
|
4666
|
+
};
|
|
4667
|
+
}
|
|
4668
|
+
function createProject(input) {
|
|
4669
|
+
const db = getDatabase();
|
|
4670
|
+
const id = generateId("proj");
|
|
4671
|
+
const slug = generateSlug(input.name);
|
|
4672
|
+
db.run(`INSERT INTO projects (id, name, slug, description, path) VALUES (?, ?, ?, ?, ?)`, [id, input.name, slug, input.description ?? null, input.path ?? null]);
|
|
4673
|
+
return getProject(id);
|
|
4674
|
+
}
|
|
4675
|
+
function getProject(idOrSlug) {
|
|
4676
|
+
const db = getDatabase();
|
|
4677
|
+
const id = resolveProject(db, idOrSlug);
|
|
4678
|
+
if (!id)
|
|
4679
|
+
return null;
|
|
4680
|
+
const row = db.query("SELECT * FROM projects WHERE id = ?").get(id);
|
|
4681
|
+
if (!row)
|
|
4682
|
+
return null;
|
|
4683
|
+
const countRow = db.query("SELECT COUNT(*) as n FROM prompts WHERE project_id = ?").get(id);
|
|
4684
|
+
return rowToProject(row, countRow.n);
|
|
4685
|
+
}
|
|
4686
|
+
function listProjects() {
|
|
4687
|
+
const db = getDatabase();
|
|
4688
|
+
const rows = db.query("SELECT * FROM projects ORDER BY name ASC").all();
|
|
4689
|
+
return rows.map((row) => {
|
|
4690
|
+
const countRow = db.query("SELECT COUNT(*) as n FROM prompts WHERE project_id = ?").get(row["id"]);
|
|
4691
|
+
return rowToProject(row, countRow.n);
|
|
4692
|
+
});
|
|
4693
|
+
}
|
|
4694
|
+
function deleteProject(idOrSlug) {
|
|
4695
|
+
const db = getDatabase();
|
|
4696
|
+
const id = resolveProject(db, idOrSlug);
|
|
4697
|
+
if (!id)
|
|
4698
|
+
throw new ProjectNotFoundError(idOrSlug);
|
|
4699
|
+
db.run("DELETE FROM projects WHERE id = ?", [id]);
|
|
4700
|
+
}
|
|
4701
|
+
|
|
4610
4702
|
// src/lib/search.ts
|
|
4611
4703
|
function rowToSearchResult(row, snippet) {
|
|
4612
4704
|
return {
|
|
@@ -4621,6 +4713,7 @@ function rowToSearchResult(row, snippet) {
|
|
|
4621
4713
|
tags: JSON.parse(row["tags"] || "[]"),
|
|
4622
4714
|
variables: JSON.parse(row["variables"] || "[]"),
|
|
4623
4715
|
pinned: Boolean(row["pinned"]),
|
|
4716
|
+
project_id: row["project_id"] ?? null,
|
|
4624
4717
|
is_template: Boolean(row["is_template"]),
|
|
4625
4718
|
source: row["source"],
|
|
4626
4719
|
version: row["version"],
|
|
@@ -4664,6 +4757,10 @@ function searchPrompts(query, filter = {}) {
|
|
|
4664
4757
|
for (const tag of filter.tags)
|
|
4665
4758
|
params.push(`%"${tag}"%`);
|
|
4666
4759
|
}
|
|
4760
|
+
if (filter.project_id !== undefined && filter.project_id !== null) {
|
|
4761
|
+
conditions.push("(p.project_id = ? OR p.project_id IS NULL)");
|
|
4762
|
+
params.push(filter.project_id);
|
|
4763
|
+
}
|
|
4667
4764
|
const where = conditions.length > 0 ? `AND ${conditions.join(" AND ")}` : "";
|
|
4668
4765
|
const limit = filter.limit ?? 50;
|
|
4669
4766
|
const offset = filter.offset ?? 0;
|
|
@@ -4817,11 +4914,19 @@ server.registerTool("prompts_save", {
|
|
|
4817
4914
|
tags: exports_external.array(exports_external.string()).optional().describe("Tags for filtering and search"),
|
|
4818
4915
|
source: exports_external.enum(["manual", "ai-session", "imported"]).optional().describe("Where this prompt came from"),
|
|
4819
4916
|
changed_by: exports_external.string().optional().describe("Agent name making this change"),
|
|
4820
|
-
force: exports_external.boolean().optional().describe("Save even if a similar prompt already exists")
|
|
4917
|
+
force: exports_external.boolean().optional().describe("Save even if a similar prompt already exists"),
|
|
4918
|
+
project: exports_external.string().optional().describe("Project name, slug, or ID to scope this prompt to")
|
|
4821
4919
|
}
|
|
4822
4920
|
}, async (args) => {
|
|
4823
4921
|
try {
|
|
4824
|
-
const { force, ...input } = args;
|
|
4922
|
+
const { force, project, ...input } = args;
|
|
4923
|
+
if (project) {
|
|
4924
|
+
const db = getDatabase();
|
|
4925
|
+
const pid = resolveProject(db, project);
|
|
4926
|
+
if (!pid)
|
|
4927
|
+
return err(`Project not found: ${project}`);
|
|
4928
|
+
input.project_id = pid;
|
|
4929
|
+
}
|
|
4825
4930
|
const { prompt, created, duplicate_warning } = upsertPrompt(input, force ?? false);
|
|
4826
4931
|
return ok({ ...prompt, _created: created, _duplicate_warning: duplicate_warning ?? null });
|
|
4827
4932
|
} catch (e) {
|
|
@@ -4845,9 +4950,19 @@ server.registerTool("prompts_list", {
|
|
|
4845
4950
|
is_template: exports_external.boolean().optional(),
|
|
4846
4951
|
source: exports_external.enum(["manual", "ai-session", "imported"]).optional(),
|
|
4847
4952
|
limit: exports_external.number().optional().default(50),
|
|
4848
|
-
offset: exports_external.number().optional().default(0)
|
|
4849
|
-
|
|
4850
|
-
}
|
|
4953
|
+
offset: exports_external.number().optional().default(0),
|
|
4954
|
+
project: exports_external.string().optional().describe("Project name, slug, or ID \u2014 shows project prompts first, then globals")
|
|
4955
|
+
}
|
|
4956
|
+
}, async ({ project, ...args }) => {
|
|
4957
|
+
if (project) {
|
|
4958
|
+
const db = getDatabase();
|
|
4959
|
+
const pid = resolveProject(db, project);
|
|
4960
|
+
if (!pid)
|
|
4961
|
+
return err(`Project not found: ${project}`);
|
|
4962
|
+
return ok(listPrompts({ ...args, project_id: pid }));
|
|
4963
|
+
}
|
|
4964
|
+
return ok(listPrompts(args));
|
|
4965
|
+
});
|
|
4851
4966
|
server.registerTool("prompts_delete", {
|
|
4852
4967
|
description: "Delete a prompt by ID or slug.",
|
|
4853
4968
|
inputSchema: { id: exports_external.string() }
|
|
@@ -4917,9 +5032,19 @@ server.registerTool("prompts_search", {
|
|
|
4917
5032
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
4918
5033
|
is_template: exports_external.boolean().optional(),
|
|
4919
5034
|
source: exports_external.enum(["manual", "ai-session", "imported"]).optional(),
|
|
4920
|
-
limit: exports_external.number().optional().default(20)
|
|
4921
|
-
|
|
4922
|
-
}
|
|
5035
|
+
limit: exports_external.number().optional().default(20),
|
|
5036
|
+
project: exports_external.string().optional().describe("Project name, slug, or ID to scope search")
|
|
5037
|
+
}
|
|
5038
|
+
}, async ({ q, project, ...filter }) => {
|
|
5039
|
+
if (project) {
|
|
5040
|
+
const db = getDatabase();
|
|
5041
|
+
const pid = resolveProject(db, project);
|
|
5042
|
+
if (!pid)
|
|
5043
|
+
return err(`Project not found: ${project}`);
|
|
5044
|
+
return ok(searchPrompts(q, { ...filter, project_id: pid }));
|
|
5045
|
+
}
|
|
5046
|
+
return ok(searchPrompts(q, filter));
|
|
5047
|
+
});
|
|
4923
5048
|
server.registerTool("prompts_similar", {
|
|
4924
5049
|
description: "Find prompts similar to a given prompt (by tag overlap and collection).",
|
|
4925
5050
|
inputSchema: {
|
|
@@ -5057,10 +5182,19 @@ server.registerTool("prompts_save_from_session", {
|
|
|
5057
5182
|
tags: exports_external.array(exports_external.string()).optional().describe("Relevant tags extracted from the prompt context"),
|
|
5058
5183
|
collection: exports_external.string().optional().describe("Collection to save into (default: 'sessions')"),
|
|
5059
5184
|
description: exports_external.string().optional().describe("One-line description of what this prompt does"),
|
|
5060
|
-
agent: exports_external.string().optional().describe("Agent name saving this prompt")
|
|
5185
|
+
agent: exports_external.string().optional().describe("Agent name saving this prompt"),
|
|
5186
|
+
project: exports_external.string().optional().describe("Project name, slug, or ID to scope this prompt to")
|
|
5061
5187
|
}
|
|
5062
|
-
}, async ({ title, body, slug, tags, collection, description, agent }) => {
|
|
5188
|
+
}, async ({ title, body, slug, tags, collection, description, agent, project }) => {
|
|
5063
5189
|
try {
|
|
5190
|
+
let project_id;
|
|
5191
|
+
if (project) {
|
|
5192
|
+
const db = getDatabase();
|
|
5193
|
+
const pid = resolveProject(db, project);
|
|
5194
|
+
if (!pid)
|
|
5195
|
+
return err(`Project not found: ${project}`);
|
|
5196
|
+
project_id = pid;
|
|
5197
|
+
}
|
|
5064
5198
|
const { prompt, created } = upsertPrompt({
|
|
5065
5199
|
title,
|
|
5066
5200
|
body,
|
|
@@ -5069,7 +5203,8 @@ server.registerTool("prompts_save_from_session", {
|
|
|
5069
5203
|
collection: collection ?? "sessions",
|
|
5070
5204
|
description,
|
|
5071
5205
|
source: "ai-session",
|
|
5072
|
-
changed_by: agent
|
|
5206
|
+
changed_by: agent,
|
|
5207
|
+
project_id
|
|
5073
5208
|
});
|
|
5074
5209
|
return ok({ ...prompt, _created: created, _tip: created ? `Saved as "${prompt.slug}". Use prompts_use("${prompt.slug}") to retrieve it.` : `Updated existing prompt "${prompt.slug}".` });
|
|
5075
5210
|
} catch (e) {
|
|
@@ -5132,5 +5267,43 @@ server.registerTool("prompts_stats", {
|
|
|
5132
5267
|
description: "Get usage statistics: most used prompts, recently used, counts by collection and source.",
|
|
5133
5268
|
inputSchema: {}
|
|
5134
5269
|
}, async () => ok(getPromptStats()));
|
|
5270
|
+
server.registerTool("prompts_project_create", {
|
|
5271
|
+
description: "Create a new project to scope prompts.",
|
|
5272
|
+
inputSchema: {
|
|
5273
|
+
name: exports_external.string().describe("Project name"),
|
|
5274
|
+
description: exports_external.string().optional().describe("Short description"),
|
|
5275
|
+
path: exports_external.string().optional().describe("Optional filesystem path this project maps to")
|
|
5276
|
+
}
|
|
5277
|
+
}, async ({ name, description, path }) => {
|
|
5278
|
+
try {
|
|
5279
|
+
return ok(createProject({ name, description, path }));
|
|
5280
|
+
} catch (e) {
|
|
5281
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
5282
|
+
}
|
|
5283
|
+
});
|
|
5284
|
+
server.registerTool("prompts_project_list", {
|
|
5285
|
+
description: "List all projects with prompt counts.",
|
|
5286
|
+
inputSchema: {}
|
|
5287
|
+
}, async () => ok(listProjects()));
|
|
5288
|
+
server.registerTool("prompts_project_get", {
|
|
5289
|
+
description: "Get a project by ID, slug, or name.",
|
|
5290
|
+
inputSchema: { id: exports_external.string().describe("Project ID, slug, or name") }
|
|
5291
|
+
}, async ({ id }) => {
|
|
5292
|
+
const project = getProject(id);
|
|
5293
|
+
if (!project)
|
|
5294
|
+
return err(`Project not found: ${id}`);
|
|
5295
|
+
return ok(project);
|
|
5296
|
+
});
|
|
5297
|
+
server.registerTool("prompts_project_delete", {
|
|
5298
|
+
description: "Delete a project. Prompts in the project become global (project_id set to null).",
|
|
5299
|
+
inputSchema: { id: exports_external.string().describe("Project ID, slug, or name") }
|
|
5300
|
+
}, async ({ id }) => {
|
|
5301
|
+
try {
|
|
5302
|
+
deleteProject(id);
|
|
5303
|
+
return ok({ deleted: true, id });
|
|
5304
|
+
} catch (e) {
|
|
5305
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
5306
|
+
}
|
|
5307
|
+
});
|
|
5135
5308
|
var transport = new StdioServerTransport;
|
|
5136
5309
|
await server.connect(transport);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";;;eAsCmB,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;;AAF9C,wBAkOC"}
|
package/dist/server/index.js
CHANGED
|
@@ -115,6 +115,21 @@ function runMigrations(db) {
|
|
|
115
115
|
name: "003_pinned",
|
|
116
116
|
sql: `ALTER TABLE prompts ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0;`
|
|
117
117
|
},
|
|
118
|
+
{
|
|
119
|
+
name: "004_projects",
|
|
120
|
+
sql: `
|
|
121
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
122
|
+
id TEXT PRIMARY KEY,
|
|
123
|
+
name TEXT NOT NULL UNIQUE,
|
|
124
|
+
slug TEXT NOT NULL UNIQUE,
|
|
125
|
+
description TEXT,
|
|
126
|
+
path TEXT,
|
|
127
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
128
|
+
);
|
|
129
|
+
ALTER TABLE prompts ADD COLUMN project_id TEXT REFERENCES projects(id) ON DELETE SET NULL;
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_prompts_project_id ON prompts(project_id);
|
|
131
|
+
`
|
|
132
|
+
},
|
|
118
133
|
{
|
|
119
134
|
name: "002_fts5",
|
|
120
135
|
sql: `
|
|
@@ -155,6 +170,24 @@ function runMigrations(db) {
|
|
|
155
170
|
db.run("INSERT INTO _migrations (name) VALUES (?)", [migration.name]);
|
|
156
171
|
}
|
|
157
172
|
}
|
|
173
|
+
function resolveProject(db, idOrSlug) {
|
|
174
|
+
const byId = db.query("SELECT id FROM projects WHERE id = ?").get(idOrSlug);
|
|
175
|
+
if (byId)
|
|
176
|
+
return byId.id;
|
|
177
|
+
const bySlug = db.query("SELECT id FROM projects WHERE slug = ?").get(idOrSlug);
|
|
178
|
+
if (bySlug)
|
|
179
|
+
return bySlug.id;
|
|
180
|
+
const byName = db.query("SELECT id FROM projects WHERE lower(name) = ?").get(idOrSlug.toLowerCase());
|
|
181
|
+
if (byName)
|
|
182
|
+
return byName.id;
|
|
183
|
+
const byPrefix = db.query("SELECT id FROM projects WHERE id LIKE ? LIMIT 2").all(`${idOrSlug}%`);
|
|
184
|
+
if (byPrefix.length === 1 && byPrefix[0])
|
|
185
|
+
return byPrefix[0].id;
|
|
186
|
+
const bySlugPrefix = db.query("SELECT id FROM projects WHERE slug LIKE ? LIMIT 2").all(`${idOrSlug}%`);
|
|
187
|
+
if (bySlugPrefix.length === 1 && bySlugPrefix[0])
|
|
188
|
+
return bySlugPrefix[0].id;
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
158
191
|
function hasFts(db) {
|
|
159
192
|
return db.query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='prompts_fts'").get() !== null;
|
|
160
193
|
}
|
|
@@ -194,25 +227,24 @@ function uniqueSlug(baseSlug) {
|
|
|
194
227
|
}
|
|
195
228
|
return slug;
|
|
196
229
|
}
|
|
230
|
+
var CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
231
|
+
function nanoid(len) {
|
|
232
|
+
let id = "";
|
|
233
|
+
for (let i = 0;i < len; i++) {
|
|
234
|
+
id += CHARS[Math.floor(Math.random() * CHARS.length)];
|
|
235
|
+
}
|
|
236
|
+
return id;
|
|
237
|
+
}
|
|
197
238
|
function generatePromptId() {
|
|
198
239
|
const db = getDatabase();
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
next = parseInt(match[1], 10) + 1;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return `PRMT-${String(next).padStart(5, "0")}`;
|
|
240
|
+
let id;
|
|
241
|
+
do {
|
|
242
|
+
id = `prmt-${nanoid(8)}`;
|
|
243
|
+
} while (db.query("SELECT 1 FROM prompts WHERE id = ?").get(id));
|
|
244
|
+
return id;
|
|
208
245
|
}
|
|
209
246
|
function generateId(prefix) {
|
|
210
|
-
|
|
211
|
-
let id = prefix + "-";
|
|
212
|
-
for (let i = 0;i < 8; i++) {
|
|
213
|
-
id += chars[Math.floor(Math.random() * chars.length)];
|
|
214
|
-
}
|
|
215
|
-
return id;
|
|
247
|
+
return `${prefix}-${nanoid(8)}`;
|
|
216
248
|
}
|
|
217
249
|
|
|
218
250
|
// src/db/collections.ts
|
|
@@ -364,6 +396,12 @@ class DuplicateSlugError extends Error {
|
|
|
364
396
|
this.name = "DuplicateSlugError";
|
|
365
397
|
}
|
|
366
398
|
}
|
|
399
|
+
class ProjectNotFoundError extends Error {
|
|
400
|
+
constructor(id) {
|
|
401
|
+
super(`Project not found: ${id}`);
|
|
402
|
+
this.name = "ProjectNotFoundError";
|
|
403
|
+
}
|
|
404
|
+
}
|
|
367
405
|
|
|
368
406
|
// src/db/prompts.ts
|
|
369
407
|
function rowToPrompt(row) {
|
|
@@ -378,6 +416,7 @@ function rowToPrompt(row) {
|
|
|
378
416
|
tags: JSON.parse(row["tags"] || "[]"),
|
|
379
417
|
variables: JSON.parse(row["variables"] || "[]"),
|
|
380
418
|
pinned: Boolean(row["pinned"]),
|
|
419
|
+
project_id: row["project_id"] ?? null,
|
|
381
420
|
is_template: Boolean(row["is_template"]),
|
|
382
421
|
source: row["source"],
|
|
383
422
|
version: row["version"],
|
|
@@ -401,11 +440,12 @@ function createPrompt(input) {
|
|
|
401
440
|
ensureCollection(collection);
|
|
402
441
|
const tags = JSON.stringify(input.tags || []);
|
|
403
442
|
const source = input.source || "manual";
|
|
443
|
+
const project_id = input.project_id ?? null;
|
|
404
444
|
const vars = extractVariables(input.body);
|
|
405
445
|
const variables = JSON.stringify(vars.map((v) => ({ name: v, required: true })));
|
|
406
446
|
const is_template = vars.length > 0 ? 1 : 0;
|
|
407
|
-
db.run(`INSERT INTO prompts (id, name, slug, title, body, description, collection, tags, variables, is_template, source)
|
|
408
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, name, slug, input.title, input.body, input.description ?? null, collection, tags, variables, is_template, source]);
|
|
447
|
+
db.run(`INSERT INTO prompts (id, name, slug, title, body, description, collection, tags, variables, is_template, source, project_id)
|
|
448
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, name, slug, input.title, input.body, input.description ?? null, collection, tags, variables, is_template, source, project_id]);
|
|
409
449
|
db.run(`INSERT INTO prompt_versions (id, prompt_id, body, version, changed_by)
|
|
410
450
|
VALUES (?, ?, ?, 1, ?)`, [generateId("VER"), id, input.body, input.changed_by ?? null]);
|
|
411
451
|
return getPrompt(id);
|
|
@@ -449,10 +489,16 @@ function listPrompts(filter = {}) {
|
|
|
449
489
|
params.push(`%"${tag}"%`);
|
|
450
490
|
}
|
|
451
491
|
}
|
|
492
|
+
let orderBy = "pinned DESC, use_count DESC, updated_at DESC";
|
|
493
|
+
if (filter.project_id !== undefined && filter.project_id !== null) {
|
|
494
|
+
conditions.push("(project_id = ? OR project_id IS NULL)");
|
|
495
|
+
params.push(filter.project_id);
|
|
496
|
+
orderBy = `(CASE WHEN project_id = '${filter.project_id}' THEN 0 ELSE 1 END), pinned DESC, use_count DESC, updated_at DESC`;
|
|
497
|
+
}
|
|
452
498
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
453
499
|
const limit = filter.limit ?? 100;
|
|
454
500
|
const offset = filter.offset ?? 0;
|
|
455
|
-
const rows = db.query(`SELECT * FROM prompts ${where} ORDER BY
|
|
501
|
+
const rows = db.query(`SELECT * FROM prompts ${where} ORDER BY ${orderBy} LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
456
502
|
return rows.map(rowToPrompt);
|
|
457
503
|
}
|
|
458
504
|
function updatePrompt(idOrSlug, input) {
|
|
@@ -579,6 +625,52 @@ function restoreVersion(promptId, version, changedBy) {
|
|
|
579
625
|
VALUES (?, ?, ?, ?, ?)`, [generateId("VER"), promptId, ver.body, newVersion, changedBy ?? null]);
|
|
580
626
|
}
|
|
581
627
|
|
|
628
|
+
// src/db/projects.ts
|
|
629
|
+
function rowToProject(row, promptCount) {
|
|
630
|
+
return {
|
|
631
|
+
id: row["id"],
|
|
632
|
+
name: row["name"],
|
|
633
|
+
slug: row["slug"],
|
|
634
|
+
description: row["description"] ?? null,
|
|
635
|
+
path: row["path"] ?? null,
|
|
636
|
+
prompt_count: promptCount,
|
|
637
|
+
created_at: row["created_at"]
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
function createProject(input) {
|
|
641
|
+
const db = getDatabase();
|
|
642
|
+
const id = generateId("proj");
|
|
643
|
+
const slug = generateSlug(input.name);
|
|
644
|
+
db.run(`INSERT INTO projects (id, name, slug, description, path) VALUES (?, ?, ?, ?, ?)`, [id, input.name, slug, input.description ?? null, input.path ?? null]);
|
|
645
|
+
return getProject(id);
|
|
646
|
+
}
|
|
647
|
+
function getProject(idOrSlug) {
|
|
648
|
+
const db = getDatabase();
|
|
649
|
+
const id = resolveProject(db, idOrSlug);
|
|
650
|
+
if (!id)
|
|
651
|
+
return null;
|
|
652
|
+
const row = db.query("SELECT * FROM projects WHERE id = ?").get(id);
|
|
653
|
+
if (!row)
|
|
654
|
+
return null;
|
|
655
|
+
const countRow = db.query("SELECT COUNT(*) as n FROM prompts WHERE project_id = ?").get(id);
|
|
656
|
+
return rowToProject(row, countRow.n);
|
|
657
|
+
}
|
|
658
|
+
function listProjects() {
|
|
659
|
+
const db = getDatabase();
|
|
660
|
+
const rows = db.query("SELECT * FROM projects ORDER BY name ASC").all();
|
|
661
|
+
return rows.map((row) => {
|
|
662
|
+
const countRow = db.query("SELECT COUNT(*) as n FROM prompts WHERE project_id = ?").get(row["id"]);
|
|
663
|
+
return rowToProject(row, countRow.n);
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
function deleteProject(idOrSlug) {
|
|
667
|
+
const db = getDatabase();
|
|
668
|
+
const id = resolveProject(db, idOrSlug);
|
|
669
|
+
if (!id)
|
|
670
|
+
throw new ProjectNotFoundError(idOrSlug);
|
|
671
|
+
db.run("DELETE FROM projects WHERE id = ?", [id]);
|
|
672
|
+
}
|
|
673
|
+
|
|
582
674
|
// src/lib/search.ts
|
|
583
675
|
function rowToSearchResult(row, snippet) {
|
|
584
676
|
return {
|
|
@@ -593,6 +685,7 @@ function rowToSearchResult(row, snippet) {
|
|
|
593
685
|
tags: JSON.parse(row["tags"] || "[]"),
|
|
594
686
|
variables: JSON.parse(row["variables"] || "[]"),
|
|
595
687
|
pinned: Boolean(row["pinned"]),
|
|
688
|
+
project_id: row["project_id"] ?? null,
|
|
596
689
|
is_template: Boolean(row["is_template"]),
|
|
597
690
|
source: row["source"],
|
|
598
691
|
version: row["version"],
|
|
@@ -636,6 +729,10 @@ function searchPrompts(query, filter = {}) {
|
|
|
636
729
|
for (const tag of filter.tags)
|
|
637
730
|
params.push(`%"${tag}"%`);
|
|
638
731
|
}
|
|
732
|
+
if (filter.project_id !== undefined && filter.project_id !== null) {
|
|
733
|
+
conditions.push("(p.project_id = ? OR p.project_id IS NULL)");
|
|
734
|
+
params.push(filter.project_id);
|
|
735
|
+
}
|
|
639
736
|
const where = conditions.length > 0 ? `AND ${conditions.join(" AND ")}` : "";
|
|
640
737
|
const limit = filter.limit ?? 50;
|
|
641
738
|
const offset = filter.offset ?? 0;
|
|
@@ -756,7 +853,15 @@ var server_default = {
|
|
|
756
853
|
const source = url.searchParams.get("source") ?? undefined;
|
|
757
854
|
const limit = parseInt(url.searchParams.get("limit") ?? "100");
|
|
758
855
|
const offset = parseInt(url.searchParams.get("offset") ?? "0");
|
|
759
|
-
|
|
856
|
+
const projectParam = url.searchParams.get("project") ?? undefined;
|
|
857
|
+
let project_id;
|
|
858
|
+
if (projectParam) {
|
|
859
|
+
const pid = resolveProject(getDatabase(), projectParam);
|
|
860
|
+
if (!pid)
|
|
861
|
+
return notFound(`Project not found: ${projectParam}`);
|
|
862
|
+
project_id = pid;
|
|
863
|
+
}
|
|
864
|
+
return json(listPrompts({ collection, tags, is_template, source, limit, offset, project_id }));
|
|
760
865
|
}
|
|
761
866
|
if (path === "/api/prompts" && method === "POST") {
|
|
762
867
|
const body = await parseBody(req);
|
|
@@ -865,6 +970,43 @@ var server_default = {
|
|
|
865
970
|
const collection = url.searchParams.get("collection") ?? undefined;
|
|
866
971
|
return json(exportToJson(collection));
|
|
867
972
|
}
|
|
973
|
+
if (path === "/api/projects" && method === "GET") {
|
|
974
|
+
return json(listProjects());
|
|
975
|
+
}
|
|
976
|
+
if (path === "/api/projects" && method === "POST") {
|
|
977
|
+
const { name, description, path: projPath } = await parseBody(req);
|
|
978
|
+
if (!name)
|
|
979
|
+
return badRequest("name is required");
|
|
980
|
+
return json(createProject({ name, description, path: projPath }), 201);
|
|
981
|
+
}
|
|
982
|
+
const projectMatch = path.match(/^\/api\/projects\/([^/]+)$/);
|
|
983
|
+
if (projectMatch) {
|
|
984
|
+
const projId = projectMatch[1];
|
|
985
|
+
if (method === "GET") {
|
|
986
|
+
const project = getProject(projId);
|
|
987
|
+
if (!project)
|
|
988
|
+
return notFound(`Project not found: ${projId}`);
|
|
989
|
+
return json(project);
|
|
990
|
+
}
|
|
991
|
+
if (method === "DELETE") {
|
|
992
|
+
try {
|
|
993
|
+
deleteProject(projId);
|
|
994
|
+
return json({ deleted: true, id: projId });
|
|
995
|
+
} catch (e) {
|
|
996
|
+
return notFound(e instanceof Error ? e.message : String(e));
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
const projectPromptsMatch = path.match(/^\/api\/projects\/([^/]+)\/prompts$/);
|
|
1001
|
+
if (projectPromptsMatch && method === "GET") {
|
|
1002
|
+
const projId = projectPromptsMatch[1];
|
|
1003
|
+
const project = getProject(projId);
|
|
1004
|
+
if (!project)
|
|
1005
|
+
return notFound(`Project not found: ${projId}`);
|
|
1006
|
+
const limit = parseInt(url.searchParams.get("limit") ?? "100");
|
|
1007
|
+
const offset = parseInt(url.searchParams.get("offset") ?? "0");
|
|
1008
|
+
return json(listPrompts({ project_id: project.id, limit, offset }));
|
|
1009
|
+
}
|
|
868
1010
|
if (path === "/health") {
|
|
869
1011
|
return json({ status: "ok", port: PORT });
|
|
870
1012
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -11,12 +11,22 @@ export interface Prompt {
|
|
|
11
11
|
is_template: boolean;
|
|
12
12
|
source: PromptSource;
|
|
13
13
|
pinned: boolean;
|
|
14
|
+
project_id: string | null;
|
|
14
15
|
version: number;
|
|
15
16
|
use_count: number;
|
|
16
17
|
last_used_at: string | null;
|
|
17
18
|
created_at: string;
|
|
18
19
|
updated_at: string;
|
|
19
20
|
}
|
|
21
|
+
export interface Project {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
slug: string;
|
|
25
|
+
description: string | null;
|
|
26
|
+
path: string | null;
|
|
27
|
+
prompt_count: number;
|
|
28
|
+
created_at: string;
|
|
29
|
+
}
|
|
20
30
|
export interface TemplateVariable {
|
|
21
31
|
name: string;
|
|
22
32
|
description?: string;
|
|
@@ -56,6 +66,7 @@ export interface CreatePromptInput {
|
|
|
56
66
|
tags?: string[];
|
|
57
67
|
source?: PromptSource;
|
|
58
68
|
changed_by?: string;
|
|
69
|
+
project_id?: string | null;
|
|
59
70
|
}
|
|
60
71
|
export interface UpdatePromptInput {
|
|
61
72
|
title?: string;
|
|
@@ -73,6 +84,7 @@ export interface ListPromptsFilter {
|
|
|
73
84
|
q?: string;
|
|
74
85
|
limit?: number;
|
|
75
86
|
offset?: number;
|
|
87
|
+
project_id?: string | null;
|
|
76
88
|
}
|
|
77
89
|
export interface SearchResult {
|
|
78
90
|
prompt: Prompt;
|
|
@@ -123,4 +135,7 @@ export declare class DuplicateSlugError extends Error {
|
|
|
123
135
|
export declare class TemplateRenderError extends Error {
|
|
124
136
|
constructor(message: string);
|
|
125
137
|
}
|
|
138
|
+
export declare class ProjectNotFoundError extends Error {
|
|
139
|
+
constructor(id: string);
|
|
140
|
+
}
|
|
126
141
|
//# sourceMappingURL=index.d.ts.map
|