@hasna/prompts 0.1.0 → 0.2.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/README.md +31 -31
- package/dist/cli/index.js +258 -20
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/prompts.d.ts +3 -1
- package/dist/db/prompts.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +64 -3
- package/dist/lib/duplicates.d.ts +7 -0
- package/dist/lib/duplicates.d.ts.map +1 -0
- package/dist/lib/lint.d.ts +16 -0
- package/dist/lib/lint.d.ts.map +1 -0
- package/dist/lib/mementos.d.ts +13 -0
- package/dist/lib/mementos.d.ts.map +1 -0
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/mcp/index.js +220 -14
- package/dist/server/index.js +60 -7
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -12,19 +12,19 @@ bun install -g @hasna/prompts
|
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
# Save a prompt
|
|
15
|
-
|
|
15
|
+
prompts save "TypeScript Code Review" \
|
|
16
16
|
--body "Review this TypeScript code for correctness, types, and style:\n\n{{code}}" \
|
|
17
17
|
--tags "code,review,typescript" \
|
|
18
18
|
--collection "code"
|
|
19
19
|
|
|
20
20
|
# Use it (prints body, increments counter)
|
|
21
|
-
|
|
21
|
+
prompts use typescript-code-review
|
|
22
22
|
|
|
23
23
|
# Render a template
|
|
24
|
-
|
|
24
|
+
prompts render typescript-code-review --var code="$(cat myfile.ts)"
|
|
25
25
|
|
|
26
26
|
# Search
|
|
27
|
-
|
|
27
|
+
prompts search "code review"
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
---
|
|
@@ -37,7 +37,7 @@ Add to your Claude/agent config:
|
|
|
37
37
|
{
|
|
38
38
|
"mcpServers": {
|
|
39
39
|
"prompts": {
|
|
40
|
-
"command": "
|
|
40
|
+
"command": "prompts-mcp"
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -86,28 +86,28 @@ Later, in any session:
|
|
|
86
86
|
## CLI Reference
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
89
|
+
prompts save <title> # Save a prompt (--body, --file, or stdin)
|
|
90
|
+
prompts use <id|slug> # Get body, increment counter
|
|
91
|
+
prompts get <id|slug> # Get details without incrementing
|
|
92
|
+
prompts list # List all prompts
|
|
93
|
+
prompts search <query> # Full-text search
|
|
94
|
+
prompts render <id> -v k=v # Render template with variables
|
|
95
|
+
prompts templates # List templates
|
|
96
|
+
prompts inspect <id> # Show template variables
|
|
97
|
+
prompts update <id> # Update fields
|
|
98
|
+
prompts delete <id> # Delete
|
|
99
|
+
prompts history <id> # Version history
|
|
100
|
+
prompts restore <id> <v> # Restore version
|
|
101
|
+
prompts collections # List collections
|
|
102
|
+
prompts move <id> <col> # Move to collection
|
|
103
|
+
prompts export # Export as JSON
|
|
104
|
+
prompts import <file> # Import from JSON
|
|
105
|
+
prompts stats # Usage statistics
|
|
106
106
|
|
|
107
107
|
# Global flags
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
prompts list --json # Machine-readable output
|
|
109
|
+
prompts list -c code # Filter by collection
|
|
110
|
+
prompts list -t review,ts # Filter by tags
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
---
|
|
@@ -118,18 +118,18 @@ Prompts with `{{variable}}` syntax are automatically detected as templates.
|
|
|
118
118
|
|
|
119
119
|
```bash
|
|
120
120
|
# Save a template
|
|
121
|
-
|
|
121
|
+
prompts save "PR Description" \
|
|
122
122
|
--body "Write a PR description for this {{language|TypeScript}} change:\n\n{{diff}}\n\nFocus on: {{focus|what changed and why}}"
|
|
123
123
|
|
|
124
124
|
# Inspect variables
|
|
125
|
-
|
|
125
|
+
prompts inspect pr-description
|
|
126
126
|
# Variables for pr-description:
|
|
127
127
|
# language optional (default: "TypeScript")
|
|
128
128
|
# diff required
|
|
129
129
|
# focus optional (default: "what changed and why")
|
|
130
130
|
|
|
131
131
|
# Render
|
|
132
|
-
|
|
132
|
+
prompts render pr-description \
|
|
133
133
|
--var diff="$(git diff main)" \
|
|
134
134
|
--var language=Go
|
|
135
135
|
```
|
|
@@ -183,7 +183,7 @@ importFromClaudeCommands([
|
|
|
183
183
|
## REST API
|
|
184
184
|
|
|
185
185
|
```bash
|
|
186
|
-
|
|
186
|
+
prompts-serve # starts on port 19430
|
|
187
187
|
```
|
|
188
188
|
|
|
189
189
|
| Method | Endpoint | Description |
|
|
@@ -212,7 +212,7 @@ open-prompts-serve # starts on port 19430
|
|
|
212
212
|
## Web Dashboard
|
|
213
213
|
|
|
214
214
|
```bash
|
|
215
|
-
|
|
215
|
+
prompts-serve # start API on :19430
|
|
216
216
|
# open dashboard/dist/index.html or run dashboard dev server
|
|
217
217
|
```
|
|
218
218
|
|
|
@@ -256,7 +256,7 @@ Priority order:
|
|
|
256
256
|
# Export existing slash commands as prompts
|
|
257
257
|
for f in .claude/commands/*.md; do
|
|
258
258
|
name=$(basename "$f" .md)
|
|
259
|
-
|
|
259
|
+
prompts save "$name" --file "$f" --collection claude-commands --tags "slash-command"
|
|
260
260
|
done
|
|
261
261
|
```
|
|
262
262
|
|
package/dist/cli/index.js
CHANGED
|
@@ -2183,6 +2183,10 @@ function runMigrations(db) {
|
|
|
2183
2183
|
CREATE INDEX IF NOT EXISTS idx_versions_prompt_id ON prompt_versions(prompt_id);
|
|
2184
2184
|
`
|
|
2185
2185
|
},
|
|
2186
|
+
{
|
|
2187
|
+
name: "003_pinned",
|
|
2188
|
+
sql: `ALTER TABLE prompts ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0;`
|
|
2189
|
+
},
|
|
2186
2190
|
{
|
|
2187
2191
|
name: "002_fts5",
|
|
2188
2192
|
sql: `
|
|
@@ -2236,6 +2240,15 @@ function resolvePrompt(db, idOrSlug) {
|
|
|
2236
2240
|
const byPrefix = db.query("SELECT id FROM prompts WHERE id LIKE ? LIMIT 2").all(`${idOrSlug}%`);
|
|
2237
2241
|
if (byPrefix.length === 1 && byPrefix[0])
|
|
2238
2242
|
return byPrefix[0].id;
|
|
2243
|
+
const bySlugPrefix = db.query("SELECT id FROM prompts WHERE slug LIKE ? LIMIT 2").all(`${idOrSlug}%`);
|
|
2244
|
+
if (bySlugPrefix.length === 1 && bySlugPrefix[0])
|
|
2245
|
+
return bySlugPrefix[0].id;
|
|
2246
|
+
const bySlugSub = db.query("SELECT id FROM prompts WHERE slug LIKE ? LIMIT 2").all(`%${idOrSlug}%`);
|
|
2247
|
+
if (bySlugSub.length === 1 && bySlugSub[0])
|
|
2248
|
+
return bySlugSub[0].id;
|
|
2249
|
+
const byTitle = db.query("SELECT id FROM prompts WHERE lower(title) LIKE ? LIMIT 2").all(`%${idOrSlug.toLowerCase()}%`);
|
|
2250
|
+
if (byTitle.length === 1 && byTitle[0])
|
|
2251
|
+
return byTitle[0].id;
|
|
2239
2252
|
return null;
|
|
2240
2253
|
}
|
|
2241
2254
|
|
|
@@ -2329,6 +2342,36 @@ function movePrompt(promptIdOrSlug, targetCollection) {
|
|
|
2329
2342
|
]);
|
|
2330
2343
|
}
|
|
2331
2344
|
|
|
2345
|
+
// src/lib/duplicates.ts
|
|
2346
|
+
function tokenize(text) {
|
|
2347
|
+
return new Set(text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2));
|
|
2348
|
+
}
|
|
2349
|
+
function similarity(a, b) {
|
|
2350
|
+
const ta = tokenize(a);
|
|
2351
|
+
const tb = tokenize(b);
|
|
2352
|
+
if (ta.size === 0 || tb.size === 0)
|
|
2353
|
+
return 0;
|
|
2354
|
+
let shared = 0;
|
|
2355
|
+
for (const word of ta) {
|
|
2356
|
+
if (tb.has(word))
|
|
2357
|
+
shared++;
|
|
2358
|
+
}
|
|
2359
|
+
return shared / Math.max(ta.size, tb.size);
|
|
2360
|
+
}
|
|
2361
|
+
function findDuplicates(body, threshold = 0.8, excludeSlug) {
|
|
2362
|
+
const all = listPrompts({ limit: 1e4 });
|
|
2363
|
+
const matches = [];
|
|
2364
|
+
for (const p of all) {
|
|
2365
|
+
if (excludeSlug && p.slug === excludeSlug)
|
|
2366
|
+
continue;
|
|
2367
|
+
const score = similarity(body, p.body);
|
|
2368
|
+
if (score >= threshold) {
|
|
2369
|
+
matches.push({ prompt: p, score });
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
return matches.sort((a, b) => b.score - a.score);
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2332
2375
|
// src/lib/template.ts
|
|
2333
2376
|
var VAR_PATTERN = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:\|\s*(.*?)\s*)?\}\}/g;
|
|
2334
2377
|
function extractVariables(body) {
|
|
@@ -2406,6 +2449,7 @@ function rowToPrompt(row) {
|
|
|
2406
2449
|
collection: row["collection"],
|
|
2407
2450
|
tags: JSON.parse(row["tags"] || "[]"),
|
|
2408
2451
|
variables: JSON.parse(row["variables"] || "[]"),
|
|
2452
|
+
pinned: Boolean(row["pinned"]),
|
|
2409
2453
|
is_template: Boolean(row["is_template"]),
|
|
2410
2454
|
source: row["source"],
|
|
2411
2455
|
version: row["version"],
|
|
@@ -2480,7 +2524,7 @@ function listPrompts(filter = {}) {
|
|
|
2480
2524
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2481
2525
|
const limit = filter.limit ?? 100;
|
|
2482
2526
|
const offset = filter.offset ?? 0;
|
|
2483
|
-
const rows = db.query(`SELECT * FROM prompts ${where} ORDER BY use_count DESC, updated_at DESC LIMIT ? OFFSET ?`).all(
|
|
2527
|
+
const rows = db.query(`SELECT * FROM prompts ${where} ORDER BY pinned DESC, use_count DESC, updated_at DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
2484
2528
|
return rows.map(rowToPrompt);
|
|
2485
2529
|
}
|
|
2486
2530
|
function updatePrompt(idOrSlug, input) {
|
|
@@ -2530,7 +2574,13 @@ function usePrompt(idOrSlug) {
|
|
|
2530
2574
|
db.run("UPDATE prompts SET use_count = use_count + 1, last_used_at = datetime('now') WHERE id = ?", [prompt.id]);
|
|
2531
2575
|
return requirePrompt(prompt.id);
|
|
2532
2576
|
}
|
|
2533
|
-
function
|
|
2577
|
+
function pinPrompt(idOrSlug, pinned) {
|
|
2578
|
+
const db = getDatabase();
|
|
2579
|
+
const prompt = requirePrompt(idOrSlug);
|
|
2580
|
+
db.run("UPDATE prompts SET pinned = ?, updated_at = datetime('now') WHERE id = ?", [pinned ? 1 : 0, prompt.id]);
|
|
2581
|
+
return requirePrompt(prompt.id);
|
|
2582
|
+
}
|
|
2583
|
+
function upsertPrompt(input, force = false) {
|
|
2534
2584
|
const db = getDatabase();
|
|
2535
2585
|
const slug = input.slug || generateSlug(input.title);
|
|
2536
2586
|
const existing = db.query("SELECT id FROM prompts WHERE slug = ?").get(slug);
|
|
@@ -2545,8 +2595,16 @@ function upsertPrompt(input) {
|
|
|
2545
2595
|
});
|
|
2546
2596
|
return { prompt: prompt2, created: false };
|
|
2547
2597
|
}
|
|
2598
|
+
let duplicate_warning;
|
|
2599
|
+
if (!force && input.body) {
|
|
2600
|
+
const dupes = findDuplicates(input.body, 0.8, slug);
|
|
2601
|
+
if (dupes.length > 0) {
|
|
2602
|
+
const top = dupes[0];
|
|
2603
|
+
duplicate_warning = `Similar prompt already exists: "${top.prompt.slug}" (${Math.round(top.score * 100)}% match). Use --force to save anyway.`;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2548
2606
|
const prompt = createPrompt({ ...input, slug });
|
|
2549
|
-
return { prompt, created: true };
|
|
2607
|
+
return { prompt, created: true, duplicate_warning };
|
|
2550
2608
|
}
|
|
2551
2609
|
function getPromptStats() {
|
|
2552
2610
|
const db = getDatabase();
|
|
@@ -2612,6 +2670,7 @@ function rowToSearchResult(row, snippet) {
|
|
|
2612
2670
|
collection: row["collection"],
|
|
2613
2671
|
tags: JSON.parse(row["tags"] || "[]"),
|
|
2614
2672
|
variables: JSON.parse(row["variables"] || "[]"),
|
|
2673
|
+
pinned: Boolean(row["pinned"]),
|
|
2615
2674
|
is_template: Boolean(row["is_template"]),
|
|
2616
2675
|
source: row["source"],
|
|
2617
2676
|
version: row["version"],
|
|
@@ -2666,7 +2725,7 @@ function searchPrompts(query, filter = {}) {
|
|
|
2666
2725
|
WHERE prompts_fts MATCH ?
|
|
2667
2726
|
${where}
|
|
2668
2727
|
ORDER BY bm25(prompts_fts)
|
|
2669
|
-
LIMIT ? OFFSET ?`).all(
|
|
2728
|
+
LIMIT ? OFFSET ?`).all(ftsQuery, ...params, limit, offset);
|
|
2670
2729
|
return rows2.map((r) => rowToSearchResult(r, r["snippet"]));
|
|
2671
2730
|
} catch {}
|
|
2672
2731
|
}
|
|
@@ -2674,7 +2733,7 @@ function searchPrompts(query, filter = {}) {
|
|
|
2674
2733
|
const rows = db.query(`SELECT *, 1 as score FROM prompts
|
|
2675
2734
|
WHERE (name LIKE ? OR slug LIKE ? OR title LIKE ? OR body LIKE ? OR description LIKE ? OR tags LIKE ?)
|
|
2676
2735
|
ORDER BY use_count DESC, updated_at DESC
|
|
2677
|
-
LIMIT ? OFFSET ?`).all(
|
|
2736
|
+
LIMIT ? OFFSET ?`).all(like, like, like, like, like, like, filter.limit ?? 50, filter.offset ?? 0);
|
|
2678
2737
|
return rows.map((r) => rowToSearchResult(r));
|
|
2679
2738
|
}
|
|
2680
2739
|
|
|
@@ -2711,10 +2770,44 @@ function exportToJson(collection) {
|
|
|
2711
2770
|
return { prompts, exported_at: new Date().toISOString(), collection };
|
|
2712
2771
|
}
|
|
2713
2772
|
|
|
2773
|
+
// src/lib/lint.ts
|
|
2774
|
+
function lintPrompt(p) {
|
|
2775
|
+
const issues = [];
|
|
2776
|
+
const issue = (severity, rule, message) => ({
|
|
2777
|
+
prompt_id: p.id,
|
|
2778
|
+
slug: p.slug,
|
|
2779
|
+
severity,
|
|
2780
|
+
rule,
|
|
2781
|
+
message
|
|
2782
|
+
});
|
|
2783
|
+
if (!p.description) {
|
|
2784
|
+
issues.push(issue("warn", "missing-description", "No description provided"));
|
|
2785
|
+
}
|
|
2786
|
+
if (p.body.trim().length < 10) {
|
|
2787
|
+
issues.push(issue("error", "body-too-short", `Body is only ${p.body.trim().length} characters`));
|
|
2788
|
+
}
|
|
2789
|
+
if (p.tags.length === 0) {
|
|
2790
|
+
issues.push(issue("info", "no-tags", "No tags \u2014 prompt will be harder to discover"));
|
|
2791
|
+
}
|
|
2792
|
+
if (p.is_template) {
|
|
2793
|
+
const undocumented = p.variables.filter((v) => !v.description || v.description.trim() === "");
|
|
2794
|
+
if (undocumented.length > 0) {
|
|
2795
|
+
issues.push(issue("warn", "undocumented-vars", `Template variables without description: ${undocumented.map((v) => v.name).join(", ")}`));
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
if (p.collection === "default" && p.use_count === 0) {
|
|
2799
|
+
issues.push(issue("info", "uncollected", "In default collection and never used \u2014 consider organizing"));
|
|
2800
|
+
}
|
|
2801
|
+
return issues;
|
|
2802
|
+
}
|
|
2803
|
+
function lintAll(prompts) {
|
|
2804
|
+
return prompts.map((p) => ({ prompt: p, issues: lintPrompt(p) })).filter((r) => r.issues.length > 0);
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2714
2807
|
// src/cli/index.tsx
|
|
2715
2808
|
var require2 = createRequire(import.meta.url);
|
|
2716
2809
|
var pkg = require2("../../package.json");
|
|
2717
|
-
var program2 = new Command().name("
|
|
2810
|
+
var program2 = new Command().name("prompts").version(pkg.version).description("Reusable prompt library \u2014 save, search, render prompts from any AI session").option("--json", "Output as JSON");
|
|
2718
2811
|
function isJson() {
|
|
2719
2812
|
return Boolean(program2.opts()["json"]);
|
|
2720
2813
|
}
|
|
@@ -2737,9 +2830,10 @@ function handleError(e) {
|
|
|
2737
2830
|
function fmtPrompt(p) {
|
|
2738
2831
|
const tags = p.tags.length > 0 ? chalk.gray(` [${p.tags.join(", ")}]`) : "";
|
|
2739
2832
|
const template = p.is_template ? chalk.cyan(" \u25C7") : "";
|
|
2740
|
-
|
|
2833
|
+
const pin = p.pinned ? chalk.yellow(" \uD83D\uDCCC") : "";
|
|
2834
|
+
return `${chalk.bold(p.id)} ${chalk.green(p.slug)}${template}${pin} ${p.title}${tags} ${chalk.gray(p.collection)}`;
|
|
2741
2835
|
}
|
|
2742
|
-
program2.command("save <title>").description("Save a new prompt (or update existing by slug)").option("-b, --body <body>", "Prompt body (use - to read from stdin)").option("-f, --file <path>", "Read body from file").option("-s, --slug <slug>", "Custom slug").option("-d, --description <desc>", "Short description").option("-c, --collection <name>", "Collection", "default").option("-t, --tags <tags>", "Comma-separated tags").option("--source <source>", "Source: manual|ai-session|imported", "manual").option("--agent <name>", "Agent name (for attribution)").action(async (title, opts) => {
|
|
2836
|
+
program2.command("save <title>").description("Save a new prompt (or update existing by slug)").option("-b, --body <body>", "Prompt body (use - to read from stdin)").option("-f, --file <path>", "Read body from file").option("-s, --slug <slug>", "Custom slug").option("-d, --description <desc>", "Short description").option("-c, --collection <name>", "Collection", "default").option("-t, --tags <tags>", "Comma-separated tags").option("--source <source>", "Source: manual|ai-session|imported", "manual").option("--agent <name>", "Agent name (for attribution)").option("--force", "Save even if a similar prompt already exists").action(async (title, opts) => {
|
|
2743
2837
|
try {
|
|
2744
2838
|
let body = opts["body"] ?? "";
|
|
2745
2839
|
if (opts["file"]) {
|
|
@@ -2753,7 +2847,7 @@ program2.command("save <title>").description("Save a new prompt (or update exist
|
|
|
2753
2847
|
}
|
|
2754
2848
|
if (!body)
|
|
2755
2849
|
handleError("No body provided. Use --body, --file, or pipe via stdin.");
|
|
2756
|
-
const { prompt, created } = upsertPrompt({
|
|
2850
|
+
const { prompt, created, duplicate_warning } = upsertPrompt({
|
|
2757
2851
|
title,
|
|
2758
2852
|
body,
|
|
2759
2853
|
slug: opts["slug"],
|
|
@@ -2762,7 +2856,10 @@ program2.command("save <title>").description("Save a new prompt (or update exist
|
|
|
2762
2856
|
tags: opts["tags"] ? opts["tags"].split(",").map((t) => t.trim()) : [],
|
|
2763
2857
|
source: opts["source"] || "manual",
|
|
2764
2858
|
changed_by: opts["agent"]
|
|
2765
|
-
});
|
|
2859
|
+
}, Boolean(opts["force"]));
|
|
2860
|
+
if (duplicate_warning && !isJson()) {
|
|
2861
|
+
console.warn(chalk.yellow(`Warning: ${duplicate_warning}`));
|
|
2862
|
+
}
|
|
2766
2863
|
if (isJson()) {
|
|
2767
2864
|
output(prompt);
|
|
2768
2865
|
} else {
|
|
@@ -2801,14 +2898,17 @@ program2.command("get <id>").description("Get prompt details without incrementin
|
|
|
2801
2898
|
handleError(e);
|
|
2802
2899
|
}
|
|
2803
2900
|
});
|
|
2804
|
-
program2.command("list").description("List prompts").option("-c, --collection <name>", "Filter by collection").option("-t, --tags <tags>", "Filter by tags (comma-separated)").option("--templates", "Show only templates").option("-n, --limit <n>", "Max results", "50").action((opts) => {
|
|
2901
|
+
program2.command("list").description("List prompts").option("-c, --collection <name>", "Filter by collection").option("-t, --tags <tags>", "Filter by tags (comma-separated)").option("--templates", "Show only templates").option("--recent", "Sort by recently used").option("-n, --limit <n>", "Max results", "50").action((opts) => {
|
|
2805
2902
|
try {
|
|
2806
|
-
|
|
2903
|
+
let prompts = listPrompts({
|
|
2807
2904
|
collection: opts["collection"],
|
|
2808
2905
|
tags: opts["tags"] ? opts["tags"].split(",").map((t) => t.trim()) : undefined,
|
|
2809
2906
|
is_template: opts["templates"] ? true : undefined,
|
|
2810
2907
|
limit: parseInt(opts["limit"]) || 50
|
|
2811
2908
|
});
|
|
2909
|
+
if (opts["recent"]) {
|
|
2910
|
+
prompts = prompts.filter((p) => p.last_used_at !== null).sort((a, b) => (b.last_used_at ?? "").localeCompare(a.last_used_at ?? ""));
|
|
2911
|
+
}
|
|
2812
2912
|
if (isJson()) {
|
|
2813
2913
|
output(prompts);
|
|
2814
2914
|
} else if (prompts.length === 0) {
|
|
@@ -2828,7 +2928,7 @@ program2.command("search <query>").description("Full-text search across prompts
|
|
|
2828
2928
|
const results = searchPrompts(query, {
|
|
2829
2929
|
collection: opts["collection"],
|
|
2830
2930
|
tags: opts["tags"] ? opts["tags"].split(",").map((t) => t.trim()) : undefined,
|
|
2831
|
-
limit: parseInt(opts["limit"]) || 20
|
|
2931
|
+
limit: parseInt(opts["limit"] ?? "20") || 20
|
|
2832
2932
|
});
|
|
2833
2933
|
if (isJson()) {
|
|
2834
2934
|
output(results);
|
|
@@ -2837,8 +2937,10 @@ program2.command("search <query>").description("Full-text search across prompts
|
|
|
2837
2937
|
} else {
|
|
2838
2938
|
for (const r of results) {
|
|
2839
2939
|
console.log(fmtPrompt(r.prompt));
|
|
2840
|
-
if (r.snippet)
|
|
2841
|
-
|
|
2940
|
+
if (r.snippet) {
|
|
2941
|
+
const highlighted = r.snippet.replace(/\[([^\]]+)\]/g, (_m, word) => chalk.yellowBright(word));
|
|
2942
|
+
console.log(chalk.gray(" ") + chalk.gray(highlighted));
|
|
2943
|
+
}
|
|
2842
2944
|
}
|
|
2843
2945
|
console.log(chalk.gray(`
|
|
2844
2946
|
${results.length} result(s)`));
|
|
@@ -2915,12 +3017,12 @@ program2.command("inspect <id>").description("Show a prompt's variables (for tem
|
|
|
2915
3017
|
program2.command("update <id>").description("Update a prompt's fields").option("--title <title>").option("-b, --body <body>").option("-d, --description <desc>").option("-c, --collection <name>").option("-t, --tags <tags>").option("--agent <name>").action((id, opts) => {
|
|
2916
3018
|
try {
|
|
2917
3019
|
const prompt = updatePrompt(id, {
|
|
2918
|
-
title: opts["title"],
|
|
2919
|
-
body: opts["body"],
|
|
2920
|
-
description: opts["description"],
|
|
2921
|
-
collection: opts["collection"],
|
|
3020
|
+
title: opts["title"] ?? undefined,
|
|
3021
|
+
body: opts["body"] ?? undefined,
|
|
3022
|
+
description: opts["description"] ?? undefined,
|
|
3023
|
+
collection: opts["collection"] ?? undefined,
|
|
2922
3024
|
tags: opts["tags"] ? opts["tags"].split(",").map((t) => t.trim()) : undefined,
|
|
2923
|
-
changed_by: opts["agent"]
|
|
3025
|
+
changed_by: opts["agent"] ?? undefined
|
|
2924
3026
|
});
|
|
2925
3027
|
if (isJson())
|
|
2926
3028
|
output(prompt);
|
|
@@ -3079,4 +3181,140 @@ By collection:`));
|
|
|
3079
3181
|
handleError(e);
|
|
3080
3182
|
}
|
|
3081
3183
|
});
|
|
3184
|
+
program2.command("recent [n]").description("Show recently used prompts (default: 10)").action((n) => {
|
|
3185
|
+
try {
|
|
3186
|
+
const limit = parseInt(n ?? "10") || 10;
|
|
3187
|
+
const prompts = listPrompts({ limit }).filter((p) => p.last_used_at !== null).sort((a, b) => (b.last_used_at ?? "").localeCompare(a.last_used_at ?? "")).slice(0, limit);
|
|
3188
|
+
if (isJson()) {
|
|
3189
|
+
output(prompts);
|
|
3190
|
+
return;
|
|
3191
|
+
}
|
|
3192
|
+
if (prompts.length === 0) {
|
|
3193
|
+
console.log(chalk.gray("No recently used prompts."));
|
|
3194
|
+
return;
|
|
3195
|
+
}
|
|
3196
|
+
for (const p of prompts) {
|
|
3197
|
+
const ago = chalk.gray(new Date(p.last_used_at).toLocaleString());
|
|
3198
|
+
console.log(`${chalk.bold(p.id)} ${chalk.green(p.slug)} ${p.title} ${ago}`);
|
|
3199
|
+
}
|
|
3200
|
+
} catch (e) {
|
|
3201
|
+
handleError(e);
|
|
3202
|
+
}
|
|
3203
|
+
});
|
|
3204
|
+
program2.command("lint").description("Check prompt quality: missing descriptions, undocumented vars, short bodies, no tags").option("-c, --collection <name>", "Lint only this collection").action((opts) => {
|
|
3205
|
+
try {
|
|
3206
|
+
const prompts = listPrompts({ collection: opts["collection"], limit: 1e4 });
|
|
3207
|
+
const results = lintAll(prompts);
|
|
3208
|
+
if (isJson()) {
|
|
3209
|
+
output(results);
|
|
3210
|
+
return;
|
|
3211
|
+
}
|
|
3212
|
+
if (results.length === 0) {
|
|
3213
|
+
console.log(chalk.green("\u2713 All prompts pass lint."));
|
|
3214
|
+
return;
|
|
3215
|
+
}
|
|
3216
|
+
let errors = 0, warns = 0, infos = 0;
|
|
3217
|
+
for (const { prompt: p, issues } of results) {
|
|
3218
|
+
console.log(`
|
|
3219
|
+
${chalk.bold(p.slug)} ${chalk.gray(p.id)}`);
|
|
3220
|
+
for (const issue of issues) {
|
|
3221
|
+
if (issue.severity === "error") {
|
|
3222
|
+
console.log(chalk.red(` \u2717 [${issue.rule}] ${issue.message}`));
|
|
3223
|
+
errors++;
|
|
3224
|
+
} else if (issue.severity === "warn") {
|
|
3225
|
+
console.log(chalk.yellow(` \u26A0 [${issue.rule}] ${issue.message}`));
|
|
3226
|
+
warns++;
|
|
3227
|
+
} else {
|
|
3228
|
+
console.log(chalk.gray(` \u2139 [${issue.rule}] ${issue.message}`));
|
|
3229
|
+
infos++;
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
console.log(chalk.bold(`
|
|
3234
|
+
${results.length} prompt(s) with issues \u2014 ${errors} errors, ${warns} warnings, ${infos} info`));
|
|
3235
|
+
if (errors > 0)
|
|
3236
|
+
process.exit(1);
|
|
3237
|
+
} catch (e) {
|
|
3238
|
+
handleError(e);
|
|
3239
|
+
}
|
|
3240
|
+
});
|
|
3241
|
+
program2.command("stale [days]").description("List prompts not used in N days (default: 30)").action((days) => {
|
|
3242
|
+
try {
|
|
3243
|
+
const threshold = parseInt(days ?? "30") || 30;
|
|
3244
|
+
const cutoff = new Date(Date.now() - threshold * 24 * 60 * 60 * 1000).toISOString();
|
|
3245
|
+
const all = listPrompts({ limit: 1e4 });
|
|
3246
|
+
const stale = all.filter((p) => p.last_used_at === null || p.last_used_at < cutoff).sort((a, b) => (a.last_used_at ?? "").localeCompare(b.last_used_at ?? ""));
|
|
3247
|
+
if (isJson()) {
|
|
3248
|
+
output(stale);
|
|
3249
|
+
return;
|
|
3250
|
+
}
|
|
3251
|
+
if (stale.length === 0) {
|
|
3252
|
+
console.log(chalk.green(`No stale prompts (threshold: ${threshold} days).`));
|
|
3253
|
+
return;
|
|
3254
|
+
}
|
|
3255
|
+
console.log(chalk.bold(`Stale prompts (not used in ${threshold}+ days):`));
|
|
3256
|
+
for (const p of stale) {
|
|
3257
|
+
const last = p.last_used_at ? chalk.gray(new Date(p.last_used_at).toLocaleDateString()) : chalk.red("never");
|
|
3258
|
+
console.log(` ${chalk.green(p.slug)} ${chalk.gray(`${p.use_count}\xD7`)} last used: ${last}`);
|
|
3259
|
+
}
|
|
3260
|
+
console.log(chalk.gray(`
|
|
3261
|
+
${stale.length} stale prompt(s)`));
|
|
3262
|
+
} catch (e) {
|
|
3263
|
+
handleError(e);
|
|
3264
|
+
}
|
|
3265
|
+
});
|
|
3266
|
+
program2.command("pin <id>").description("Pin a prompt so it always appears first in lists").action((id) => {
|
|
3267
|
+
try {
|
|
3268
|
+
const p = pinPrompt(id, true);
|
|
3269
|
+
if (isJson())
|
|
3270
|
+
output(p);
|
|
3271
|
+
else
|
|
3272
|
+
console.log(chalk.yellow(`\uD83D\uDCCC Pinned ${chalk.bold(p.slug)}`));
|
|
3273
|
+
} catch (e) {
|
|
3274
|
+
handleError(e);
|
|
3275
|
+
}
|
|
3276
|
+
});
|
|
3277
|
+
program2.command("unpin <id>").description("Unpin a prompt").action((id) => {
|
|
3278
|
+
try {
|
|
3279
|
+
const p = pinPrompt(id, false);
|
|
3280
|
+
if (isJson())
|
|
3281
|
+
output(p);
|
|
3282
|
+
else
|
|
3283
|
+
console.log(chalk.gray(`Unpinned ${chalk.bold(p.slug)}`));
|
|
3284
|
+
} catch (e) {
|
|
3285
|
+
handleError(e);
|
|
3286
|
+
}
|
|
3287
|
+
});
|
|
3288
|
+
program2.command("copy <id>").description("Copy prompt body to clipboard and increment use counter").action(async (id) => {
|
|
3289
|
+
try {
|
|
3290
|
+
const prompt = usePrompt(id);
|
|
3291
|
+
const { platform } = process;
|
|
3292
|
+
if (platform === "darwin") {
|
|
3293
|
+
const proc = Bun.spawn(["pbcopy"], { stdin: "pipe" });
|
|
3294
|
+
proc.stdin.write(prompt.body);
|
|
3295
|
+
proc.stdin.end();
|
|
3296
|
+
await proc.exited;
|
|
3297
|
+
} else if (platform === "linux") {
|
|
3298
|
+
try {
|
|
3299
|
+
const proc = Bun.spawn(["xclip", "-selection", "clipboard"], { stdin: "pipe" });
|
|
3300
|
+
proc.stdin.write(prompt.body);
|
|
3301
|
+
proc.stdin.end();
|
|
3302
|
+
await proc.exited;
|
|
3303
|
+
} catch {
|
|
3304
|
+
const proc = Bun.spawn(["xsel", "--clipboard", "--input"], { stdin: "pipe" });
|
|
3305
|
+
proc.stdin.write(prompt.body);
|
|
3306
|
+
proc.stdin.end();
|
|
3307
|
+
await proc.exited;
|
|
3308
|
+
}
|
|
3309
|
+
} else {
|
|
3310
|
+
handleError("Clipboard not supported on this platform. Use `prompts use` instead.");
|
|
3311
|
+
}
|
|
3312
|
+
if (isJson())
|
|
3313
|
+
output({ copied: true, id: prompt.id, slug: prompt.slug });
|
|
3314
|
+
else
|
|
3315
|
+
console.log(chalk.green(`Copied ${chalk.bold(prompt.slug)} to clipboard`));
|
|
3316
|
+
} catch (e) {
|
|
3317
|
+
handleError(e);
|
|
3318
|
+
}
|
|
3319
|
+
});
|
|
3082
3320
|
program2.parse();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAMrC,wBAAgB,SAAS,IAAI,MAAM,CAsBlC;AAED,wBAAgB,WAAW,IAAI,QAAQ,CAmBtC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAGD,wBAAgB,aAAa,IAAI,IAAI,CAEpC;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAMrC,wBAAgB,SAAS,IAAI,MAAM,CAsBlC;AAED,wBAAgB,WAAW,IAAI,QAAQ,CAmBtC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAGD,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAsHD,wBAAgB,MAAM,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAM5C;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkC3E"}
|
package/dist/db/prompts.d.ts
CHANGED
|
@@ -6,9 +6,11 @@ export declare function listPrompts(filter?: ListPromptsFilter): Prompt[];
|
|
|
6
6
|
export declare function updatePrompt(idOrSlug: string, input: UpdatePromptInput): Prompt;
|
|
7
7
|
export declare function deletePrompt(idOrSlug: string): void;
|
|
8
8
|
export declare function usePrompt(idOrSlug: string): Prompt;
|
|
9
|
-
export declare function
|
|
9
|
+
export declare function pinPrompt(idOrSlug: string, pinned: boolean): Prompt;
|
|
10
|
+
export declare function upsertPrompt(input: CreatePromptInput, force?: boolean): {
|
|
10
11
|
prompt: Prompt;
|
|
11
12
|
created: boolean;
|
|
13
|
+
duplicate_warning?: string;
|
|
12
14
|
};
|
|
13
15
|
export declare function getPromptStats(): {
|
|
14
16
|
total_prompts: number;
|
package/dist/db/prompts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/db/prompts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/db/prompts.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,MAAM,EACN,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EAGlB,MAAM,mBAAmB,CAAA;AA0B1B,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAyC7D;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOzD;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAItD;AAED,wBAAgB,WAAW,CAAC,MAAM,GAAE,iBAAsB,GAAG,MAAM,EAAE,CAmCpE;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,MAAM,CA8C/E;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAInD;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQlD;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAKnE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,UAAQ,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAE,CA6BtI;AAED,wBAAgB,cAAc;;;;;YAOJ,MAAM;cAAQ,MAAM;cAAQ,MAAM;eAAS,MAAM;mBAAa,MAAM;;;YAGpE,MAAM;cAAQ,MAAM;cAAQ,MAAM;eAAS,MAAM;sBAAgB,MAAM;;;oBAG/D,MAAM;eAAS,MAAM;;;gBAGzB,MAAM;eAAS,MAAM;;EAGlD"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createPrompt, getPrompt, requirePrompt, listPrompts, updatePrompt, deletePrompt, usePrompt, upsertPrompt, getPromptStats } from "./db/prompts.js";
|
|
1
|
+
export { createPrompt, getPrompt, requirePrompt, listPrompts, updatePrompt, deletePrompt, usePrompt, upsertPrompt, getPromptStats, pinPrompt } from "./db/prompts.js";
|
|
2
2
|
export { listVersions, getVersion, restoreVersion } from "./db/versions.js";
|
|
3
3
|
export { listCollections, getCollection, ensureCollection, movePrompt } from "./db/collections.js";
|
|
4
4
|
export { registerAgent, listAgents } from "./db/agents.js";
|
|
@@ -7,6 +7,8 @@ export { searchPrompts, findSimilar } from "./lib/search.js";
|
|
|
7
7
|
export { extractVariables, extractVariableInfo, renderTemplate, validateVars } from "./lib/template.js";
|
|
8
8
|
export type { VariableInfo } from "./lib/template.js";
|
|
9
9
|
export { importFromJson, exportToJson } from "./lib/importer.js";
|
|
10
|
+
export { findDuplicates } from "./lib/duplicates.js";
|
|
11
|
+
export type { DuplicateMatch } from "./lib/duplicates.js";
|
|
10
12
|
export { generateSlug, uniqueSlug, generatePromptId } from "./lib/ids.js";
|
|
11
13
|
export type { Prompt, PromptVersion, Collection, Agent, TemplateVariable, PromptSource, CreatePromptInput, UpdatePromptInput, ListPromptsFilter, SearchResult, RenderResult, PromptStats, } from "./types/index.js";
|
|
12
14
|
export { PromptNotFoundError, VersionConflictError, DuplicateSlugError, TemplateRenderError, } from "./types/index.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AACrK,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAC3E,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAClG,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAGzD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAG5D,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACvG,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGrD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAGzD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAGzE,YAAY,EACV,MAAM,EACN,aAAa,EACb,UAAU,EACV,KAAK,EACL,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,WAAW,GACZ,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,kBAAkB,CAAA"}
|