@hasna/prompts 0.2.2 → 0.3.1
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 +907 -93
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/prompts.d.ts +12 -1
- package/dist/db/prompts.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +190 -2
- 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 +3 -1
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/mcp/index.js +562 -24
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +192 -10
- package/dist/types/index.d.ts +52 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -17,6 +17,16 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
17
17
|
return to;
|
|
18
18
|
};
|
|
19
19
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
20
|
+
var __export = (target, all) => {
|
|
21
|
+
for (var name in all)
|
|
22
|
+
__defProp(target, name, {
|
|
23
|
+
get: all[name],
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
set: (newValue) => all[name] = () => newValue
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
20
30
|
var __require = import.meta.require;
|
|
21
31
|
|
|
22
32
|
// node_modules/commander/lib/error.js
|
|
@@ -2053,31 +2063,10 @@ var require_commander = __commonJS((exports) => {
|
|
|
2053
2063
|
exports.InvalidOptionArgumentError = InvalidArgumentError;
|
|
2054
2064
|
});
|
|
2055
2065
|
|
|
2056
|
-
// node_modules/commander/esm.mjs
|
|
2057
|
-
var import__ = __toESM(require_commander(), 1);
|
|
2058
|
-
var {
|
|
2059
|
-
program,
|
|
2060
|
-
createCommand,
|
|
2061
|
-
createArgument,
|
|
2062
|
-
createOption,
|
|
2063
|
-
CommanderError,
|
|
2064
|
-
InvalidArgumentError,
|
|
2065
|
-
InvalidOptionArgumentError,
|
|
2066
|
-
Command,
|
|
2067
|
-
Argument,
|
|
2068
|
-
Option,
|
|
2069
|
-
Help
|
|
2070
|
-
} = import__.default;
|
|
2071
|
-
|
|
2072
|
-
// src/cli/index.tsx
|
|
2073
|
-
import chalk from "chalk";
|
|
2074
|
-
import { createRequire } from "module";
|
|
2075
|
-
|
|
2076
2066
|
// src/db/database.ts
|
|
2077
2067
|
import { Database } from "bun:sqlite";
|
|
2078
2068
|
import { join } from "path";
|
|
2079
2069
|
import { existsSync, mkdirSync } from "fs";
|
|
2080
|
-
var _db = null;
|
|
2081
2070
|
function getDbPath() {
|
|
2082
2071
|
if (process.env["PROMPTS_DB_PATH"]) {
|
|
2083
2072
|
return process.env["PROMPTS_DB_PATH"];
|
|
@@ -2202,6 +2191,26 @@ function runMigrations(db) {
|
|
|
2202
2191
|
CREATE INDEX IF NOT EXISTS idx_prompts_project_id ON prompts(project_id);
|
|
2203
2192
|
`
|
|
2204
2193
|
},
|
|
2194
|
+
{
|
|
2195
|
+
name: "005_chaining",
|
|
2196
|
+
sql: `ALTER TABLE prompts ADD COLUMN next_prompt TEXT;`
|
|
2197
|
+
},
|
|
2198
|
+
{
|
|
2199
|
+
name: "006_expiry",
|
|
2200
|
+
sql: `ALTER TABLE prompts ADD COLUMN expires_at TEXT;`
|
|
2201
|
+
},
|
|
2202
|
+
{
|
|
2203
|
+
name: "007_usage_log",
|
|
2204
|
+
sql: `
|
|
2205
|
+
CREATE TABLE IF NOT EXISTS usage_log (
|
|
2206
|
+
id TEXT PRIMARY KEY,
|
|
2207
|
+
prompt_id TEXT NOT NULL REFERENCES prompts(id) ON DELETE CASCADE,
|
|
2208
|
+
used_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2209
|
+
);
|
|
2210
|
+
CREATE INDEX IF NOT EXISTS idx_usage_log_prompt_id ON usage_log(prompt_id);
|
|
2211
|
+
CREATE INDEX IF NOT EXISTS idx_usage_log_used_at ON usage_log(used_at);
|
|
2212
|
+
`
|
|
2213
|
+
},
|
|
2205
2214
|
{
|
|
2206
2215
|
name: "002_fts5",
|
|
2207
2216
|
sql: `
|
|
@@ -2284,6 +2293,8 @@ function resolvePrompt(db, idOrSlug) {
|
|
|
2284
2293
|
return byTitle[0].id;
|
|
2285
2294
|
return null;
|
|
2286
2295
|
}
|
|
2296
|
+
var _db = null;
|
|
2297
|
+
var init_database = () => {};
|
|
2287
2298
|
|
|
2288
2299
|
// src/lib/ids.ts
|
|
2289
2300
|
function generateSlug(title) {
|
|
@@ -2299,7 +2310,6 @@ function uniqueSlug(baseSlug) {
|
|
|
2299
2310
|
}
|
|
2300
2311
|
return slug;
|
|
2301
2312
|
}
|
|
2302
|
-
var CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
2303
2313
|
function nanoid(len) {
|
|
2304
2314
|
let id = "";
|
|
2305
2315
|
for (let i = 0;i < len; i++) {
|
|
@@ -2318,6 +2328,10 @@ function generatePromptId() {
|
|
|
2318
2328
|
function generateId(prefix) {
|
|
2319
2329
|
return `${prefix}-${nanoid(8)}`;
|
|
2320
2330
|
}
|
|
2331
|
+
var CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
2332
|
+
var init_ids = __esm(() => {
|
|
2333
|
+
init_database();
|
|
2334
|
+
});
|
|
2321
2335
|
|
|
2322
2336
|
// src/db/collections.ts
|
|
2323
2337
|
function rowToCollection(row) {
|
|
@@ -2373,6 +2387,10 @@ function movePrompt(promptIdOrSlug, targetCollection) {
|
|
|
2373
2387
|
row.id
|
|
2374
2388
|
]);
|
|
2375
2389
|
}
|
|
2390
|
+
var init_collections = __esm(() => {
|
|
2391
|
+
init_database();
|
|
2392
|
+
init_ids();
|
|
2393
|
+
});
|
|
2376
2394
|
|
|
2377
2395
|
// src/lib/duplicates.ts
|
|
2378
2396
|
function tokenize(text) {
|
|
@@ -2403,9 +2421,11 @@ function findDuplicates(body, threshold = 0.8, excludeSlug) {
|
|
|
2403
2421
|
}
|
|
2404
2422
|
return matches.sort((a, b) => b.score - a.score);
|
|
2405
2423
|
}
|
|
2424
|
+
var init_duplicates = __esm(() => {
|
|
2425
|
+
init_prompts();
|
|
2426
|
+
});
|
|
2406
2427
|
|
|
2407
2428
|
// src/lib/template.ts
|
|
2408
|
-
var VAR_PATTERN = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:\|\s*(.*?)\s*)?\}\}/g;
|
|
2409
2429
|
function extractVariables(body) {
|
|
2410
2430
|
const vars = new Set;
|
|
2411
2431
|
const pattern = new RegExp(VAR_PATTERN.source, "g");
|
|
@@ -2446,34 +2466,39 @@ function renderTemplate(body, vars) {
|
|
|
2446
2466
|
});
|
|
2447
2467
|
return { rendered, missing_vars: missing, used_defaults: usedDefaults };
|
|
2448
2468
|
}
|
|
2469
|
+
var VAR_PATTERN;
|
|
2470
|
+
var init_template = __esm(() => {
|
|
2471
|
+
VAR_PATTERN = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:\|\s*(.*?)\s*)?\}\}/g;
|
|
2472
|
+
});
|
|
2449
2473
|
|
|
2450
2474
|
// src/types/index.ts
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
}
|
|
2464
|
-
|
|
2465
|
-
class DuplicateSlugError extends Error {
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
}
|
|
2471
|
-
class ProjectNotFoundError extends Error {
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
}
|
|
2475
|
+
var PromptNotFoundError, VersionConflictError, DuplicateSlugError, ProjectNotFoundError;
|
|
2476
|
+
var init_types = __esm(() => {
|
|
2477
|
+
PromptNotFoundError = class PromptNotFoundError extends Error {
|
|
2478
|
+
constructor(id) {
|
|
2479
|
+
super(`Prompt not found: ${id}`);
|
|
2480
|
+
this.name = "PromptNotFoundError";
|
|
2481
|
+
}
|
|
2482
|
+
};
|
|
2483
|
+
VersionConflictError = class VersionConflictError extends Error {
|
|
2484
|
+
constructor(id) {
|
|
2485
|
+
super(`Version conflict on prompt: ${id}`);
|
|
2486
|
+
this.name = "VersionConflictError";
|
|
2487
|
+
}
|
|
2488
|
+
};
|
|
2489
|
+
DuplicateSlugError = class DuplicateSlugError extends Error {
|
|
2490
|
+
constructor(slug) {
|
|
2491
|
+
super(`A prompt with slug "${slug}" already exists`);
|
|
2492
|
+
this.name = "DuplicateSlugError";
|
|
2493
|
+
}
|
|
2494
|
+
};
|
|
2495
|
+
ProjectNotFoundError = class ProjectNotFoundError extends Error {
|
|
2496
|
+
constructor(id) {
|
|
2497
|
+
super(`Project not found: ${id}`);
|
|
2498
|
+
this.name = "ProjectNotFoundError";
|
|
2499
|
+
}
|
|
2500
|
+
};
|
|
2501
|
+
});
|
|
2477
2502
|
|
|
2478
2503
|
// src/db/prompts.ts
|
|
2479
2504
|
function rowToPrompt(row) {
|
|
@@ -2488,6 +2513,8 @@ function rowToPrompt(row) {
|
|
|
2488
2513
|
tags: JSON.parse(row["tags"] || "[]"),
|
|
2489
2514
|
variables: JSON.parse(row["variables"] || "[]"),
|
|
2490
2515
|
pinned: Boolean(row["pinned"]),
|
|
2516
|
+
next_prompt: row["next_prompt"] ?? null,
|
|
2517
|
+
expires_at: row["expires_at"] ?? null,
|
|
2491
2518
|
project_id: row["project_id"] ?? null,
|
|
2492
2519
|
is_template: Boolean(row["is_template"]),
|
|
2493
2520
|
source: row["source"],
|
|
@@ -2568,7 +2595,7 @@ function listPrompts(filter = {}) {
|
|
|
2568
2595
|
orderBy = `(CASE WHEN project_id = '${filter.project_id}' THEN 0 ELSE 1 END), pinned DESC, use_count DESC, updated_at DESC`;
|
|
2569
2596
|
}
|
|
2570
2597
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2571
|
-
const limit = filter.limit ??
|
|
2598
|
+
const limit = filter.limit ?? 20;
|
|
2572
2599
|
const offset = filter.offset ?? 0;
|
|
2573
2600
|
const rows = db.query(`SELECT * FROM prompts ${where} ORDER BY ${orderBy} LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
2574
2601
|
return rows.map(rowToPrompt);
|
|
@@ -2586,6 +2613,7 @@ function updatePrompt(idOrSlug, input) {
|
|
|
2586
2613
|
description = COALESCE(?, description),
|
|
2587
2614
|
collection = COALESCE(?, collection),
|
|
2588
2615
|
tags = COALESCE(?, tags),
|
|
2616
|
+
next_prompt = CASE WHEN ? IS NOT NULL THEN ? ELSE next_prompt END,
|
|
2589
2617
|
variables = ?,
|
|
2590
2618
|
is_template = ?,
|
|
2591
2619
|
version = version + 1,
|
|
@@ -2596,6 +2624,8 @@ function updatePrompt(idOrSlug, input) {
|
|
|
2596
2624
|
input.description ?? null,
|
|
2597
2625
|
input.collection ?? null,
|
|
2598
2626
|
input.tags ? JSON.stringify(input.tags) : null,
|
|
2627
|
+
"next_prompt" in input ? input.next_prompt ?? "" : null,
|
|
2628
|
+
"next_prompt" in input ? input.next_prompt ?? null : null,
|
|
2599
2629
|
variables,
|
|
2600
2630
|
is_template,
|
|
2601
2631
|
prompt.id,
|
|
@@ -2618,6 +2648,30 @@ function usePrompt(idOrSlug) {
|
|
|
2618
2648
|
const db = getDatabase();
|
|
2619
2649
|
const prompt = requirePrompt(idOrSlug);
|
|
2620
2650
|
db.run("UPDATE prompts SET use_count = use_count + 1, last_used_at = datetime('now') WHERE id = ?", [prompt.id]);
|
|
2651
|
+
db.run("INSERT INTO usage_log (id, prompt_id) VALUES (?, ?)", [generateId("UL"), prompt.id]);
|
|
2652
|
+
return requirePrompt(prompt.id);
|
|
2653
|
+
}
|
|
2654
|
+
function getTrending(days = 7, limit = 10) {
|
|
2655
|
+
const db = getDatabase();
|
|
2656
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
2657
|
+
return db.query(`SELECT p.id, p.slug, p.title, COUNT(ul.id) as uses
|
|
2658
|
+
FROM usage_log ul
|
|
2659
|
+
JOIN prompts p ON p.id = ul.prompt_id
|
|
2660
|
+
WHERE ul.used_at >= ?
|
|
2661
|
+
GROUP BY p.id
|
|
2662
|
+
ORDER BY uses DESC
|
|
2663
|
+
LIMIT ?`).all(cutoff, limit);
|
|
2664
|
+
}
|
|
2665
|
+
function setExpiry(idOrSlug, expiresAt) {
|
|
2666
|
+
const db = getDatabase();
|
|
2667
|
+
const prompt = requirePrompt(idOrSlug);
|
|
2668
|
+
db.run("UPDATE prompts SET expires_at = ?, updated_at = datetime('now') WHERE id = ?", [expiresAt, prompt.id]);
|
|
2669
|
+
return requirePrompt(prompt.id);
|
|
2670
|
+
}
|
|
2671
|
+
function setNextPrompt(idOrSlug, nextSlug) {
|
|
2672
|
+
const db = getDatabase();
|
|
2673
|
+
const prompt = requirePrompt(idOrSlug);
|
|
2674
|
+
db.run("UPDATE prompts SET next_prompt = ?, updated_at = datetime('now') WHERE id = ?", [nextSlug, prompt.id]);
|
|
2621
2675
|
return requirePrompt(prompt.id);
|
|
2622
2676
|
}
|
|
2623
2677
|
function pinPrompt(idOrSlug, pinned) {
|
|
@@ -2663,8 +2717,198 @@ function getPromptStats() {
|
|
|
2663
2717
|
const bySource = db.query("SELECT source, COUNT(*) as count FROM prompts GROUP BY source ORDER BY count DESC").all();
|
|
2664
2718
|
return { total_prompts: total, total_templates: templates, total_collections: collections, most_used: mostUsed, recently_used: recentlyUsed, by_collection: byCollection, by_source: bySource };
|
|
2665
2719
|
}
|
|
2720
|
+
var init_prompts = __esm(() => {
|
|
2721
|
+
init_database();
|
|
2722
|
+
init_ids();
|
|
2723
|
+
init_collections();
|
|
2724
|
+
init_duplicates();
|
|
2725
|
+
init_template();
|
|
2726
|
+
init_types();
|
|
2727
|
+
init_ids();
|
|
2728
|
+
});
|
|
2729
|
+
|
|
2730
|
+
// src/lib/importer.ts
|
|
2731
|
+
var exports_importer = {};
|
|
2732
|
+
__export(exports_importer, {
|
|
2733
|
+
scanAndImportSlashCommands: () => scanAndImportSlashCommands,
|
|
2734
|
+
promptToMarkdown: () => promptToMarkdown,
|
|
2735
|
+
markdownToImportItem: () => markdownToImportItem,
|
|
2736
|
+
importFromMarkdown: () => importFromMarkdown,
|
|
2737
|
+
importFromJson: () => importFromJson,
|
|
2738
|
+
importFromClaudeCommands: () => importFromClaudeCommands,
|
|
2739
|
+
exportToMarkdownFiles: () => exportToMarkdownFiles,
|
|
2740
|
+
exportToJson: () => exportToJson
|
|
2741
|
+
});
|
|
2742
|
+
function importFromJson(items, changedBy) {
|
|
2743
|
+
let created = 0;
|
|
2744
|
+
let updated = 0;
|
|
2745
|
+
const errors = [];
|
|
2746
|
+
for (const item of items) {
|
|
2747
|
+
try {
|
|
2748
|
+
const input = {
|
|
2749
|
+
title: item.title,
|
|
2750
|
+
body: item.body,
|
|
2751
|
+
slug: item.slug,
|
|
2752
|
+
description: item.description,
|
|
2753
|
+
collection: item.collection,
|
|
2754
|
+
tags: item.tags,
|
|
2755
|
+
source: "imported",
|
|
2756
|
+
changed_by: changedBy
|
|
2757
|
+
};
|
|
2758
|
+
const { created: wasCreated } = upsertPrompt(input);
|
|
2759
|
+
if (wasCreated)
|
|
2760
|
+
created++;
|
|
2761
|
+
else
|
|
2762
|
+
updated++;
|
|
2763
|
+
} catch (e) {
|
|
2764
|
+
errors.push({ item: item.title, error: e instanceof Error ? e.message : String(e) });
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
return { created, updated, errors };
|
|
2768
|
+
}
|
|
2769
|
+
function exportToJson(collection) {
|
|
2770
|
+
const prompts = listPrompts({ collection, limit: 1e4 });
|
|
2771
|
+
return { prompts, exported_at: new Date().toISOString(), collection };
|
|
2772
|
+
}
|
|
2773
|
+
function promptToMarkdown(prompt) {
|
|
2774
|
+
const tags = prompt.tags.length > 0 ? `[${prompt.tags.join(", ")}]` : "[]";
|
|
2775
|
+
const desc = prompt.description ? `
|
|
2776
|
+
description: ${prompt.description}` : "";
|
|
2777
|
+
return `---
|
|
2778
|
+
title: ${prompt.title}
|
|
2779
|
+
slug: ${prompt.slug}
|
|
2780
|
+
collection: ${prompt.collection}
|
|
2781
|
+
tags: ${tags}${desc}
|
|
2782
|
+
---
|
|
2783
|
+
|
|
2784
|
+
${prompt.body}
|
|
2785
|
+
`;
|
|
2786
|
+
}
|
|
2787
|
+
function exportToMarkdownFiles(collection) {
|
|
2788
|
+
const prompts = listPrompts({ collection, limit: 1e4 });
|
|
2789
|
+
return prompts.map((p) => ({
|
|
2790
|
+
filename: `${p.slug}.md`,
|
|
2791
|
+
content: promptToMarkdown(p)
|
|
2792
|
+
}));
|
|
2793
|
+
}
|
|
2794
|
+
function markdownToImportItem(content, filename) {
|
|
2795
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n+([\s\S]*)$/);
|
|
2796
|
+
if (!frontmatterMatch) {
|
|
2797
|
+
if (!filename)
|
|
2798
|
+
return null;
|
|
2799
|
+
const title2 = filename.replace(/\.md$/, "").replace(/-/g, " ");
|
|
2800
|
+
return { title: title2, body: content.trim() };
|
|
2801
|
+
}
|
|
2802
|
+
const frontmatter = frontmatterMatch[1] ?? "";
|
|
2803
|
+
const body = (frontmatterMatch[2] ?? "").trim();
|
|
2804
|
+
const get = (key) => {
|
|
2805
|
+
const m = frontmatter.match(new RegExp(`^${key}:\\s*(.+)$`, "m"));
|
|
2806
|
+
return m ? (m[1] ?? "").trim() : null;
|
|
2807
|
+
};
|
|
2808
|
+
const title = get("title") ?? (filename?.replace(/\.md$/, "").replace(/-/g, " ") ?? "Untitled");
|
|
2809
|
+
const slug = get("slug") ?? undefined;
|
|
2810
|
+
const collection = get("collection") ?? undefined;
|
|
2811
|
+
const description = get("description") ?? undefined;
|
|
2812
|
+
const tagsStr = get("tags");
|
|
2813
|
+
let tags;
|
|
2814
|
+
if (tagsStr) {
|
|
2815
|
+
const inner = tagsStr.replace(/^\[/, "").replace(/\]$/, "");
|
|
2816
|
+
tags = inner.split(",").map((t) => t.trim()).filter(Boolean);
|
|
2817
|
+
}
|
|
2818
|
+
return { title, slug, body, collection, tags, description };
|
|
2819
|
+
}
|
|
2820
|
+
function importFromMarkdown(files, changedBy) {
|
|
2821
|
+
const items = files.map((f) => markdownToImportItem(f.content, f.filename)).filter((item) => item !== null);
|
|
2822
|
+
return importFromJson(items, changedBy);
|
|
2823
|
+
}
|
|
2824
|
+
function scanAndImportSlashCommands(rootDir, changedBy) {
|
|
2825
|
+
const { existsSync: existsSync2, readdirSync, readFileSync } = __require("fs");
|
|
2826
|
+
const { join: join2 } = __require("path");
|
|
2827
|
+
const home = process.env["HOME"] ?? "~";
|
|
2828
|
+
const sources = [
|
|
2829
|
+
{ dir: join2(rootDir, ".claude", "commands"), collection: "claude-commands", tags: ["claude", "slash-command"] },
|
|
2830
|
+
{ dir: join2(home, ".claude", "commands"), collection: "claude-commands", tags: ["claude", "slash-command"] },
|
|
2831
|
+
{ dir: join2(rootDir, ".codex", "skills"), collection: "codex-skills", tags: ["codex", "skill"] },
|
|
2832
|
+
{ dir: join2(home, ".codex", "skills"), collection: "codex-skills", tags: ["codex", "skill"] },
|
|
2833
|
+
{ dir: join2(rootDir, ".gemini", "extensions"), collection: "gemini-extensions", tags: ["gemini", "extension"] },
|
|
2834
|
+
{ dir: join2(home, ".gemini", "extensions"), collection: "gemini-extensions", tags: ["gemini", "extension"] }
|
|
2835
|
+
];
|
|
2836
|
+
const files = [];
|
|
2837
|
+
const scanned = [];
|
|
2838
|
+
for (const { dir, collection, tags } of sources) {
|
|
2839
|
+
if (!existsSync2(dir))
|
|
2840
|
+
continue;
|
|
2841
|
+
let entries;
|
|
2842
|
+
try {
|
|
2843
|
+
entries = readdirSync(dir);
|
|
2844
|
+
} catch {
|
|
2845
|
+
continue;
|
|
2846
|
+
}
|
|
2847
|
+
for (const entry of entries) {
|
|
2848
|
+
if (!entry.endsWith(".md"))
|
|
2849
|
+
continue;
|
|
2850
|
+
const filePath = join2(dir, entry);
|
|
2851
|
+
try {
|
|
2852
|
+
const content = readFileSync(filePath, "utf-8");
|
|
2853
|
+
files.push({ filename: entry, content, collection, tags });
|
|
2854
|
+
scanned.push({ source: dir, file: entry });
|
|
2855
|
+
} catch {}
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
const items = files.map((f) => {
|
|
2859
|
+
const base = markdownToImportItem(f.content, f.filename);
|
|
2860
|
+
if (base)
|
|
2861
|
+
return { ...base, collection: base.collection ?? f.collection, tags: base.tags ?? f.tags };
|
|
2862
|
+
const name = f.filename.replace(/\.md$/, "");
|
|
2863
|
+
const title = name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
2864
|
+
return { title, slug: name, body: f.content.trim(), collection: f.collection, tags: f.tags };
|
|
2865
|
+
});
|
|
2866
|
+
const imported = importFromJson(items, changedBy);
|
|
2867
|
+
return { scanned, imported };
|
|
2868
|
+
}
|
|
2869
|
+
function importFromClaudeCommands(files, changedBy) {
|
|
2870
|
+
const items = files.map((f) => {
|
|
2871
|
+
const name = f.filename.replace(/\.md$/, "");
|
|
2872
|
+
const title = name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
2873
|
+
return {
|
|
2874
|
+
title,
|
|
2875
|
+
slug: name,
|
|
2876
|
+
body: f.content.trim(),
|
|
2877
|
+
collection: "claude-commands",
|
|
2878
|
+
tags: ["claude", "slash-command"]
|
|
2879
|
+
};
|
|
2880
|
+
});
|
|
2881
|
+
return importFromJson(items, changedBy);
|
|
2882
|
+
}
|
|
2883
|
+
var init_importer = __esm(() => {
|
|
2884
|
+
init_prompts();
|
|
2885
|
+
});
|
|
2886
|
+
|
|
2887
|
+
// node_modules/commander/esm.mjs
|
|
2888
|
+
var import__ = __toESM(require_commander(), 1);
|
|
2889
|
+
var {
|
|
2890
|
+
program,
|
|
2891
|
+
createCommand,
|
|
2892
|
+
createArgument,
|
|
2893
|
+
createOption,
|
|
2894
|
+
CommanderError,
|
|
2895
|
+
InvalidArgumentError,
|
|
2896
|
+
InvalidOptionArgumentError,
|
|
2897
|
+
Command,
|
|
2898
|
+
Argument,
|
|
2899
|
+
Option,
|
|
2900
|
+
Help
|
|
2901
|
+
} = import__.default;
|
|
2902
|
+
|
|
2903
|
+
// src/cli/index.tsx
|
|
2904
|
+
init_prompts();
|
|
2905
|
+
import chalk from "chalk";
|
|
2906
|
+
import { createRequire } from "module";
|
|
2666
2907
|
|
|
2667
2908
|
// src/db/versions.ts
|
|
2909
|
+
init_database();
|
|
2910
|
+
init_ids();
|
|
2911
|
+
init_types();
|
|
2668
2912
|
function rowToVersion(row) {
|
|
2669
2913
|
return {
|
|
2670
2914
|
id: row["id"],
|
|
@@ -2703,7 +2947,13 @@ function restoreVersion(promptId, version, changedBy) {
|
|
|
2703
2947
|
VALUES (?, ?, ?, ?, ?)`, [generateId("VER"), promptId, ver.body, newVersion, changedBy ?? null]);
|
|
2704
2948
|
}
|
|
2705
2949
|
|
|
2950
|
+
// src/cli/index.tsx
|
|
2951
|
+
init_collections();
|
|
2952
|
+
|
|
2706
2953
|
// src/db/projects.ts
|
|
2954
|
+
init_database();
|
|
2955
|
+
init_ids();
|
|
2956
|
+
init_types();
|
|
2707
2957
|
function rowToProject(row, promptCount) {
|
|
2708
2958
|
return {
|
|
2709
2959
|
id: row["id"],
|
|
@@ -2749,7 +2999,12 @@ function deleteProject(idOrSlug) {
|
|
|
2749
2999
|
db.run("DELETE FROM projects WHERE id = ?", [id]);
|
|
2750
3000
|
}
|
|
2751
3001
|
|
|
3002
|
+
// src/cli/index.tsx
|
|
3003
|
+
init_database();
|
|
3004
|
+
|
|
2752
3005
|
// src/lib/search.ts
|
|
3006
|
+
init_database();
|
|
3007
|
+
init_prompts();
|
|
2753
3008
|
function rowToSearchResult(row, snippet) {
|
|
2754
3009
|
return {
|
|
2755
3010
|
prompt: {
|
|
@@ -2763,6 +3018,8 @@ function rowToSearchResult(row, snippet) {
|
|
|
2763
3018
|
tags: JSON.parse(row["tags"] || "[]"),
|
|
2764
3019
|
variables: JSON.parse(row["variables"] || "[]"),
|
|
2765
3020
|
pinned: Boolean(row["pinned"]),
|
|
3021
|
+
next_prompt: row["next_prompt"] ?? null,
|
|
3022
|
+
expires_at: row["expires_at"] ?? null,
|
|
2766
3023
|
project_id: row["project_id"] ?? null,
|
|
2767
3024
|
is_template: Boolean(row["is_template"]),
|
|
2768
3025
|
source: row["source"],
|
|
@@ -2830,43 +3087,34 @@ function searchPrompts(query, filter = {}) {
|
|
|
2830
3087
|
const rows = db.query(`SELECT *, 1 as score FROM prompts
|
|
2831
3088
|
WHERE (name LIKE ? OR slug LIKE ? OR title LIKE ? OR body LIKE ? OR description LIKE ? OR tags LIKE ?)
|
|
2832
3089
|
ORDER BY use_count DESC, updated_at DESC
|
|
2833
|
-
LIMIT ? OFFSET ?`).all(like, like, like, like, like, like, filter.limit ??
|
|
3090
|
+
LIMIT ? OFFSET ?`).all(like, like, like, like, like, like, filter.limit ?? 10, filter.offset ?? 0);
|
|
2834
3091
|
return rows.map((r) => rowToSearchResult(r));
|
|
2835
3092
|
}
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
const
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
body: item.body,
|
|
2847
|
-
slug: item.slug,
|
|
2848
|
-
description: item.description,
|
|
2849
|
-
collection: item.collection,
|
|
2850
|
-
tags: item.tags,
|
|
2851
|
-
source: "imported",
|
|
2852
|
-
changed_by: changedBy
|
|
2853
|
-
};
|
|
2854
|
-
const { created: wasCreated } = upsertPrompt(input);
|
|
2855
|
-
if (wasCreated)
|
|
2856
|
-
created++;
|
|
2857
|
-
else
|
|
2858
|
-
updated++;
|
|
2859
|
-
} catch (e) {
|
|
2860
|
-
errors.push({ item: item.title, error: e instanceof Error ? e.message : String(e) });
|
|
2861
|
-
}
|
|
3093
|
+
function findSimilar(promptId, limit = 5) {
|
|
3094
|
+
const db = getDatabase();
|
|
3095
|
+
const prompt = db.query("SELECT * FROM prompts WHERE id = ?").get(promptId);
|
|
3096
|
+
if (!prompt)
|
|
3097
|
+
return [];
|
|
3098
|
+
const tags = JSON.parse(prompt["tags"] || "[]");
|
|
3099
|
+
const collection = prompt["collection"];
|
|
3100
|
+
if (tags.length === 0) {
|
|
3101
|
+
const rows = db.query("SELECT *, 1 as score FROM prompts WHERE collection = ? AND id != ? ORDER BY use_count DESC LIMIT ?").all(collection, promptId, limit);
|
|
3102
|
+
return rows.map((r) => rowToSearchResult(r));
|
|
2862
3103
|
}
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
3104
|
+
const allRows = db.query("SELECT * FROM prompts WHERE id != ?").all(promptId);
|
|
3105
|
+
const scored = allRows.map((row) => {
|
|
3106
|
+
const rowTags = JSON.parse(row["tags"] || "[]");
|
|
3107
|
+
const overlap = rowTags.filter((t) => tags.includes(t)).length;
|
|
3108
|
+
const sameCollection = row["collection"] === collection ? 1 : 0;
|
|
3109
|
+
return { row, score: overlap * 2 + sameCollection };
|
|
3110
|
+
});
|
|
3111
|
+
return scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((s) => rowToSearchResult(s.row, undefined));
|
|
2868
3112
|
}
|
|
2869
3113
|
|
|
3114
|
+
// src/cli/index.tsx
|
|
3115
|
+
init_template();
|
|
3116
|
+
init_importer();
|
|
3117
|
+
|
|
2870
3118
|
// src/lib/lint.ts
|
|
2871
3119
|
function lintPrompt(p) {
|
|
2872
3120
|
const issues = [];
|
|
@@ -2901,6 +3149,271 @@ function lintAll(prompts) {
|
|
|
2901
3149
|
return prompts.map((p) => ({ prompt: p, issues: lintPrompt(p) })).filter((r) => r.issues.length > 0);
|
|
2902
3150
|
}
|
|
2903
3151
|
|
|
3152
|
+
// src/lib/audit.ts
|
|
3153
|
+
init_database();
|
|
3154
|
+
function runAudit() {
|
|
3155
|
+
const db = getDatabase();
|
|
3156
|
+
const issues = [];
|
|
3157
|
+
const orphaned = db.query(`
|
|
3158
|
+
SELECT p.id, p.slug FROM prompts p
|
|
3159
|
+
WHERE p.project_id IS NOT NULL
|
|
3160
|
+
AND NOT EXISTS (SELECT 1 FROM projects pr WHERE pr.id = p.project_id)
|
|
3161
|
+
`).all();
|
|
3162
|
+
for (const p of orphaned) {
|
|
3163
|
+
issues.push({
|
|
3164
|
+
type: "orphaned-project",
|
|
3165
|
+
severity: "error",
|
|
3166
|
+
prompt_id: p.id,
|
|
3167
|
+
slug: p.slug,
|
|
3168
|
+
message: `Prompt "${p.slug}" references a deleted project`
|
|
3169
|
+
});
|
|
3170
|
+
}
|
|
3171
|
+
const emptyCollections = db.query(`
|
|
3172
|
+
SELECT c.name FROM collections c
|
|
3173
|
+
WHERE NOT EXISTS (SELECT 1 FROM prompts p WHERE p.collection = c.name)
|
|
3174
|
+
AND c.name != 'default'
|
|
3175
|
+
`).all();
|
|
3176
|
+
for (const c of emptyCollections) {
|
|
3177
|
+
issues.push({
|
|
3178
|
+
type: "empty-collection",
|
|
3179
|
+
severity: "info",
|
|
3180
|
+
message: `Collection "${c.name}" has no prompts`
|
|
3181
|
+
});
|
|
3182
|
+
}
|
|
3183
|
+
const missingHistory = db.query(`
|
|
3184
|
+
SELECT p.id, p.slug FROM prompts p
|
|
3185
|
+
WHERE NOT EXISTS (SELECT 1 FROM prompt_versions v WHERE v.prompt_id = p.id)
|
|
3186
|
+
`).all();
|
|
3187
|
+
for (const p of missingHistory) {
|
|
3188
|
+
issues.push({
|
|
3189
|
+
type: "missing-version-history",
|
|
3190
|
+
severity: "warn",
|
|
3191
|
+
prompt_id: p.id,
|
|
3192
|
+
slug: p.slug,
|
|
3193
|
+
message: `Prompt "${p.slug}" has no version history entries`
|
|
3194
|
+
});
|
|
3195
|
+
}
|
|
3196
|
+
const slugs = db.query("SELECT id, slug FROM prompts").all();
|
|
3197
|
+
const seen = new Map;
|
|
3198
|
+
for (const { id, slug } of slugs) {
|
|
3199
|
+
const base = slug.replace(/-\d+$/, "");
|
|
3200
|
+
if (seen.has(base) && seen.get(base) !== id) {
|
|
3201
|
+
issues.push({
|
|
3202
|
+
type: "near-duplicate-slug",
|
|
3203
|
+
severity: "info",
|
|
3204
|
+
slug,
|
|
3205
|
+
message: `"${slug}" looks like a duplicate of "${base}" \u2014 consider merging`
|
|
3206
|
+
});
|
|
3207
|
+
} else {
|
|
3208
|
+
seen.set(base, id);
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
const now = new Date().toISOString();
|
|
3212
|
+
const expired = db.query(`
|
|
3213
|
+
SELECT id, slug FROM prompts WHERE expires_at IS NOT NULL AND expires_at < ?
|
|
3214
|
+
`).all(now);
|
|
3215
|
+
for (const p of expired) {
|
|
3216
|
+
issues.push({
|
|
3217
|
+
type: "expired",
|
|
3218
|
+
severity: "warn",
|
|
3219
|
+
prompt_id: p.id,
|
|
3220
|
+
slug: p.slug,
|
|
3221
|
+
message: `Prompt "${p.slug}" has expired`
|
|
3222
|
+
});
|
|
3223
|
+
}
|
|
3224
|
+
const errors = issues.filter((i) => i.severity === "error").length;
|
|
3225
|
+
const warnings = issues.filter((i) => i.severity === "warn").length;
|
|
3226
|
+
const info = issues.filter((i) => i.severity === "info").length;
|
|
3227
|
+
return { issues, errors, warnings, info, checked_at: new Date().toISOString() };
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
// src/lib/completion.ts
|
|
3231
|
+
var SUBCOMMANDS = [
|
|
3232
|
+
"save",
|
|
3233
|
+
"use",
|
|
3234
|
+
"get",
|
|
3235
|
+
"list",
|
|
3236
|
+
"search",
|
|
3237
|
+
"render",
|
|
3238
|
+
"templates",
|
|
3239
|
+
"inspect",
|
|
3240
|
+
"update",
|
|
3241
|
+
"delete",
|
|
3242
|
+
"history",
|
|
3243
|
+
"restore",
|
|
3244
|
+
"collections",
|
|
3245
|
+
"move",
|
|
3246
|
+
"pin",
|
|
3247
|
+
"unpin",
|
|
3248
|
+
"copy",
|
|
3249
|
+
"recent",
|
|
3250
|
+
"stale",
|
|
3251
|
+
"unused",
|
|
3252
|
+
"lint",
|
|
3253
|
+
"stats",
|
|
3254
|
+
"export",
|
|
3255
|
+
"import",
|
|
3256
|
+
"import-slash-commands",
|
|
3257
|
+
"watch",
|
|
3258
|
+
"similar",
|
|
3259
|
+
"diff",
|
|
3260
|
+
"duplicate",
|
|
3261
|
+
"trending",
|
|
3262
|
+
"audit",
|
|
3263
|
+
"completion",
|
|
3264
|
+
"project"
|
|
3265
|
+
];
|
|
3266
|
+
var GLOBAL_OPTIONS = ["--json", "--project"];
|
|
3267
|
+
var SOURCES = ["manual", "ai-session", "imported"];
|
|
3268
|
+
function generateZshCompletion() {
|
|
3269
|
+
return `#compdef prompts
|
|
3270
|
+
|
|
3271
|
+
_prompts() {
|
|
3272
|
+
local state line
|
|
3273
|
+
typeset -A opt_args
|
|
3274
|
+
|
|
3275
|
+
_arguments \\
|
|
3276
|
+
'--json[Output as JSON]' \\
|
|
3277
|
+
'--project[Active project]:project:->projects' \\
|
|
3278
|
+
'1:command:->commands' \\
|
|
3279
|
+
'*::args:->args'
|
|
3280
|
+
|
|
3281
|
+
case $state in
|
|
3282
|
+
commands)
|
|
3283
|
+
local commands=(${SUBCOMMANDS.map((c) => `'${c}'`).join(" ")})
|
|
3284
|
+
_describe 'command' commands
|
|
3285
|
+
;;
|
|
3286
|
+
projects)
|
|
3287
|
+
local projects=($(prompts project list --json 2>/dev/null | command grep -o '"slug":"[^"]*"' | cut -d'"' -f4))
|
|
3288
|
+
_describe 'project' projects
|
|
3289
|
+
;;
|
|
3290
|
+
args)
|
|
3291
|
+
case $line[1] in
|
|
3292
|
+
use|get|copy|pin|unpin|inspect|history|diff|duplicate|similar)
|
|
3293
|
+
local slugs=($(prompts list --json 2>/dev/null | command grep -o '"slug":"[^"]*"' | cut -d'"' -f4))
|
|
3294
|
+
_describe 'prompt' slugs
|
|
3295
|
+
;;
|
|
3296
|
+
save|update)
|
|
3297
|
+
_arguments \\
|
|
3298
|
+
'-b[Body]:body:' \\
|
|
3299
|
+
'-f[File]:file:_files' \\
|
|
3300
|
+
'-s[Slug]:slug:' \\
|
|
3301
|
+
'-d[Description]:description:' \\
|
|
3302
|
+
'-c[Collection]:collection:($(prompts collections --json 2>/dev/null | command grep -o '"name":"[^"]*"' | cut -d'"' -f4))' \\
|
|
3303
|
+
'-t[Tags]:tags:' \\
|
|
3304
|
+
'--source[Source]:source:(${SOURCES.join(" ")})' \\
|
|
3305
|
+
'--pin[Pin immediately]' \\
|
|
3306
|
+
'--force[Force save]'
|
|
3307
|
+
;;
|
|
3308
|
+
list|search)
|
|
3309
|
+
_arguments \\
|
|
3310
|
+
'-c[Collection]:collection:($(prompts collections --json 2>/dev/null | command grep -o '"name":"[^"]*"' | cut -d'"' -f4))' \\
|
|
3311
|
+
'-t[Tags]:tags:' \\
|
|
3312
|
+
'--templates[Templates only]' \\
|
|
3313
|
+
'--recent[Sort by recent]' \\
|
|
3314
|
+
'-n[Limit]:number:'
|
|
3315
|
+
;;
|
|
3316
|
+
move)
|
|
3317
|
+
local slugs=($(prompts list --json 2>/dev/null | command grep -o '"slug":"[^"]*"' | cut -d'"' -f4))
|
|
3318
|
+
_describe 'prompt' slugs
|
|
3319
|
+
;;
|
|
3320
|
+
restore)
|
|
3321
|
+
local slugs=($(prompts list --json 2>/dev/null | command grep -o '"slug":"[^"]*"' | cut -d'"' -f4))
|
|
3322
|
+
_describe 'prompt' slugs
|
|
3323
|
+
;;
|
|
3324
|
+
completion)
|
|
3325
|
+
_arguments '1:shell:(zsh bash)'
|
|
3326
|
+
;;
|
|
3327
|
+
esac
|
|
3328
|
+
;;
|
|
3329
|
+
esac
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
_prompts
|
|
3333
|
+
`;
|
|
3334
|
+
}
|
|
3335
|
+
function generateBashCompletion() {
|
|
3336
|
+
return `_prompts_completions() {
|
|
3337
|
+
local cur prev words
|
|
3338
|
+
COMPREPLY=()
|
|
3339
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
3340
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
3341
|
+
|
|
3342
|
+
local subcommands="${SUBCOMMANDS.join(" ")}"
|
|
3343
|
+
|
|
3344
|
+
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
3345
|
+
COMPREPLY=($(compgen -W "\${subcommands}" -- "\${cur}"))
|
|
3346
|
+
return 0
|
|
3347
|
+
fi
|
|
3348
|
+
|
|
3349
|
+
case "\${prev}" in
|
|
3350
|
+
--project)
|
|
3351
|
+
local projects=$(prompts project list --json 2>/dev/null | grep -o '"slug":"[^"]*"' | cut -d'"' -f4)
|
|
3352
|
+
COMPREPLY=($(compgen -W "\${projects}" -- "\${cur}"))
|
|
3353
|
+
return 0
|
|
3354
|
+
;;
|
|
3355
|
+
-c)
|
|
3356
|
+
local cols=$(prompts collections --json 2>/dev/null | grep -o '"name":"[^"]*"' | cut -d'"' -f4)
|
|
3357
|
+
COMPREPLY=($(compgen -W "\${cols}" -- "\${cur}"))
|
|
3358
|
+
return 0
|
|
3359
|
+
;;
|
|
3360
|
+
--source)
|
|
3361
|
+
COMPREPLY=($(compgen -W "${SOURCES.join(" ")}" -- "\${cur}"))
|
|
3362
|
+
return 0
|
|
3363
|
+
;;
|
|
3364
|
+
esac
|
|
3365
|
+
|
|
3366
|
+
local cmd="\${COMP_WORDS[1]}"
|
|
3367
|
+
case "\${cmd}" in
|
|
3368
|
+
use|get|copy|pin|unpin|inspect|history|diff|duplicate|similar|render|restore|move|update|delete)
|
|
3369
|
+
local slugs=$(prompts list --json 2>/dev/null | grep -o '"slug":"[^"]*"' | cut -d'"' -f4)
|
|
3370
|
+
COMPREPLY=($(compgen -W "\${slugs}" -- "\${cur}"))
|
|
3371
|
+
;;
|
|
3372
|
+
completion)
|
|
3373
|
+
COMPREPLY=($(compgen -W "zsh bash" -- "\${cur}"))
|
|
3374
|
+
;;
|
|
3375
|
+
*)
|
|
3376
|
+
COMPREPLY=($(compgen -W "${GLOBAL_OPTIONS.join(" ")}" -- "\${cur}"))
|
|
3377
|
+
;;
|
|
3378
|
+
esac
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
complete -F _prompts_completions prompts
|
|
3382
|
+
`;
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
// src/lib/diff.ts
|
|
3386
|
+
function diffTexts(a, b) {
|
|
3387
|
+
const aLines = a.split(`
|
|
3388
|
+
`);
|
|
3389
|
+
const bLines = b.split(`
|
|
3390
|
+
`);
|
|
3391
|
+
const m = aLines.length;
|
|
3392
|
+
const n = bLines.length;
|
|
3393
|
+
const lcs = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
3394
|
+
for (let i2 = 1;i2 <= m; i2++) {
|
|
3395
|
+
for (let j2 = 1;j2 <= n; j2++) {
|
|
3396
|
+
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);
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
const trace = [];
|
|
3400
|
+
let i = m, j = n;
|
|
3401
|
+
while (i > 0 || j > 0) {
|
|
3402
|
+
if (i > 0 && j > 0 && aLines[i - 1] === bLines[j - 1]) {
|
|
3403
|
+
trace.unshift({ type: "unchanged", content: aLines[i - 1] ?? "" });
|
|
3404
|
+
i--;
|
|
3405
|
+
j--;
|
|
3406
|
+
} else if (j > 0 && (i === 0 || (lcs[i][j - 1] ?? 0) >= (lcs[i - 1][j] ?? 0))) {
|
|
3407
|
+
trace.unshift({ type: "added", content: bLines[j - 1] ?? "" });
|
|
3408
|
+
j--;
|
|
3409
|
+
} else {
|
|
3410
|
+
trace.unshift({ type: "removed", content: aLines[i - 1] ?? "" });
|
|
3411
|
+
i--;
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
return trace;
|
|
3415
|
+
}
|
|
3416
|
+
|
|
2904
3417
|
// src/cli/index.tsx
|
|
2905
3418
|
var require2 = createRequire(import.meta.url);
|
|
2906
3419
|
var pkg = require2("../../package.json");
|
|
@@ -2937,7 +3450,7 @@ function fmtPrompt(p) {
|
|
|
2937
3450
|
const pin = p.pinned ? chalk.yellow(" \uD83D\uDCCC") : "";
|
|
2938
3451
|
return `${chalk.bold(p.id)} ${chalk.green(p.slug)}${template}${pin} ${p.title}${tags} ${chalk.gray(p.collection)}`;
|
|
2939
3452
|
}
|
|
2940
|
-
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) => {
|
|
3453
|
+
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").option("--pin", "Pin immediately so it appears first in all lists").action(async (title, opts) => {
|
|
2941
3454
|
try {
|
|
2942
3455
|
let body = opts["body"] ?? "";
|
|
2943
3456
|
if (opts["file"]) {
|
|
@@ -2966,13 +3479,17 @@ program2.command("save <title>").description("Save a new prompt (or update exist
|
|
|
2966
3479
|
if (duplicate_warning && !isJson()) {
|
|
2967
3480
|
console.warn(chalk.yellow(`Warning: ${duplicate_warning}`));
|
|
2968
3481
|
}
|
|
3482
|
+
if (opts["pin"])
|
|
3483
|
+
pinPrompt(prompt.id, true);
|
|
2969
3484
|
if (isJson()) {
|
|
2970
|
-
output(prompt);
|
|
3485
|
+
output(opts["pin"] ? { ...prompt, pinned: true } : prompt);
|
|
2971
3486
|
} else {
|
|
2972
3487
|
const action = created ? chalk.green("Created") : chalk.yellow("Updated");
|
|
2973
3488
|
console.log(`${action} ${chalk.bold(prompt.id)} \u2014 ${chalk.green(prompt.slug)}`);
|
|
2974
3489
|
console.log(chalk.gray(` Title: ${prompt.title}`));
|
|
2975
3490
|
console.log(chalk.gray(` Collection: ${prompt.collection}`));
|
|
3491
|
+
if (opts["pin"])
|
|
3492
|
+
console.log(chalk.yellow(" \uD83D\uDCCC Pinned"));
|
|
2976
3493
|
if (prompt.is_template) {
|
|
2977
3494
|
const vars = extractVariableInfo(prompt.body);
|
|
2978
3495
|
console.log(chalk.cyan(` Template vars: ${vars.map((v) => v.name).join(", ")}`));
|
|
@@ -2982,13 +3499,33 @@ program2.command("save <title>").description("Save a new prompt (or update exist
|
|
|
2982
3499
|
handleError(e);
|
|
2983
3500
|
}
|
|
2984
3501
|
});
|
|
2985
|
-
program2.command("use <id>").description("Get a prompt's body and increment its use counter").action((id) => {
|
|
3502
|
+
program2.command("use <id>").description("Get a prompt's body and increment its use counter").option("--edit", "Open in $EDITOR for quick tweaks before printing").action(async (id, opts) => {
|
|
2986
3503
|
try {
|
|
2987
3504
|
const prompt = usePrompt(id);
|
|
3505
|
+
let body = prompt.body;
|
|
3506
|
+
if (opts.edit) {
|
|
3507
|
+
const editor = process.env["EDITOR"] ?? process.env["VISUAL"] ?? "nano";
|
|
3508
|
+
const { writeFileSync, readFileSync, unlinkSync } = await import("fs");
|
|
3509
|
+
const { tmpdir } = await import("os");
|
|
3510
|
+
const { join: join2 } = await import("path");
|
|
3511
|
+
const tmp = join2(tmpdir(), `prompts-${prompt.id}-${Date.now()}.md`);
|
|
3512
|
+
writeFileSync(tmp, body);
|
|
3513
|
+
const proc = Bun.spawnSync([editor, tmp], { stdio: ["inherit", "inherit", "inherit"] });
|
|
3514
|
+
if (proc.exitCode === 0) {
|
|
3515
|
+
body = readFileSync(tmp, "utf-8");
|
|
3516
|
+
}
|
|
3517
|
+
try {
|
|
3518
|
+
unlinkSync(tmp);
|
|
3519
|
+
} catch {}
|
|
3520
|
+
}
|
|
2988
3521
|
if (isJson()) {
|
|
2989
|
-
output(prompt);
|
|
3522
|
+
output({ ...prompt, body });
|
|
2990
3523
|
} else {
|
|
2991
|
-
console.log(
|
|
3524
|
+
console.log(body);
|
|
3525
|
+
if (prompt.next_prompt) {
|
|
3526
|
+
console.error(chalk.gray(`
|
|
3527
|
+
\u2192 next: ${chalk.bold(prompt.next_prompt)}`));
|
|
3528
|
+
}
|
|
2992
3529
|
}
|
|
2993
3530
|
} catch (e) {
|
|
2994
3531
|
handleError(e);
|
|
@@ -3354,18 +3891,29 @@ program2.command("stale [days]").description("List prompts not used in N days (d
|
|
|
3354
3891
|
const cutoff = new Date(Date.now() - threshold * 24 * 60 * 60 * 1000).toISOString();
|
|
3355
3892
|
const all = listPrompts({ limit: 1e4 });
|
|
3356
3893
|
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 ?? ""));
|
|
3894
|
+
const now = new Date().toISOString();
|
|
3895
|
+
const expired = all.filter((p) => p.expires_at !== null && p.expires_at < now);
|
|
3357
3896
|
if (isJson()) {
|
|
3358
|
-
output(stale);
|
|
3897
|
+
output({ stale, expired });
|
|
3359
3898
|
return;
|
|
3360
3899
|
}
|
|
3361
|
-
if (
|
|
3900
|
+
if (expired.length > 0) {
|
|
3901
|
+
console.log(chalk.red(`
|
|
3902
|
+
Expired (${expired.length}):`));
|
|
3903
|
+
for (const p of expired)
|
|
3904
|
+
console.log(chalk.red(` \u2717 ${p.slug}`) + chalk.gray(` expired ${new Date(p.expires_at).toLocaleDateString()}`));
|
|
3905
|
+
}
|
|
3906
|
+
if (stale.length === 0 && expired.length === 0) {
|
|
3362
3907
|
console.log(chalk.green(`No stale prompts (threshold: ${threshold} days).`));
|
|
3363
3908
|
return;
|
|
3364
3909
|
}
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3910
|
+
if (stale.length > 0) {
|
|
3911
|
+
console.log(chalk.bold(`
|
|
3912
|
+
Stale prompts (not used in ${threshold}+ days):`));
|
|
3913
|
+
for (const p of stale) {
|
|
3914
|
+
const last = p.last_used_at ? chalk.gray(new Date(p.last_used_at).toLocaleDateString()) : chalk.red("never");
|
|
3915
|
+
console.log(` ${chalk.green(p.slug)} ${chalk.gray(`${p.use_count}\xD7`)} last used: ${last}`);
|
|
3916
|
+
}
|
|
3369
3917
|
}
|
|
3370
3918
|
console.log(chalk.gray(`
|
|
3371
3919
|
${stale.length} stale prompt(s)`));
|
|
@@ -3472,6 +4020,31 @@ projectCmd.command("get <id>").description("Get project details").action((id) =>
|
|
|
3472
4020
|
handleError(e);
|
|
3473
4021
|
}
|
|
3474
4022
|
});
|
|
4023
|
+
projectCmd.command("prompts <id>").description("List all prompts for a project (project-scoped + globals)").option("-n, --limit <n>", "Max results", "100").action((id, opts) => {
|
|
4024
|
+
try {
|
|
4025
|
+
const project = getProject(id);
|
|
4026
|
+
if (!project)
|
|
4027
|
+
handleError(`Project not found: ${id}`);
|
|
4028
|
+
const prompts = listPrompts({ project_id: project.id, limit: parseInt(opts["limit"] ?? "100") || 100 });
|
|
4029
|
+
if (isJson()) {
|
|
4030
|
+
output(prompts);
|
|
4031
|
+
return;
|
|
4032
|
+
}
|
|
4033
|
+
if (prompts.length === 0) {
|
|
4034
|
+
console.log(chalk.gray("No prompts."));
|
|
4035
|
+
return;
|
|
4036
|
+
}
|
|
4037
|
+
console.log(chalk.bold(`Prompts for project: ${project.name}`));
|
|
4038
|
+
for (const p of prompts) {
|
|
4039
|
+
const scope = p.project_id ? chalk.cyan(" [project]") : chalk.gray(" [global]");
|
|
4040
|
+
console.log(fmtPrompt(p) + scope);
|
|
4041
|
+
}
|
|
4042
|
+
console.log(chalk.gray(`
|
|
4043
|
+
${prompts.length} prompt(s)`));
|
|
4044
|
+
} catch (e) {
|
|
4045
|
+
handleError(e);
|
|
4046
|
+
}
|
|
4047
|
+
});
|
|
3475
4048
|
projectCmd.command("delete <id>").description("Delete a project (prompts become global)").option("-y, --yes", "Skip confirmation").action(async (id, opts) => {
|
|
3476
4049
|
try {
|
|
3477
4050
|
const project = getProject(id);
|
|
@@ -3500,4 +4073,245 @@ projectCmd.command("delete <id>").description("Delete a project (prompts become
|
|
|
3500
4073
|
handleError(e);
|
|
3501
4074
|
}
|
|
3502
4075
|
});
|
|
4076
|
+
program2.command("audit").description("Check for orphaned project refs, empty collections, missing history, near-duplicate slugs, expired prompts").action(() => {
|
|
4077
|
+
try {
|
|
4078
|
+
const report = runAudit();
|
|
4079
|
+
if (isJson()) {
|
|
4080
|
+
output(report);
|
|
4081
|
+
return;
|
|
4082
|
+
}
|
|
4083
|
+
if (report.issues.length === 0) {
|
|
4084
|
+
console.log(chalk.green("\u2713 No audit issues found."));
|
|
4085
|
+
return;
|
|
4086
|
+
}
|
|
4087
|
+
for (const issue of report.issues) {
|
|
4088
|
+
const sym = issue.severity === "error" ? chalk.red("\u2717") : issue.severity === "warn" ? chalk.yellow("\u26A0") : chalk.gray("\u2139");
|
|
4089
|
+
const slug = issue.slug ? chalk.green(` ${issue.slug}`) : "";
|
|
4090
|
+
console.log(`${sym}${slug} ${issue.message}`);
|
|
4091
|
+
}
|
|
4092
|
+
console.log(chalk.bold(`
|
|
4093
|
+
${report.issues.length} issue(s) \u2014 ${report.errors} errors, ${report.warnings} warnings, ${report.info} info`));
|
|
4094
|
+
if (report.errors > 0)
|
|
4095
|
+
process.exit(1);
|
|
4096
|
+
} catch (e) {
|
|
4097
|
+
handleError(e);
|
|
4098
|
+
}
|
|
4099
|
+
});
|
|
4100
|
+
program2.command("unused").description("List prompts that have never been used (use_count = 0)").option("-c, --collection <name>").option("-n, --limit <n>", "Max results", "50").action((opts) => {
|
|
4101
|
+
try {
|
|
4102
|
+
const all = listPrompts({ collection: opts["collection"], limit: parseInt(opts["limit"] ?? "50") || 50 });
|
|
4103
|
+
const unused = all.filter((p) => p.use_count === 0).sort((a, b) => a.created_at.localeCompare(b.created_at));
|
|
4104
|
+
if (isJson()) {
|
|
4105
|
+
output(unused);
|
|
4106
|
+
return;
|
|
4107
|
+
}
|
|
4108
|
+
if (unused.length === 0) {
|
|
4109
|
+
console.log(chalk.green("All prompts have been used at least once."));
|
|
4110
|
+
return;
|
|
4111
|
+
}
|
|
4112
|
+
console.log(chalk.bold(`Unused prompts (${unused.length}):`));
|
|
4113
|
+
for (const p of unused) {
|
|
4114
|
+
console.log(` ${fmtPrompt(p)} ${chalk.gray(`created ${new Date(p.created_at).toLocaleDateString()}`)}`);
|
|
4115
|
+
}
|
|
4116
|
+
} catch (e) {
|
|
4117
|
+
handleError(e);
|
|
4118
|
+
}
|
|
4119
|
+
});
|
|
4120
|
+
program2.command("trending").description("Most used prompts in the last N days").option("--days <n>", "Lookback window in days", "7").option("-n, --limit <n>", "Max results", "10").action((opts) => {
|
|
4121
|
+
try {
|
|
4122
|
+
const results = getTrending(parseInt(opts["days"] ?? "7") || 7, parseInt(opts["limit"] ?? "10") || 10);
|
|
4123
|
+
if (isJson()) {
|
|
4124
|
+
output(results);
|
|
4125
|
+
return;
|
|
4126
|
+
}
|
|
4127
|
+
if (results.length === 0) {
|
|
4128
|
+
console.log(chalk.gray("No usage data yet."));
|
|
4129
|
+
return;
|
|
4130
|
+
}
|
|
4131
|
+
console.log(chalk.bold(`Trending (last ${opts["days"] ?? "7"} days):`));
|
|
4132
|
+
for (const r of results) {
|
|
4133
|
+
console.log(` ${chalk.green(r.slug)} ${chalk.bold(String(r.uses))}\xD7 ${chalk.gray(r.title)}`);
|
|
4134
|
+
}
|
|
4135
|
+
} catch (e) {
|
|
4136
|
+
handleError(e);
|
|
4137
|
+
}
|
|
4138
|
+
});
|
|
4139
|
+
program2.command("expire <id> [date]").description("Set expiry date for a prompt (ISO date, e.g. 2026-12-31). Use 'none' to clear.").action((id, date) => {
|
|
4140
|
+
try {
|
|
4141
|
+
const expiresAt = !date || date === "none" ? null : new Date(date).toISOString();
|
|
4142
|
+
const p = setExpiry(id, expiresAt);
|
|
4143
|
+
if (isJson())
|
|
4144
|
+
output(p);
|
|
4145
|
+
else
|
|
4146
|
+
console.log(expiresAt ? chalk.yellow(`Expires ${p.slug} on ${new Date(expiresAt).toLocaleDateString()}`) : chalk.gray(`Cleared expiry for ${p.slug}`));
|
|
4147
|
+
} catch (e) {
|
|
4148
|
+
handleError(e);
|
|
4149
|
+
}
|
|
4150
|
+
});
|
|
4151
|
+
program2.command("duplicate <id>").description("Clone a prompt with a new slug").option("-s, --to <slug>", "New slug (auto-generated if omitted)").option("--title <title>", "New title (defaults to 'Copy of <original>')").action((id, opts) => {
|
|
4152
|
+
try {
|
|
4153
|
+
const source = getPrompt(id);
|
|
4154
|
+
if (!source)
|
|
4155
|
+
handleError(`Prompt not found: ${id}`);
|
|
4156
|
+
const p = source;
|
|
4157
|
+
const { prompt } = upsertPrompt({
|
|
4158
|
+
title: opts["title"] ?? `Copy of ${p.title}`,
|
|
4159
|
+
slug: opts["to"],
|
|
4160
|
+
body: p.body,
|
|
4161
|
+
description: p.description ?? undefined,
|
|
4162
|
+
collection: p.collection,
|
|
4163
|
+
tags: p.tags,
|
|
4164
|
+
source: "manual"
|
|
4165
|
+
});
|
|
4166
|
+
if (isJson())
|
|
4167
|
+
output(prompt);
|
|
4168
|
+
else
|
|
4169
|
+
console.log(`${chalk.green("Duplicated")} ${chalk.bold(p.slug)} \u2192 ${chalk.bold(prompt.slug)}`);
|
|
4170
|
+
} catch (e) {
|
|
4171
|
+
handleError(e);
|
|
4172
|
+
}
|
|
4173
|
+
});
|
|
4174
|
+
program2.command("diff <id> <v1> [v2]").description("Show diff between two versions of a prompt (v2 defaults to current)").action((id, v1, v2) => {
|
|
4175
|
+
try {
|
|
4176
|
+
const prompt = getPrompt(id);
|
|
4177
|
+
if (!prompt)
|
|
4178
|
+
handleError(`Prompt not found: ${id}`);
|
|
4179
|
+
const versions = listVersions(prompt.id);
|
|
4180
|
+
const versionA = versions.find((v) => v.version === parseInt(v1));
|
|
4181
|
+
if (!versionA)
|
|
4182
|
+
handleError(`Version ${v1} not found`);
|
|
4183
|
+
const bodyB = v2 ? versions.find((v) => v.version === parseInt(v2))?.body ?? null : prompt.body;
|
|
4184
|
+
if (bodyB === null)
|
|
4185
|
+
handleError(`Version ${v2} not found`);
|
|
4186
|
+
const lines = diffTexts(versionA.body, bodyB);
|
|
4187
|
+
if (isJson()) {
|
|
4188
|
+
output(lines);
|
|
4189
|
+
return;
|
|
4190
|
+
}
|
|
4191
|
+
const label2 = v2 ? `v${v2}` : "current";
|
|
4192
|
+
console.log(chalk.bold(`${prompt.slug}: v${v1} \u2192 ${label2}`));
|
|
4193
|
+
for (const l of lines) {
|
|
4194
|
+
if (l.type === "added")
|
|
4195
|
+
console.log(chalk.green(`+ ${l.content}`));
|
|
4196
|
+
else if (l.type === "removed")
|
|
4197
|
+
console.log(chalk.red(`- ${l.content}`));
|
|
4198
|
+
else
|
|
4199
|
+
console.log(chalk.gray(` ${l.content}`));
|
|
4200
|
+
}
|
|
4201
|
+
} catch (e) {
|
|
4202
|
+
handleError(e);
|
|
4203
|
+
}
|
|
4204
|
+
});
|
|
4205
|
+
program2.command("chain <id> [next]").description("Set the next prompt in a chain, or show the chain for a prompt. Use 'none' to clear.").action((id, next) => {
|
|
4206
|
+
try {
|
|
4207
|
+
if (next !== undefined) {
|
|
4208
|
+
const nextSlug = next === "none" ? null : next;
|
|
4209
|
+
const p = setNextPrompt(id, nextSlug);
|
|
4210
|
+
if (isJson())
|
|
4211
|
+
output(p);
|
|
4212
|
+
else
|
|
4213
|
+
console.log(nextSlug ? `${chalk.green(p.slug)} \u2192 ${chalk.bold(nextSlug)}` : chalk.gray(`Cleared chain for ${p.slug}`));
|
|
4214
|
+
return;
|
|
4215
|
+
}
|
|
4216
|
+
const prompt = getPrompt(id);
|
|
4217
|
+
if (!prompt)
|
|
4218
|
+
handleError(`Prompt not found: ${id}`);
|
|
4219
|
+
const chain = [];
|
|
4220
|
+
let cur = prompt;
|
|
4221
|
+
const seen = new Set;
|
|
4222
|
+
while (cur && !seen.has(cur.id)) {
|
|
4223
|
+
chain.push({ slug: cur.slug, title: cur.title });
|
|
4224
|
+
seen.add(cur.id);
|
|
4225
|
+
cur = cur.next_prompt ? getPrompt(cur.next_prompt) : null;
|
|
4226
|
+
}
|
|
4227
|
+
if (isJson()) {
|
|
4228
|
+
output(chain);
|
|
4229
|
+
return;
|
|
4230
|
+
}
|
|
4231
|
+
console.log(chain.map((c) => chalk.green(c.slug)).join(chalk.gray(" \u2192 ")));
|
|
4232
|
+
} catch (e) {
|
|
4233
|
+
handleError(e);
|
|
4234
|
+
}
|
|
4235
|
+
});
|
|
4236
|
+
program2.command("similar <id>").description("Find prompts similar to a given prompt (by tag overlap and collection)").option("-n, --limit <n>", "Max results", "5").action((id, opts) => {
|
|
4237
|
+
try {
|
|
4238
|
+
const prompt = getPrompt(id);
|
|
4239
|
+
if (!prompt)
|
|
4240
|
+
handleError(`Prompt not found: ${id}`);
|
|
4241
|
+
const results = findSimilar(prompt.id, parseInt(opts["limit"] ?? "5") || 5);
|
|
4242
|
+
if (isJson()) {
|
|
4243
|
+
output(results);
|
|
4244
|
+
return;
|
|
4245
|
+
}
|
|
4246
|
+
if (results.length === 0) {
|
|
4247
|
+
console.log(chalk.gray("No similar prompts found."));
|
|
4248
|
+
return;
|
|
4249
|
+
}
|
|
4250
|
+
for (const r of results) {
|
|
4251
|
+
const score = chalk.gray(`${Math.round(r.score * 100)}%`);
|
|
4252
|
+
console.log(`${fmtPrompt(r.prompt)} ${score}`);
|
|
4253
|
+
}
|
|
4254
|
+
} catch (e) {
|
|
4255
|
+
handleError(e);
|
|
4256
|
+
}
|
|
4257
|
+
});
|
|
4258
|
+
program2.command("completion [shell]").description("Output shell completion script (zsh or bash)").action((shell) => {
|
|
4259
|
+
const sh = shell ?? (process.env["SHELL"]?.includes("zsh") ? "zsh" : "bash");
|
|
4260
|
+
if (sh === "zsh") {
|
|
4261
|
+
console.log(generateZshCompletion());
|
|
4262
|
+
} else if (sh === "bash") {
|
|
4263
|
+
console.log(generateBashCompletion());
|
|
4264
|
+
} else {
|
|
4265
|
+
handleError(`Unknown shell: ${sh}. Use 'zsh' or 'bash'.`);
|
|
4266
|
+
}
|
|
4267
|
+
});
|
|
4268
|
+
program2.command("watch [dir]").description("Watch a directory for .md changes and auto-import prompts (default: .prompts/)").option("-c, --collection <name>", "Collection to import into", "watched").option("--agent <name>", "Attribution").action(async (dir, opts) => {
|
|
4269
|
+
const { existsSync: existsSync2, mkdirSync: mkdirSync2 } = await import("fs");
|
|
4270
|
+
const { resolve, join: join2 } = await import("path");
|
|
4271
|
+
const watchDir = resolve(dir ?? join2(process.cwd(), ".prompts"));
|
|
4272
|
+
if (!existsSync2(watchDir))
|
|
4273
|
+
mkdirSync2(watchDir, { recursive: true });
|
|
4274
|
+
console.log(chalk.bold(`Watching ${watchDir} for .md changes\u2026`) + chalk.gray(" (Ctrl+C to stop)"));
|
|
4275
|
+
const { importFromMarkdown: importFromMarkdown2 } = await Promise.resolve().then(() => (init_importer(), exports_importer));
|
|
4276
|
+
const { readFileSync } = await import("fs");
|
|
4277
|
+
const fsWatch = (await import("fs")).watch;
|
|
4278
|
+
fsWatch(watchDir, { persistent: true }, async (_event, filename) => {
|
|
4279
|
+
if (!filename?.endsWith(".md"))
|
|
4280
|
+
return;
|
|
4281
|
+
const filePath = join2(watchDir, filename);
|
|
4282
|
+
try {
|
|
4283
|
+
const content = readFileSync(filePath, "utf-8");
|
|
4284
|
+
const result = importFromMarkdown2([{ filename, content }], opts["agent"]);
|
|
4285
|
+
const action = result.created > 0 ? chalk.green("Created") : chalk.yellow("Updated");
|
|
4286
|
+
console.log(`${action}: ${chalk.bold(filename.replace(".md", ""))} ${chalk.gray(new Date().toLocaleTimeString())}`);
|
|
4287
|
+
} catch {
|
|
4288
|
+
console.error(chalk.red(`Failed to import: ${filename}`));
|
|
4289
|
+
}
|
|
4290
|
+
});
|
|
4291
|
+
await new Promise(() => {});
|
|
4292
|
+
});
|
|
4293
|
+
program2.command("import-slash-commands").description("Auto-scan .claude/commands, .codex/skills, .gemini/extensions and import all prompts").option("--dir <path>", "Root dir to scan (default: cwd)", process.cwd()).option("--agent <name>", "Attribution").action((opts) => {
|
|
4294
|
+
try {
|
|
4295
|
+
const { scanned, imported } = scanAndImportSlashCommands(opts["dir"] ?? process.cwd(), opts["agent"]);
|
|
4296
|
+
if (isJson()) {
|
|
4297
|
+
output({ scanned, imported });
|
|
4298
|
+
return;
|
|
4299
|
+
}
|
|
4300
|
+
if (scanned.length === 0) {
|
|
4301
|
+
console.log(chalk.gray("No slash command files found."));
|
|
4302
|
+
return;
|
|
4303
|
+
}
|
|
4304
|
+
console.log(chalk.bold(`Scanned ${scanned.length} file(s):`));
|
|
4305
|
+
for (const s of scanned)
|
|
4306
|
+
console.log(chalk.gray(` ${s.source}/${s.file}`));
|
|
4307
|
+
console.log(`
|
|
4308
|
+
${chalk.green(`Created: ${imported.created}`)} ${chalk.yellow(`Updated: ${imported.updated}`)}`);
|
|
4309
|
+
if (imported.errors.length > 0) {
|
|
4310
|
+
for (const e of imported.errors)
|
|
4311
|
+
console.error(chalk.red(` \u2717 ${e.item}: ${e.error}`));
|
|
4312
|
+
}
|
|
4313
|
+
} catch (e) {
|
|
4314
|
+
handleError(e);
|
|
4315
|
+
}
|
|
4316
|
+
});
|
|
3503
4317
|
program2.parse();
|