@hasna/prompts 0.3.15 → 0.3.16

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 CHANGED
@@ -11932,12 +11932,73 @@ function renderTemplate(body, vars) {
11932
11932
  });
11933
11933
  return { rendered, missing_vars: missing, used_defaults: usedDefaults };
11934
11934
  }
11935
+ function validateVars(body, provided) {
11936
+ const infos = extractVariableInfo(body);
11937
+ const required = infos.filter((v) => v.required).map((v) => v.name);
11938
+ const optional = infos.filter((v) => !v.required).map((v) => v.name);
11939
+ const all = infos.map((v) => v.name);
11940
+ const missing = required.filter((v) => !(v in provided));
11941
+ const extra = Object.keys(provided).filter((v) => !all.includes(v));
11942
+ return { missing, extra, optional };
11943
+ }
11935
11944
  var VAR_PATTERN;
11936
11945
  var init_template = __esm(() => {
11937
11946
  VAR_PATTERN = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:\|\s*(.*?)\s*)?\}\}/g;
11938
11947
  });
11939
11948
 
11940
11949
  // src/db/prompts.ts
11950
+ var exports_prompts = {};
11951
+ __export(exports_prompts, {
11952
+ usePrompt: () => usePrompt,
11953
+ upsertPrompt: () => upsertPrompt,
11954
+ updatePrompt: () => updatePrompt,
11955
+ setNextPrompt: () => setNextPrompt,
11956
+ setExpiry: () => setExpiry,
11957
+ requirePrompt: () => requirePrompt,
11958
+ promptToSaveResult: () => promptToSaveResult,
11959
+ pinPrompt: () => pinPrompt,
11960
+ listPromptsSlim: () => listPromptsSlim,
11961
+ listPrompts: () => listPrompts,
11962
+ getTrending: () => getTrending,
11963
+ getPromptStats: () => getPromptStats,
11964
+ getPrompt: () => getPrompt,
11965
+ deletePrompt: () => deletePrompt,
11966
+ createPrompt: () => createPrompt
11967
+ });
11968
+ function rowToSlimPrompt(row) {
11969
+ const variables = JSON.parse(row["variables"] || "[]");
11970
+ return {
11971
+ id: row["id"],
11972
+ slug: row["slug"],
11973
+ title: row["title"],
11974
+ description: row["description"] ?? null,
11975
+ collection: row["collection"],
11976
+ tags: JSON.parse(row["tags"] || "[]"),
11977
+ variable_names: variables.map((v) => v.name),
11978
+ is_template: Boolean(row["is_template"]),
11979
+ source: row["source"],
11980
+ pinned: Boolean(row["pinned"]),
11981
+ next_prompt: row["next_prompt"] ?? null,
11982
+ expires_at: row["expires_at"] ?? null,
11983
+ project_id: row["project_id"] ?? null,
11984
+ use_count: row["use_count"],
11985
+ last_used_at: row["last_used_at"] ?? null,
11986
+ created_at: row["created_at"],
11987
+ updated_at: row["updated_at"]
11988
+ };
11989
+ }
11990
+ function promptToSaveResult(prompt, created, duplicate_warning) {
11991
+ return {
11992
+ id: prompt.id,
11993
+ slug: prompt.slug,
11994
+ title: prompt.title,
11995
+ collection: prompt.collection,
11996
+ is_template: prompt.is_template,
11997
+ variable_names: prompt.variables.map((v) => v.name),
11998
+ created,
11999
+ duplicate_warning: duplicate_warning ?? null
12000
+ };
12001
+ }
11941
12002
  function rowToPrompt(row) {
11942
12003
  return {
11943
12004
  id: row["id"],
@@ -12037,6 +12098,40 @@ function listPrompts(filter = {}) {
12037
12098
  const rows = db.query(`SELECT * FROM prompts ${where} ORDER BY ${orderBy} LIMIT ? OFFSET ?`).all(...params, limit, offset);
12038
12099
  return rows.map(rowToPrompt);
12039
12100
  }
12101
+ function listPromptsSlim(filter = {}) {
12102
+ const db = getDatabase();
12103
+ const conditions = [];
12104
+ const params = [];
12105
+ if (filter.collection) {
12106
+ conditions.push("collection = ?");
12107
+ params.push(filter.collection);
12108
+ }
12109
+ if (filter.is_template !== undefined) {
12110
+ conditions.push("is_template = ?");
12111
+ params.push(filter.is_template ? 1 : 0);
12112
+ }
12113
+ if (filter.source) {
12114
+ conditions.push("source = ?");
12115
+ params.push(filter.source);
12116
+ }
12117
+ if (filter.tags && filter.tags.length > 0) {
12118
+ const tagConds = filter.tags.map(() => "tags LIKE ?");
12119
+ conditions.push(`(${tagConds.join(" OR ")})`);
12120
+ for (const tag of filter.tags)
12121
+ params.push(`%"${tag}"%`);
12122
+ }
12123
+ let orderBy = "pinned DESC, use_count DESC, updated_at DESC";
12124
+ if (filter.project_id) {
12125
+ conditions.push("(project_id = ? OR project_id IS NULL)");
12126
+ params.push(filter.project_id);
12127
+ orderBy = `(CASE WHEN project_id = '${filter.project_id}' THEN 0 ELSE 1 END), pinned DESC, use_count DESC, updated_at DESC`;
12128
+ }
12129
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
12130
+ const limit = filter.limit ?? 20;
12131
+ const offset = filter.offset ?? 0;
12132
+ const rows = db.query(`SELECT id, slug, name, title, description, collection, tags, variables, is_template, source, pinned, next_prompt, expires_at, project_id, use_count, last_used_at, created_at, updated_at FROM prompts ${where} ORDER BY ${orderBy} LIMIT ? OFFSET ?`).all(...params, limit, offset);
12133
+ return rows.map(rowToSlimPrompt);
12134
+ }
12040
12135
  function updatePrompt(idOrSlug, input) {
12041
12136
  const db = getDatabase();
12042
12137
  const prompt = requirePrompt(idOrSlug);
@@ -12321,6 +12416,205 @@ var init_importer = __esm(() => {
12321
12416
  init_prompts();
12322
12417
  });
12323
12418
 
12419
+ // src/lib/search.ts
12420
+ var exports_search = {};
12421
+ __export(exports_search, {
12422
+ searchPromptsSlim: () => searchPromptsSlim,
12423
+ searchPrompts: () => searchPrompts,
12424
+ findSimilar: () => findSimilar
12425
+ });
12426
+ function rowToSlimSearchResult(row, snippet) {
12427
+ const variables = JSON.parse(row["variables"] || "[]");
12428
+ return {
12429
+ id: row["id"],
12430
+ slug: row["slug"],
12431
+ title: row["title"],
12432
+ description: row["description"] ?? null,
12433
+ collection: row["collection"],
12434
+ tags: JSON.parse(row["tags"] || "[]"),
12435
+ variable_names: variables.map((v) => v.name),
12436
+ is_template: Boolean(row["is_template"]),
12437
+ use_count: row["use_count"],
12438
+ score: row["score"] ?? 1,
12439
+ snippet
12440
+ };
12441
+ }
12442
+ function rowToSearchResult(row, snippet) {
12443
+ return {
12444
+ prompt: {
12445
+ id: row["id"],
12446
+ name: row["name"],
12447
+ slug: row["slug"],
12448
+ title: row["title"],
12449
+ body: row["body"],
12450
+ description: row["description"] ?? null,
12451
+ collection: row["collection"],
12452
+ tags: JSON.parse(row["tags"] || "[]"),
12453
+ variables: JSON.parse(row["variables"] || "[]"),
12454
+ pinned: Boolean(row["pinned"]),
12455
+ next_prompt: row["next_prompt"] ?? null,
12456
+ expires_at: row["expires_at"] ?? null,
12457
+ project_id: row["project_id"] ?? null,
12458
+ is_template: Boolean(row["is_template"]),
12459
+ source: row["source"],
12460
+ version: row["version"],
12461
+ use_count: row["use_count"],
12462
+ last_used_at: row["last_used_at"] ?? null,
12463
+ created_at: row["created_at"],
12464
+ updated_at: row["updated_at"]
12465
+ },
12466
+ score: row["score"] ?? 1,
12467
+ snippet
12468
+ };
12469
+ }
12470
+ function escapeFtsQuery(q) {
12471
+ return q.trim().split(/\s+/).filter(Boolean).map((w) => `"${w.replace(/"/g, '""')}"*`).join(" ");
12472
+ }
12473
+ function searchPrompts(query, filter = {}) {
12474
+ const db = getDatabase();
12475
+ if (!query.trim()) {
12476
+ const prompts = listPrompts(filter);
12477
+ return prompts.map((p) => ({ prompt: p, score: 1 }));
12478
+ }
12479
+ if (hasFts(db)) {
12480
+ const ftsQuery = escapeFtsQuery(query);
12481
+ const conditions = [];
12482
+ const params = [];
12483
+ if (filter.collection) {
12484
+ conditions.push("p.collection = ?");
12485
+ params.push(filter.collection);
12486
+ }
12487
+ if (filter.is_template !== undefined) {
12488
+ conditions.push("p.is_template = ?");
12489
+ params.push(filter.is_template ? 1 : 0);
12490
+ }
12491
+ if (filter.source) {
12492
+ conditions.push("p.source = ?");
12493
+ params.push(filter.source);
12494
+ }
12495
+ if (filter.tags && filter.tags.length > 0) {
12496
+ const tagConds = filter.tags.map(() => "p.tags LIKE ?");
12497
+ conditions.push(`(${tagConds.join(" OR ")})`);
12498
+ for (const tag of filter.tags)
12499
+ params.push(`%"${tag}"%`);
12500
+ }
12501
+ if (filter.project_id !== undefined && filter.project_id !== null) {
12502
+ conditions.push("(p.project_id = ? OR p.project_id IS NULL)");
12503
+ params.push(filter.project_id);
12504
+ }
12505
+ const where = conditions.length > 0 ? `AND ${conditions.join(" AND ")}` : "";
12506
+ const limit = filter.limit ?? 50;
12507
+ const offset = filter.offset ?? 0;
12508
+ try {
12509
+ const rows2 = db.query(`SELECT p.*, bm25(prompts_fts) as score,
12510
+ snippet(prompts_fts, 2, '[', ']', '...', 10) as snippet
12511
+ FROM prompts p
12512
+ INNER JOIN prompts_fts ON prompts_fts.rowid = p.rowid
12513
+ WHERE prompts_fts MATCH ?
12514
+ ${where}
12515
+ ORDER BY bm25(prompts_fts)
12516
+ LIMIT ? OFFSET ?`).all(ftsQuery, ...params, limit, offset);
12517
+ return rows2.map((r) => rowToSearchResult(r, r["snippet"]));
12518
+ } catch {}
12519
+ }
12520
+ const like = `%${query}%`;
12521
+ const rows = db.query(`SELECT *, 1 as score FROM prompts
12522
+ WHERE (name LIKE ? OR slug LIKE ? OR title LIKE ? OR body LIKE ? OR description LIKE ? OR tags LIKE ?)
12523
+ ORDER BY use_count DESC, updated_at DESC
12524
+ LIMIT ? OFFSET ?`).all(like, like, like, like, like, like, filter.limit ?? 10, filter.offset ?? 0);
12525
+ return rows.map((r) => rowToSearchResult(r));
12526
+ }
12527
+ function searchPromptsSlim(query, filter = {}) {
12528
+ const db = getDatabase();
12529
+ if (!query.trim()) {
12530
+ return listPromptsSlim(filter).map((p) => ({
12531
+ id: p.id,
12532
+ slug: p.slug,
12533
+ title: p.title,
12534
+ description: p.description,
12535
+ collection: p.collection,
12536
+ tags: p.tags,
12537
+ variable_names: p.variable_names,
12538
+ is_template: p.is_template,
12539
+ use_count: p.use_count,
12540
+ score: 1
12541
+ }));
12542
+ }
12543
+ if (hasFts(db)) {
12544
+ const ftsQuery = escapeFtsQuery(query);
12545
+ const conditions = [];
12546
+ const params = [];
12547
+ if (filter.collection) {
12548
+ conditions.push("p.collection = ?");
12549
+ params.push(filter.collection);
12550
+ }
12551
+ if (filter.is_template !== undefined) {
12552
+ conditions.push("p.is_template = ?");
12553
+ params.push(filter.is_template ? 1 : 0);
12554
+ }
12555
+ if (filter.source) {
12556
+ conditions.push("p.source = ?");
12557
+ params.push(filter.source);
12558
+ }
12559
+ if (filter.tags && filter.tags.length > 0) {
12560
+ const tagConds = filter.tags.map(() => "p.tags LIKE ?");
12561
+ conditions.push(`(${tagConds.join(" OR ")})`);
12562
+ for (const tag of filter.tags)
12563
+ params.push(`%"${tag}"%`);
12564
+ }
12565
+ if (filter.project_id) {
12566
+ conditions.push("(p.project_id = ? OR p.project_id IS NULL)");
12567
+ params.push(filter.project_id);
12568
+ }
12569
+ const where = conditions.length > 0 ? `AND ${conditions.join(" AND ")}` : "";
12570
+ const limit = filter.limit ?? 10;
12571
+ const offset = filter.offset ?? 0;
12572
+ try {
12573
+ const rows2 = db.query(`SELECT p.id, p.slug, p.name, p.title, p.description, p.collection, p.tags, p.variables,
12574
+ p.is_template, p.use_count, bm25(prompts_fts) as score,
12575
+ snippet(prompts_fts, 2, '[', ']', '...', 10) as snippet
12576
+ FROM prompts p
12577
+ INNER JOIN prompts_fts ON prompts_fts.rowid = p.rowid
12578
+ WHERE prompts_fts MATCH ?
12579
+ ${where}
12580
+ ORDER BY bm25(prompts_fts)
12581
+ LIMIT ? OFFSET ?`).all(ftsQuery, ...params, limit, offset);
12582
+ return rows2.map((r) => rowToSlimSearchResult(r, r["snippet"]));
12583
+ } catch {}
12584
+ }
12585
+ const like = `%${query}%`;
12586
+ const rows = db.query(`SELECT id, slug, name, title, description, collection, tags, variables, is_template, use_count, 1 as score
12587
+ FROM prompts
12588
+ WHERE (name LIKE ? OR slug LIKE ? OR title LIKE ? OR body LIKE ? OR description LIKE ? OR tags LIKE ?)
12589
+ ORDER BY use_count DESC, updated_at DESC
12590
+ LIMIT ? OFFSET ?`).all(like, like, like, like, like, like, filter.limit ?? 10, filter.offset ?? 0);
12591
+ return rows.map((r) => rowToSlimSearchResult(r));
12592
+ }
12593
+ function findSimilar(promptId, limit = 5) {
12594
+ const db = getDatabase();
12595
+ const prompt = db.query("SELECT * FROM prompts WHERE id = ?").get(promptId);
12596
+ if (!prompt)
12597
+ return [];
12598
+ const tags = JSON.parse(prompt["tags"] || "[]");
12599
+ const collection = prompt["collection"];
12600
+ if (tags.length === 0) {
12601
+ const rows = db.query("SELECT *, 1 as score FROM prompts WHERE collection = ? AND id != ? ORDER BY use_count DESC LIMIT ?").all(collection, promptId, limit);
12602
+ return rows.map((r) => rowToSearchResult(r));
12603
+ }
12604
+ const allRows = db.query("SELECT * FROM prompts WHERE id != ?").all(promptId);
12605
+ const scored = allRows.map((row) => {
12606
+ const rowTags = JSON.parse(row["tags"] || "[]");
12607
+ const overlap = rowTags.filter((t) => tags.includes(t)).length;
12608
+ const sameCollection = row["collection"] === collection ? 1 : 0;
12609
+ return { row, score: overlap * 2 + sameCollection };
12610
+ });
12611
+ return scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((s) => rowToSearchResult(s.row, undefined));
12612
+ }
12613
+ var init_search = __esm(() => {
12614
+ init_database();
12615
+ init_prompts();
12616
+ });
12617
+
12324
12618
  // node_modules/commander/esm.mjs
12325
12619
  var import__ = __toESM(require_commander(), 1);
12326
12620
  var {
@@ -12339,7 +12633,7 @@ var {
12339
12633
 
12340
12634
  // src/cli/index.tsx
12341
12635
  init_collections();
12342
- import chalk4 from "chalk";
12636
+ import chalk6 from "chalk";
12343
12637
  import { createRequire as createRequire2 } from "module";
12344
12638
 
12345
12639
  // src/db/projects.ts
@@ -12848,119 +13142,9 @@ function fmtPrompt(p) {
12848
13142
 
12849
13143
  // src/cli/commands/prompts.tsx
12850
13144
  init_prompts();
13145
+ init_search();
13146
+ init_template();
12851
13147
  import chalk2 from "chalk";
12852
-
12853
- // src/lib/search.ts
12854
- init_database();
12855
- init_prompts();
12856
- function rowToSearchResult(row, snippet) {
12857
- return {
12858
- prompt: {
12859
- id: row["id"],
12860
- name: row["name"],
12861
- slug: row["slug"],
12862
- title: row["title"],
12863
- body: row["body"],
12864
- description: row["description"] ?? null,
12865
- collection: row["collection"],
12866
- tags: JSON.parse(row["tags"] || "[]"),
12867
- variables: JSON.parse(row["variables"] || "[]"),
12868
- pinned: Boolean(row["pinned"]),
12869
- next_prompt: row["next_prompt"] ?? null,
12870
- expires_at: row["expires_at"] ?? null,
12871
- project_id: row["project_id"] ?? null,
12872
- is_template: Boolean(row["is_template"]),
12873
- source: row["source"],
12874
- version: row["version"],
12875
- use_count: row["use_count"],
12876
- last_used_at: row["last_used_at"] ?? null,
12877
- created_at: row["created_at"],
12878
- updated_at: row["updated_at"]
12879
- },
12880
- score: row["score"] ?? 1,
12881
- snippet
12882
- };
12883
- }
12884
- function escapeFtsQuery(q) {
12885
- return q.trim().split(/\s+/).filter(Boolean).map((w) => `"${w.replace(/"/g, '""')}"*`).join(" ");
12886
- }
12887
- function searchPrompts(query, filter = {}) {
12888
- const db = getDatabase();
12889
- if (!query.trim()) {
12890
- const prompts = listPrompts(filter);
12891
- return prompts.map((p) => ({ prompt: p, score: 1 }));
12892
- }
12893
- if (hasFts(db)) {
12894
- const ftsQuery = escapeFtsQuery(query);
12895
- const conditions = [];
12896
- const params = [];
12897
- if (filter.collection) {
12898
- conditions.push("p.collection = ?");
12899
- params.push(filter.collection);
12900
- }
12901
- if (filter.is_template !== undefined) {
12902
- conditions.push("p.is_template = ?");
12903
- params.push(filter.is_template ? 1 : 0);
12904
- }
12905
- if (filter.source) {
12906
- conditions.push("p.source = ?");
12907
- params.push(filter.source);
12908
- }
12909
- if (filter.tags && filter.tags.length > 0) {
12910
- const tagConds = filter.tags.map(() => "p.tags LIKE ?");
12911
- conditions.push(`(${tagConds.join(" OR ")})`);
12912
- for (const tag of filter.tags)
12913
- params.push(`%"${tag}"%`);
12914
- }
12915
- if (filter.project_id !== undefined && filter.project_id !== null) {
12916
- conditions.push("(p.project_id = ? OR p.project_id IS NULL)");
12917
- params.push(filter.project_id);
12918
- }
12919
- const where = conditions.length > 0 ? `AND ${conditions.join(" AND ")}` : "";
12920
- const limit = filter.limit ?? 50;
12921
- const offset = filter.offset ?? 0;
12922
- try {
12923
- const rows2 = db.query(`SELECT p.*, bm25(prompts_fts) as score,
12924
- snippet(prompts_fts, 2, '[', ']', '...', 10) as snippet
12925
- FROM prompts p
12926
- INNER JOIN prompts_fts ON prompts_fts.rowid = p.rowid
12927
- WHERE prompts_fts MATCH ?
12928
- ${where}
12929
- ORDER BY bm25(prompts_fts)
12930
- LIMIT ? OFFSET ?`).all(ftsQuery, ...params, limit, offset);
12931
- return rows2.map((r) => rowToSearchResult(r, r["snippet"]));
12932
- } catch {}
12933
- }
12934
- const like = `%${query}%`;
12935
- const rows = db.query(`SELECT *, 1 as score FROM prompts
12936
- WHERE (name LIKE ? OR slug LIKE ? OR title LIKE ? OR body LIKE ? OR description LIKE ? OR tags LIKE ?)
12937
- ORDER BY use_count DESC, updated_at DESC
12938
- LIMIT ? OFFSET ?`).all(like, like, like, like, like, like, filter.limit ?? 10, filter.offset ?? 0);
12939
- return rows.map((r) => rowToSearchResult(r));
12940
- }
12941
- function findSimilar(promptId, limit = 5) {
12942
- const db = getDatabase();
12943
- const prompt = db.query("SELECT * FROM prompts WHERE id = ?").get(promptId);
12944
- if (!prompt)
12945
- return [];
12946
- const tags = JSON.parse(prompt["tags"] || "[]");
12947
- const collection = prompt["collection"];
12948
- if (tags.length === 0) {
12949
- const rows = db.query("SELECT *, 1 as score FROM prompts WHERE collection = ? AND id != ? ORDER BY use_count DESC LIMIT ?").all(collection, promptId, limit);
12950
- return rows.map((r) => rowToSearchResult(r));
12951
- }
12952
- const allRows = db.query("SELECT * FROM prompts WHERE id != ?").all(promptId);
12953
- const scored = allRows.map((row) => {
12954
- const rowTags = JSON.parse(row["tags"] || "[]");
12955
- const overlap = rowTags.filter((t) => tags.includes(t)).length;
12956
- const sameCollection = row["collection"] === collection ? 1 : 0;
12957
- return { row, score: overlap * 2 + sameCollection };
12958
- });
12959
- return scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((s) => rowToSearchResult(s.row, undefined));
12960
- }
12961
-
12962
- // src/cli/commands/prompts.tsx
12963
- init_template();
12964
13148
  function registerPromptCommands(program2) {
12965
13149
  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) => {
12966
13150
  try {
@@ -13390,12 +13574,784 @@ function registerVersionCommands(program2) {
13390
13574
  });
13391
13575
  }
13392
13576
 
13577
+ // src/cli/commands/qol.ts
13578
+ init_prompts();
13579
+ init_collections();
13580
+ init_search();
13581
+ init_template();
13582
+ import chalk4 from "chalk";
13583
+ function registerQolCommands(program2) {
13584
+ program2.command("body <id>").description("Print prompt body without incrementing use counter (pipe-friendly)").action((id) => {
13585
+ try {
13586
+ const prompt = getPrompt(id);
13587
+ if (!prompt)
13588
+ handleError(program2, `Prompt not found: ${id}`);
13589
+ if (isJson(program2))
13590
+ output(program2, { id: prompt.id, slug: prompt.slug, body: prompt.body });
13591
+ else
13592
+ process.stdout.write(prompt.body);
13593
+ } catch (e) {
13594
+ handleError(program2, e);
13595
+ }
13596
+ });
13597
+ program2.command("render <id>").description("Render a template prompt with variable substitution").option("--var <kv>", "Variable as key=value (repeatable)", (val, acc) => {
13598
+ acc.push(val);
13599
+ return acc;
13600
+ }, []).option("--vars <json>", "Variables as JSON object").action((id, opts) => {
13601
+ try {
13602
+ const prompt = getPrompt(id);
13603
+ if (!prompt)
13604
+ handleError(program2, `Prompt not found: ${id}`);
13605
+ const vars = {};
13606
+ if (opts.vars) {
13607
+ Object.assign(vars, JSON.parse(opts.vars));
13608
+ }
13609
+ for (const kv of opts.var) {
13610
+ const eq = kv.indexOf("=");
13611
+ if (eq === -1)
13612
+ handleError(program2, `Invalid --var format (expected key=value): ${kv}`);
13613
+ vars[kv.slice(0, eq)] = kv.slice(eq + 1);
13614
+ }
13615
+ const { rendered, missing_vars, used_defaults } = renderTemplate(prompt.body, vars);
13616
+ if (isJson(program2)) {
13617
+ output(program2, { rendered, missing_vars, used_defaults });
13618
+ return;
13619
+ }
13620
+ if (missing_vars.length > 0) {
13621
+ console.error(chalk4.yellow(`Warning: unresolved variables: ${missing_vars.join(", ")}`));
13622
+ }
13623
+ if (used_defaults.length > 0 && !isJson(program2)) {
13624
+ console.error(chalk4.gray(`Using defaults for: ${used_defaults.join(", ")}`));
13625
+ }
13626
+ process.stdout.write(rendered);
13627
+ } catch (e) {
13628
+ handleError(program2, e);
13629
+ }
13630
+ });
13631
+ program2.command("similar <id>").description("Find prompts similar to the given one (by tags and collection)").option("-n, --limit <n>", "Max results", "5").action((id, opts) => {
13632
+ try {
13633
+ const prompt = getPrompt(id);
13634
+ if (!prompt)
13635
+ handleError(program2, `Prompt not found: ${id}`);
13636
+ const limit = parseInt(opts.limit ?? "5") || 5;
13637
+ const results = findSimilar(prompt.id, limit);
13638
+ if (isJson(program2)) {
13639
+ output(program2, results.map((r) => r.prompt));
13640
+ return;
13641
+ }
13642
+ if (results.length === 0) {
13643
+ console.log(chalk4.gray("No similar prompts found."));
13644
+ return;
13645
+ }
13646
+ console.log(chalk4.bold(`Similar to ${chalk4.green(prompt.slug)}:`));
13647
+ for (const r of results) {
13648
+ console.log(` ${fmtPrompt(r.prompt)} ${chalk4.gray(`score:${r.score}`)}`);
13649
+ }
13650
+ } catch (e) {
13651
+ handleError(program2, e);
13652
+ }
13653
+ });
13654
+ program2.command("edit <id>").description("Open a prompt in $EDITOR for full editing (title, body, description, tags, collection)").option("--agent <name>", "Attribution").action(async (id, opts) => {
13655
+ try {
13656
+ const prompt = getPrompt(id);
13657
+ if (!prompt)
13658
+ handleError(program2, `Prompt not found: ${id}`);
13659
+ const p = prompt;
13660
+ const { writeFileSync: writeFileSync2, readFileSync: readFileSync2, unlinkSync } = await import("fs");
13661
+ const { tmpdir } = await import("os");
13662
+ const { join: join7 } = await import("path");
13663
+ const frontmatter = [
13664
+ `---`,
13665
+ `title: ${p.title}`,
13666
+ `description: ${p.description ?? ""}`,
13667
+ `collection: ${p.collection}`,
13668
+ `tags: ${p.tags.join(", ")}`,
13669
+ `---`,
13670
+ ``,
13671
+ p.body
13672
+ ].join(`
13673
+ `);
13674
+ const tmpFile = join7(tmpdir(), `prompt-${p.id}.md`);
13675
+ writeFileSync2(tmpFile, frontmatter);
13676
+ const editor = process.env["EDITOR"] ?? process.env["VISUAL"] ?? "vi";
13677
+ const proc = Bun.spawn([editor, tmpFile], { stdin: "inherit", stdout: "inherit", stderr: "inherit" });
13678
+ await proc.exited;
13679
+ const edited = readFileSync2(tmpFile, "utf-8");
13680
+ unlinkSync(tmpFile);
13681
+ const fmMatch = edited.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/m);
13682
+ if (!fmMatch) {
13683
+ updatePrompt(p.id, { body: edited.trim(), changed_by: opts.agent });
13684
+ if (!isJson(program2))
13685
+ console.log(chalk4.green(`Updated ${chalk4.bold(p.slug)} (body only)`));
13686
+ return;
13687
+ }
13688
+ const fmLines = (fmMatch[1] ?? "").split(`
13689
+ `);
13690
+ const body = (fmMatch[2] ?? "").trimStart();
13691
+ const parsed = {};
13692
+ for (const line of fmLines) {
13693
+ const colon = line.indexOf(":");
13694
+ if (colon === -1)
13695
+ continue;
13696
+ parsed[line.slice(0, colon).trim()] = line.slice(colon + 1).trim();
13697
+ }
13698
+ updatePrompt(p.id, {
13699
+ title: parsed["title"] ?? p.title,
13700
+ body: body || p.body,
13701
+ description: parsed["description"] || (p.description != null ? p.description : undefined),
13702
+ collection: parsed["collection"] ?? p.collection,
13703
+ tags: parsed["tags"] ? parsed["tags"].split(",").map((t) => t.trim()).filter(Boolean) : p.tags,
13704
+ changed_by: opts.agent
13705
+ });
13706
+ if (isJson(program2))
13707
+ output(program2, getPrompt(p.id));
13708
+ else
13709
+ console.log(chalk4.green(`Updated ${chalk4.bold(p.slug)}`));
13710
+ } catch (e) {
13711
+ handleError(program2, e);
13712
+ }
13713
+ });
13714
+ program2.command("tag <id> [ops...]").description("Patch tags: +foo adds, -bar removes. Or --set foo,bar replaces all tags.").option("--set <tags>", "Replace all tags (comma-separated)").action((id, ops, opts) => {
13715
+ try {
13716
+ const prompt = getPrompt(id);
13717
+ if (!prompt)
13718
+ handleError(program2, `Prompt not found: ${id}`);
13719
+ const p = prompt;
13720
+ let tags;
13721
+ if (opts.set !== undefined) {
13722
+ tags = opts.set.split(",").map((t) => t.trim()).filter(Boolean);
13723
+ } else {
13724
+ tags = [...p.tags];
13725
+ for (const op of ops) {
13726
+ if (op.startsWith("+")) {
13727
+ const tag = op.slice(1);
13728
+ if (!tags.includes(tag))
13729
+ tags.push(tag);
13730
+ } else if (op.startsWith("-")) {
13731
+ const tag = op.slice(1);
13732
+ tags = tags.filter((t) => t !== tag);
13733
+ } else {
13734
+ if (!tags.includes(op))
13735
+ tags.push(op);
13736
+ }
13737
+ }
13738
+ }
13739
+ updatePrompt(p.id, { tags });
13740
+ if (isJson(program2))
13741
+ output(program2, { id: p.id, slug: p.slug, tags });
13742
+ else
13743
+ console.log(`${chalk4.bold(p.slug)} tags: ${tags.length > 0 ? chalk4.cyan(tags.join(", ")) : chalk4.gray("(none)")}`);
13744
+ } catch (e) {
13745
+ handleError(program2, e);
13746
+ }
13747
+ });
13748
+ program2.command("share <id>").description("Export a single prompt as a clean markdown snippet").option("--clipboard", "Copy to clipboard instead of printing").action(async (id, opts) => {
13749
+ try {
13750
+ const prompt = getPrompt(id);
13751
+ if (!prompt)
13752
+ handleError(program2, `Prompt not found: ${id}`);
13753
+ const p = prompt;
13754
+ const lines = [
13755
+ `# ${p.title}`,
13756
+ ``
13757
+ ];
13758
+ if (p.description)
13759
+ lines.push(`> ${p.description}`, ``);
13760
+ if (p.tags.length > 0)
13761
+ lines.push(`**Tags:** ${p.tags.join(", ")}`, ``);
13762
+ if (p.collection !== "default")
13763
+ lines.push(`**Collection:** ${p.collection}`, ``);
13764
+ lines.push("```", p.body, "```");
13765
+ const markdown = lines.join(`
13766
+ `);
13767
+ if (opts.clipboard) {
13768
+ const { platform: platform2 } = process;
13769
+ if (platform2 === "darwin") {
13770
+ const proc = Bun.spawn(["pbcopy"], { stdin: "pipe" });
13771
+ proc.stdin.write(markdown);
13772
+ proc.stdin.end();
13773
+ await proc.exited;
13774
+ } else if (platform2 === "linux") {
13775
+ try {
13776
+ const proc = Bun.spawn(["xclip", "-selection", "clipboard"], { stdin: "pipe" });
13777
+ proc.stdin.write(markdown);
13778
+ proc.stdin.end();
13779
+ await proc.exited;
13780
+ } catch {
13781
+ const proc = Bun.spawn(["xsel", "--clipboard", "--input"], { stdin: "pipe" });
13782
+ proc.stdin.write(markdown);
13783
+ proc.stdin.end();
13784
+ await proc.exited;
13785
+ }
13786
+ } else {
13787
+ handleError(program2, "Clipboard not supported on this platform.");
13788
+ }
13789
+ console.log(chalk4.green(`Copied ${chalk4.bold(p.slug)} to clipboard`));
13790
+ } else {
13791
+ if (isJson(program2))
13792
+ output(program2, { id: p.id, slug: p.slug, markdown });
13793
+ else
13794
+ console.log(markdown);
13795
+ }
13796
+ } catch (e) {
13797
+ handleError(program2, e);
13798
+ }
13799
+ });
13800
+ program2.command("validate <id>").description("Run lint checks and validate template variables for a single prompt").option("--var <kv>", "Simulate render with key=value (repeatable)", (val, acc) => {
13801
+ acc.push(val);
13802
+ return acc;
13803
+ }, []).option("--vars <json>", "Simulate render with JSON vars").action((id, opts) => {
13804
+ try {
13805
+ const prompt = getPrompt(id);
13806
+ if (!prompt)
13807
+ handleError(program2, `Prompt not found: ${id}`);
13808
+ const p = prompt;
13809
+ const lintIssues = lintPrompt(p);
13810
+ let exitCode = 0;
13811
+ if (isJson(program2)) {
13812
+ const result = { id: p.id, slug: p.slug, lint: lintIssues };
13813
+ if (p.is_template) {
13814
+ const vars = {};
13815
+ if (opts.vars)
13816
+ Object.assign(vars, JSON.parse(opts.vars));
13817
+ for (const kv of opts.var) {
13818
+ const eq = kv.indexOf("=");
13819
+ if (eq !== -1)
13820
+ vars[kv.slice(0, eq)] = kv.slice(eq + 1);
13821
+ }
13822
+ result["vars"] = validateVars(p.body, vars);
13823
+ }
13824
+ output(program2, result);
13825
+ return;
13826
+ }
13827
+ console.log(chalk4.bold(`Validating ${chalk4.green(p.slug)}:`));
13828
+ if (lintIssues.length === 0) {
13829
+ console.log(chalk4.green(" \u2713 Lint: no issues"));
13830
+ } else {
13831
+ for (const issue of lintIssues) {
13832
+ if (issue.severity === "error") {
13833
+ console.log(chalk4.red(` \u2717 [${issue.rule}] ${issue.message}`));
13834
+ exitCode = 1;
13835
+ } else if (issue.severity === "warn") {
13836
+ console.log(chalk4.yellow(` \u26A0 [${issue.rule}] ${issue.message}`));
13837
+ } else {
13838
+ console.log(chalk4.gray(` \u2139 [${issue.rule}] ${issue.message}`));
13839
+ }
13840
+ }
13841
+ }
13842
+ if (p.is_template) {
13843
+ const vars = {};
13844
+ if (opts.vars)
13845
+ Object.assign(vars, JSON.parse(opts.vars));
13846
+ for (const kv of opts.var) {
13847
+ const eq = kv.indexOf("=");
13848
+ if (eq !== -1)
13849
+ vars[kv.slice(0, eq)] = kv.slice(eq + 1);
13850
+ }
13851
+ const varInfo = extractVariableInfo(p.body);
13852
+ const { missing, extra, optional } = validateVars(p.body, vars);
13853
+ console.log(chalk4.bold(`
13854
+ Template variables:`));
13855
+ for (const v of varInfo) {
13856
+ const provided = v.name in vars;
13857
+ const sym = provided ? chalk4.green("\u2713") : v.required ? chalk4.red("\u2717") : chalk4.yellow("\u25CB");
13858
+ const val = provided ? chalk4.gray(` = "${vars[v.name]}"`) : v.default ? chalk4.gray(` (default: "${v.default}")`) : "";
13859
+ console.log(` ${sym} ${v.name}${val}`);
13860
+ }
13861
+ if (missing.length > 0) {
13862
+ console.log(chalk4.red(`
13863
+ Missing required vars: ${missing.join(", ")}`));
13864
+ exitCode = 1;
13865
+ }
13866
+ if (extra.length > 0)
13867
+ console.log(chalk4.yellow(` Extra vars (not in template): ${extra.join(", ")}`));
13868
+ if (optional.length > 0 && Object.keys(vars).length === 0)
13869
+ console.log(chalk4.gray(` Optional vars: ${optional.join(", ")}`));
13870
+ }
13871
+ if (exitCode === 0)
13872
+ console.log(chalk4.green(`
13873
+ \u2713 All checks passed`));
13874
+ else
13875
+ process.exit(exitCode);
13876
+ } catch (e) {
13877
+ handleError(program2, e);
13878
+ }
13879
+ });
13880
+ program2.command("bulk-move <collection> [ids...]").description("Move multiple prompts to a collection. Pass IDs as args or pipe one per line via stdin.").option("-y, --yes", "Skip confirmation").action(async (collection, ids, opts) => {
13881
+ try {
13882
+ let allIds = [...ids];
13883
+ if (process.stdin.isTTY === false && allIds.length === 0) {
13884
+ const chunks = [];
13885
+ for await (const chunk of process.stdin)
13886
+ chunks.push(chunk);
13887
+ allIds = Buffer.concat(chunks).toString("utf-8").split(`
13888
+ `).map((s) => s.trim()).filter(Boolean);
13889
+ }
13890
+ if (allIds.length === 0)
13891
+ handleError(program2, "No IDs provided.");
13892
+ if (!opts.yes && !isJson(program2)) {
13893
+ const { createInterface } = await import("readline");
13894
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
13895
+ await new Promise((resolve) => {
13896
+ rl.question(chalk4.yellow(`Move ${allIds.length} prompt(s) to "${collection}"? [y/N] `), (ans) => {
13897
+ rl.close();
13898
+ if (ans.toLowerCase() !== "y") {
13899
+ console.log("Cancelled.");
13900
+ process.exit(0);
13901
+ }
13902
+ resolve();
13903
+ });
13904
+ });
13905
+ }
13906
+ const results = [];
13907
+ for (const id of allIds) {
13908
+ try {
13909
+ movePrompt(id, collection);
13910
+ const p = getPrompt(id);
13911
+ results.push({ id, slug: p?.slug ?? id, ok: true });
13912
+ } catch (e) {
13913
+ results.push({ id, slug: id, ok: false, error: e instanceof Error ? e.message : String(e) });
13914
+ }
13915
+ }
13916
+ if (isJson(program2)) {
13917
+ output(program2, results);
13918
+ return;
13919
+ }
13920
+ const ok = results.filter((r) => r.ok);
13921
+ const fail = results.filter((r) => !r.ok);
13922
+ if (ok.length > 0)
13923
+ console.log(chalk4.green(`Moved ${ok.length} prompt(s) \u2192 ${chalk4.bold(collection)}`));
13924
+ for (const f of fail)
13925
+ console.error(chalk4.red(` \u2717 ${f.id}: ${f.error}`));
13926
+ } catch (e) {
13927
+ handleError(program2, e);
13928
+ }
13929
+ });
13930
+ program2.command("bulk-tag [ops...] [ids...]").description("Patch tags on multiple prompts. Leading ops start with + or -, rest are IDs. Or pipe IDs via stdin.").helpOption("-h, --help").addHelpText("after", `
13931
+ Examples:
13932
+ prompts bulk-tag +foo -bar PRMT-00001 PRMT-00002
13933
+ echo "PRMT-00001" | prompts bulk-tag +reviewed`).action(async (args) => {
13934
+ try {
13935
+ const ops = [];
13936
+ const ids = [];
13937
+ for (const a of args) {
13938
+ if (a.startsWith("+") || a.startsWith("-"))
13939
+ ops.push(a);
13940
+ else
13941
+ ids.push(a);
13942
+ }
13943
+ let allIds = [...ids];
13944
+ if (process.stdin.isTTY === false && allIds.length === 0) {
13945
+ const chunks = [];
13946
+ for await (const chunk of process.stdin)
13947
+ chunks.push(chunk);
13948
+ allIds = Buffer.concat(chunks).toString("utf-8").split(`
13949
+ `).map((s) => s.trim()).filter(Boolean);
13950
+ }
13951
+ if (allIds.length === 0)
13952
+ handleError(program2, "No IDs provided.");
13953
+ if (ops.length === 0)
13954
+ handleError(program2, "No tag ops provided. Use +tag to add, -tag to remove.");
13955
+ const results = [];
13956
+ for (const id of allIds) {
13957
+ const prompt = getPrompt(id);
13958
+ if (!prompt) {
13959
+ console.error(chalk4.red(` \u2717 Not found: ${id}`));
13960
+ continue;
13961
+ }
13962
+ let tags = [...prompt.tags];
13963
+ for (const op of ops) {
13964
+ if (op.startsWith("+")) {
13965
+ const t = op.slice(1);
13966
+ if (!tags.includes(t))
13967
+ tags.push(t);
13968
+ } else if (op.startsWith("-")) {
13969
+ const t = op.slice(1);
13970
+ tags = tags.filter((x) => x !== t);
13971
+ }
13972
+ }
13973
+ updatePrompt(prompt.id, { tags });
13974
+ results.push({ id: prompt.id, slug: prompt.slug, tags });
13975
+ }
13976
+ if (isJson(program2)) {
13977
+ output(program2, results);
13978
+ return;
13979
+ }
13980
+ console.log(chalk4.green(`Updated ${results.length} prompt(s)`));
13981
+ for (const r of results)
13982
+ console.log(` ${chalk4.bold(r.slug)} ${chalk4.cyan(r.tags.join(", ") || "(no tags)")}`);
13983
+ } catch (e) {
13984
+ handleError(program2, e);
13985
+ }
13986
+ });
13987
+ program2.command("bulk-delete [ids...]").alias("bulk-rm").description("Delete multiple prompts. Pass IDs as args or pipe one per line via stdin.").option("-y, --yes", "Skip confirmation").action(async (ids, opts) => {
13988
+ try {
13989
+ const { deletePrompt: deletePrompt2 } = await Promise.resolve().then(() => (init_prompts(), exports_prompts));
13990
+ let allIds = [...ids];
13991
+ if (process.stdin.isTTY === false && allIds.length === 0) {
13992
+ const chunks = [];
13993
+ for await (const chunk of process.stdin)
13994
+ chunks.push(chunk);
13995
+ allIds = Buffer.concat(chunks).toString("utf-8").split(`
13996
+ `).map((s) => s.trim()).filter(Boolean);
13997
+ }
13998
+ if (allIds.length === 0)
13999
+ handleError(program2, "No IDs provided.");
14000
+ if (!opts.yes && !isJson(program2)) {
14001
+ const { createInterface } = await import("readline");
14002
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
14003
+ await new Promise((resolve) => {
14004
+ rl.question(chalk4.red(`Delete ${allIds.length} prompt(s)? This cannot be undone. [y/N] `), (ans) => {
14005
+ rl.close();
14006
+ if (ans.toLowerCase() !== "y") {
14007
+ console.log("Cancelled.");
14008
+ process.exit(0);
14009
+ }
14010
+ resolve();
14011
+ });
14012
+ });
14013
+ }
14014
+ let deleted = 0;
14015
+ const errors = [];
14016
+ for (const id of allIds) {
14017
+ try {
14018
+ deletePrompt2(id);
14019
+ deleted++;
14020
+ } catch (e) {
14021
+ errors.push(`${id}: ${e instanceof Error ? e.message : String(e)}`);
14022
+ }
14023
+ }
14024
+ if (isJson(program2)) {
14025
+ output(program2, { deleted, errors });
14026
+ return;
14027
+ }
14028
+ console.log(chalk4.red(`Deleted ${deleted} prompt(s)`));
14029
+ for (const e of errors)
14030
+ console.error(chalk4.red(` \u2717 ${e}`));
14031
+ } catch (e) {
14032
+ handleError(program2, e);
14033
+ }
14034
+ });
14035
+ }
14036
+
14037
+ // src/cli/commands/config.ts
14038
+ init_prompts();
14039
+ import chalk5 from "chalk";
14040
+ import { homedir as homedir6 } from "os";
14041
+ import { join as join7, resolve } from "path";
14042
+ import { existsSync as existsSync6, readFileSync as readFileSync2, writeFileSync as writeFileSync2, readdirSync as readdirSync3, statSync } from "fs";
14043
+ var AGENT_CONFIGS = {
14044
+ claude: {
14045
+ global: ".claude/CLAUDE.md",
14046
+ local: "CLAUDE.md",
14047
+ label: "Claude Code"
14048
+ },
14049
+ agents: {
14050
+ global: ".agents/AGENTS.md",
14051
+ local: "AGENTS.md",
14052
+ label: "OpenAI Agents SDK"
14053
+ },
14054
+ gemini: {
14055
+ global: ".gemini/GEMINI.md",
14056
+ local: ".gemini/GEMINI.md",
14057
+ label: "Gemini CLI"
14058
+ },
14059
+ codex: {
14060
+ global: ".codex/CODEX.md",
14061
+ local: "CODEX.md",
14062
+ label: "OpenAI Codex CLI"
14063
+ },
14064
+ cursor: {
14065
+ global: ".cursor/rules",
14066
+ local: ".cursorrules",
14067
+ label: "Cursor"
14068
+ },
14069
+ aider: {
14070
+ global: ".aider/CONVENTIONS.md",
14071
+ local: ".aider.conventions.md",
14072
+ label: "Aider"
14073
+ }
14074
+ };
14075
+ function resolveConfigPath(agent, globalFlag) {
14076
+ const cfg = AGENT_CONFIGS[agent.toLowerCase()];
14077
+ if (!cfg)
14078
+ return null;
14079
+ if (globalFlag)
14080
+ return join7(homedir6(), cfg.global);
14081
+ return resolve(process.cwd(), cfg.local);
14082
+ }
14083
+ function registerConfigCommands(program2) {
14084
+ const configCmd = program2.command("config").description("Manage AI agent config files (CLAUDE.md, AGENTS.md, GEMINI.md, etc.)");
14085
+ configCmd.command("list").description("List all known config files (global + project)").action(() => {
14086
+ try {
14087
+ const rows = [];
14088
+ for (const [key, cfg] of Object.entries(AGENT_CONFIGS)) {
14089
+ const globalPath = join7(homedir6(), cfg.global);
14090
+ const localPath = resolve(process.cwd(), cfg.local);
14091
+ const globalExists = existsSync6(globalPath);
14092
+ const localExists = existsSync6(localPath);
14093
+ rows.push({
14094
+ agent: key,
14095
+ label: cfg.label,
14096
+ scope: "global",
14097
+ path: globalPath,
14098
+ exists: globalExists,
14099
+ size: globalExists ? statSync(globalPath).size : undefined
14100
+ });
14101
+ if (globalPath !== localPath) {
14102
+ rows.push({
14103
+ agent: key,
14104
+ label: cfg.label,
14105
+ scope: "project",
14106
+ path: localPath,
14107
+ exists: localExists,
14108
+ size: localExists ? statSync(localPath).size : undefined
14109
+ });
14110
+ }
14111
+ }
14112
+ if (isJson(program2)) {
14113
+ output(program2, rows);
14114
+ return;
14115
+ }
14116
+ const existingRows = rows.filter((r) => r.exists);
14117
+ const missingRows = rows.filter((r) => !r.exists);
14118
+ if (existingRows.length > 0) {
14119
+ console.log(chalk5.bold("Found:"));
14120
+ for (const r of existingRows) {
14121
+ const size = r.size !== undefined ? chalk5.gray(` (${r.size}B)`) : "";
14122
+ const scope = r.scope === "global" ? chalk5.blue("[global]") : chalk5.cyan("[project]");
14123
+ console.log(` ${scope} ${chalk5.bold(r.label)} ${chalk5.gray(r.path)}${size}`);
14124
+ }
14125
+ }
14126
+ if (missingRows.length > 0) {
14127
+ console.log(chalk5.dim(`
14128
+ Not present:`));
14129
+ for (const r of missingRows) {
14130
+ const scope = r.scope === "global" ? chalk5.blue("[global]") : chalk5.cyan("[project]");
14131
+ console.log(chalk5.gray(` ${scope} ${r.label} ${r.path}`));
14132
+ }
14133
+ }
14134
+ } catch (e) {
14135
+ handleError(program2, e);
14136
+ }
14137
+ });
14138
+ configCmd.command("get <agent>").description("Print the contents of an agent config file").option("-g, --global", "Use global (~/) config instead of project-local").action((agent, opts) => {
14139
+ try {
14140
+ const path = resolveConfigPath(agent, Boolean(opts.global));
14141
+ if (!path)
14142
+ handleError(program2, `Unknown agent "${agent}". Known: ${Object.keys(AGENT_CONFIGS).join(", ")}`);
14143
+ if (!existsSync6(path))
14144
+ handleError(program2, `Config file not found: ${path}`);
14145
+ const content = readFileSync2(path, "utf-8");
14146
+ if (isJson(program2))
14147
+ output(program2, { agent, path, content });
14148
+ else
14149
+ console.log(content);
14150
+ } catch (e) {
14151
+ handleError(program2, e);
14152
+ }
14153
+ });
14154
+ configCmd.command("edit <agent>").description("Open an agent config file in $EDITOR (creates if missing)").option("-g, --global", "Edit global (~/) config instead of project-local").action(async (agent, opts) => {
14155
+ try {
14156
+ const path = resolveConfigPath(agent, Boolean(opts.global));
14157
+ if (!path)
14158
+ handleError(program2, `Unknown agent "${agent}". Known: ${Object.keys(AGENT_CONFIGS).join(", ")}`);
14159
+ const { mkdirSync: mkdirSync4 } = await import("fs");
14160
+ const { dirname: dirname2 } = await import("path");
14161
+ mkdirSync4(dirname2(path), { recursive: true });
14162
+ if (!existsSync6(path))
14163
+ writeFileSync2(path, "", "utf-8");
14164
+ const editor = process.env["EDITOR"] ?? process.env["VISUAL"] ?? "vi";
14165
+ const proc = Bun.spawn([editor, path], { stdin: "inherit", stdout: "inherit", stderr: "inherit" });
14166
+ await proc.exited;
14167
+ console.log(chalk5.green(`Saved ${path}`));
14168
+ } catch (e) {
14169
+ handleError(program2, e);
14170
+ }
14171
+ });
14172
+ configCmd.command("inject <slug> <agent>").description("Append a saved prompt's body into an agent config file").option("-g, --global", "Inject into global (~/) config instead of project-local").option("--section <heading>", "Append under a markdown heading (creates if missing)").option("--replace", "Replace section content instead of appending").action(async (slug, agent, opts) => {
14173
+ try {
14174
+ const prompt = getPrompt(slug);
14175
+ if (!prompt)
14176
+ handleError(program2, `Prompt not found: ${slug}`);
14177
+ const path = resolveConfigPath(agent, Boolean(opts.global));
14178
+ if (!path)
14179
+ handleError(program2, `Unknown agent "${agent}". Known: ${Object.keys(AGENT_CONFIGS).join(", ")}`);
14180
+ const { mkdirSync: mkdirSync4 } = await import("fs");
14181
+ const { dirname: dirname2 } = await import("path");
14182
+ mkdirSync4(dirname2(path), { recursive: true });
14183
+ let existing = existsSync6(path) ? readFileSync2(path, "utf-8") : "";
14184
+ const injection = `
14185
+ ${prompt.body}
14186
+ `;
14187
+ if (opts.section) {
14188
+ const heading = `## ${opts.section}`;
14189
+ const idx = existing.indexOf(heading);
14190
+ if (idx === -1) {
14191
+ existing = existing.trimEnd() + `
14192
+
14193
+ ${heading}
14194
+ ${injection}`;
14195
+ } else if (opts.replace) {
14196
+ const afterHeading = idx + heading.length;
14197
+ const nextSection = existing.indexOf(`
14198
+ ## `, afterHeading);
14199
+ const sectionEnd = nextSection === -1 ? existing.length : nextSection;
14200
+ existing = existing.slice(0, afterHeading) + `
14201
+ ${injection}` + existing.slice(sectionEnd);
14202
+ } else {
14203
+ const afterHeading = idx + heading.length;
14204
+ const nextSection = existing.indexOf(`
14205
+ ## `, afterHeading);
14206
+ const insertAt = nextSection === -1 ? existing.length : nextSection;
14207
+ existing = existing.slice(0, insertAt).trimEnd() + `
14208
+ ${injection}
14209
+ ` + existing.slice(insertAt);
14210
+ }
14211
+ } else {
14212
+ existing = existing.trimEnd() + `
14213
+ ${injection}`;
14214
+ }
14215
+ writeFileSync2(path, existing);
14216
+ if (isJson(program2))
14217
+ output(program2, { injected: true, slug: prompt.slug, path, section: opts.section });
14218
+ else
14219
+ console.log(chalk5.green(`Injected "${chalk5.bold(prompt.slug)}" into ${path}`));
14220
+ } catch (e) {
14221
+ handleError(program2, e);
14222
+ }
14223
+ });
14224
+ configCmd.command("set <agent>").description("Write content to an agent config file (reads from stdin or --body)").option("-g, --global", "Write to global (~/) config instead of project-local").option("--body <content>", "Content to write (use - or omit for stdin)").option("-y, --yes", "Skip confirmation when overwriting existing file").action(async (agent, opts) => {
14225
+ try {
14226
+ const path = resolveConfigPath(agent, Boolean(opts.global));
14227
+ if (!path)
14228
+ handleError(program2, `Unknown agent "${agent}". Known: ${Object.keys(AGENT_CONFIGS).join(", ")}`);
14229
+ let content = opts.body ?? "";
14230
+ if (!content) {
14231
+ const chunks = [];
14232
+ for await (const chunk of process.stdin)
14233
+ chunks.push(chunk);
14234
+ content = Buffer.concat(chunks).toString("utf-8");
14235
+ }
14236
+ if (existsSync6(path) && !opts.yes && !isJson(program2)) {
14237
+ const { createInterface } = await import("readline");
14238
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
14239
+ await new Promise((resolve2) => {
14240
+ rl.question(chalk5.yellow(`Overwrite existing ${path}? [y/N] `), (ans) => {
14241
+ rl.close();
14242
+ if (ans.toLowerCase() !== "y") {
14243
+ console.log("Cancelled.");
14244
+ process.exit(0);
14245
+ }
14246
+ resolve2();
14247
+ });
14248
+ });
14249
+ }
14250
+ const { mkdirSync: mkdirSync4 } = await import("fs");
14251
+ const { dirname: dirname2 } = await import("path");
14252
+ mkdirSync4(dirname2(path), { recursive: true });
14253
+ writeFileSync2(path, content);
14254
+ if (isJson(program2))
14255
+ output(program2, { written: true, agent, path });
14256
+ else
14257
+ console.log(chalk5.green(`Wrote ${path}`));
14258
+ } catch (e) {
14259
+ handleError(program2, e);
14260
+ }
14261
+ });
14262
+ configCmd.command("scan [workspace]").description("Scan repos in a workspace directory for missing/present agent config files").option("--depth <n>", "Max directory depth to search for git repos", "3").option("--agents <list>", "Comma-separated agents to check (default: all)").option("--missing-only", "Only show repos with missing configs").action((workspace, opts) => {
14263
+ try {
14264
+ let scanDir = function(dir, depth) {
14265
+ if (depth > maxDepth)
14266
+ return;
14267
+ try {
14268
+ const entries = readdirSync3(dir, { withFileTypes: true });
14269
+ if (entries.some((e) => e.name === ".git" && e.isDirectory())) {
14270
+ repos.push(dir);
14271
+ return;
14272
+ }
14273
+ for (const entry of entries) {
14274
+ if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
14275
+ scanDir(join7(dir, entry.name), depth + 1);
14276
+ }
14277
+ }
14278
+ } catch {}
14279
+ };
14280
+ const wsDir = workspace ? resolve(workspace) : resolve(homedir6(), "workspace");
14281
+ if (!existsSync6(wsDir))
14282
+ handleError(program2, `Workspace not found: ${wsDir}`);
14283
+ const maxDepth = parseInt(opts.depth ?? "3") || 3;
14284
+ const agentFilter = opts.agents ? opts.agents.split(",").map((a) => a.trim().toLowerCase()) : Object.keys(AGENT_CONFIGS);
14285
+ const repos = [];
14286
+ scanDir(wsDir, 0);
14287
+ const reports = [];
14288
+ for (const repo of repos) {
14289
+ const configs = {};
14290
+ let missingCount = 0;
14291
+ let presentCount = 0;
14292
+ for (const agentKey of agentFilter) {
14293
+ const cfg = AGENT_CONFIGS[agentKey];
14294
+ if (!cfg)
14295
+ continue;
14296
+ const localPath = join7(repo, cfg.local);
14297
+ const exists = existsSync6(localPath);
14298
+ configs[agentKey] = {
14299
+ present: exists,
14300
+ path: localPath,
14301
+ size: exists ? statSync(localPath).size : undefined
14302
+ };
14303
+ if (exists)
14304
+ presentCount++;
14305
+ else
14306
+ missingCount++;
14307
+ }
14308
+ if (!opts.missingOnly || missingCount > 0) {
14309
+ reports.push({ repo, configs, missing_count: missingCount, present_count: presentCount });
14310
+ }
14311
+ }
14312
+ if (isJson(program2)) {
14313
+ output(program2, { workspace: wsDir, repos_scanned: repos.length, reports });
14314
+ return;
14315
+ }
14316
+ console.log(chalk5.bold(`Scanned ${repos.length} repo(s) in ${wsDir}
14317
+ `));
14318
+ if (reports.length === 0) {
14319
+ console.log(chalk5.green("\u2713 All repos have all config files."));
14320
+ return;
14321
+ }
14322
+ for (const r of reports) {
14323
+ const rel = r.repo.replace(wsDir + "/", "");
14324
+ const status = r.missing_count === 0 ? chalk5.green(`\u2713 ${rel}`) : chalk5.yellow(`\u25B3 ${rel}`);
14325
+ console.log(status + chalk5.gray(` (${r.present_count} present, ${r.missing_count} missing)`));
14326
+ for (const [agent, info] of Object.entries(r.configs)) {
14327
+ const sym = info.present ? chalk5.green(" \u2713") : chalk5.red(" \u2717");
14328
+ const label = AGENT_CONFIGS[agent]?.label ?? agent;
14329
+ const detail = info.present ? chalk5.gray(` ${info.size}B`) : chalk5.gray(` ${info.path}`);
14330
+ console.log(`${sym} ${label}${detail}`);
14331
+ }
14332
+ console.log();
14333
+ }
14334
+ const totalMissing = reports.reduce((s, r) => s + r.missing_count, 0);
14335
+ if (totalMissing > 0) {
14336
+ console.log(chalk5.yellow(`${totalMissing} config file(s) missing across ${reports.filter((r) => r.missing_count > 0).length} repo(s)`));
14337
+ console.log(chalk5.gray(`Use \`prompts config inject <slug> <agent>\` to add config content from your saved prompts.`));
14338
+ } else {
14339
+ console.log(chalk5.green("\u2713 All checked repos have all config files."));
14340
+ }
14341
+ } catch (e) {
14342
+ handleError(program2, e);
14343
+ }
14344
+ });
14345
+ }
14346
+
13393
14347
  // src/cli/index.tsx
13394
14348
  var require2 = createRequire2(import.meta.url);
13395
14349
  var pkg = require2("../../package.json");
13396
14350
  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").option("--project <name>", "Active project (name, slug, or ID) for scoped operations");
13397
14351
  registerPromptCommands(program2);
13398
14352
  registerVersionCommands(program2);
14353
+ registerQolCommands(program2);
14354
+ registerConfigCommands(program2);
13399
14355
  program2.command("collections").description("List all collections").action(() => {
13400
14356
  try {
13401
14357
  const cols = listCollections();
@@ -13403,9 +14359,9 @@ program2.command("collections").description("List all collections").action(() =>
13403
14359
  output(program2, cols);
13404
14360
  } else {
13405
14361
  for (const c of cols) {
13406
- console.log(`${chalk4.bold(c.name)} ${chalk4.gray(`${c.prompt_count} prompt(s)`)}`);
14362
+ console.log(`${chalk6.bold(c.name)} ${chalk6.gray(`${c.prompt_count} prompt(s)`)}`);
13407
14363
  if (c.description)
13408
- console.log(chalk4.gray(" " + c.description));
14364
+ console.log(chalk6.gray(" " + c.description));
13409
14365
  }
13410
14366
  }
13411
14367
  } catch (e) {
@@ -13418,7 +14374,7 @@ program2.command("move <id> <collection>").description("Move a prompt to a diffe
13418
14374
  if (isJson(program2))
13419
14375
  output(program2, { moved: true, id, collection });
13420
14376
  else
13421
- console.log(`${chalk4.green("Moved")} ${id} \u2192 ${chalk4.bold(collection)}`);
14377
+ console.log(`${chalk6.green("Moved")} ${id} \u2192 ${chalk6.bold(collection)}`);
13422
14378
  } catch (e) {
13423
14379
  handleError(program2, e);
13424
14380
  }
@@ -13428,9 +14384,9 @@ program2.command("export").description("Export prompts as JSON").option("-c, --c
13428
14384
  const data = exportToJson(opts["collection"]);
13429
14385
  const json = JSON.stringify(data, null, 2);
13430
14386
  if (opts["output"]) {
13431
- const { writeFileSync: writeFileSync2 } = await import("fs");
13432
- writeFileSync2(opts["output"], json);
13433
- console.log(chalk4.green(`Exported ${data.prompts.length} prompt(s) to ${opts["output"]}`));
14387
+ const { writeFileSync: writeFileSync3 } = await import("fs");
14388
+ writeFileSync3(opts["output"], json);
14389
+ console.log(chalk6.green(`Exported ${data.prompts.length} prompt(s) to ${opts["output"]}`));
13434
14390
  } else {
13435
14391
  console.log(json);
13436
14392
  }
@@ -13440,18 +14396,18 @@ program2.command("export").description("Export prompts as JSON").option("-c, --c
13440
14396
  });
13441
14397
  program2.command("import <file>").description("Import prompts from a JSON file").option("--agent <name>").action(async (file, opts) => {
13442
14398
  try {
13443
- const { readFileSync: readFileSync2 } = await import("fs");
13444
- const raw = JSON.parse(readFileSync2(file, "utf-8"));
14399
+ const { readFileSync: readFileSync3 } = await import("fs");
14400
+ const raw = JSON.parse(readFileSync3(file, "utf-8"));
13445
14401
  const items = Array.isArray(raw) ? raw : raw.prompts ?? [];
13446
14402
  const results = importFromJson(items, opts["agent"]);
13447
14403
  if (isJson(program2))
13448
14404
  output(program2, results);
13449
14405
  else {
13450
- console.log(chalk4.green(`Created: ${results.created}, Updated: ${results.updated}`));
14406
+ console.log(chalk6.green(`Created: ${results.created}, Updated: ${results.updated}`));
13451
14407
  if (results.errors.length > 0) {
13452
- console.error(chalk4.red(`Errors: ${results.errors.length}`));
14408
+ console.error(chalk6.red(`Errors: ${results.errors.length}`));
13453
14409
  for (const e of results.errors)
13454
- console.error(chalk4.red(` ${e.item}: ${e.error}`));
14410
+ console.error(chalk6.red(` ${e.item}: ${e.error}`));
13455
14411
  }
13456
14412
  }
13457
14413
  } catch (e) {
@@ -13464,19 +14420,19 @@ program2.command("stats").description("Show usage statistics").action(() => {
13464
14420
  if (isJson(program2)) {
13465
14421
  output(program2, stats);
13466
14422
  } else {
13467
- console.log(chalk4.bold("Prompt Stats"));
14423
+ console.log(chalk6.bold("Prompt Stats"));
13468
14424
  console.log(` Total: ${stats.total_prompts} Templates: ${stats.total_templates} Collections: ${stats.total_collections}`);
13469
14425
  if (stats.most_used.length > 0) {
13470
- console.log(chalk4.bold(`
14426
+ console.log(chalk6.bold(`
13471
14427
  Most used:`));
13472
14428
  for (const p of stats.most_used)
13473
- console.log(` ${chalk4.green(p.slug)} ${chalk4.gray(`${p.use_count}\xD7`)}`);
14429
+ console.log(` ${chalk6.green(p.slug)} ${chalk6.gray(`${p.use_count}\xD7`)}`);
13474
14430
  }
13475
14431
  if (stats.by_collection.length > 0) {
13476
- console.log(chalk4.bold(`
14432
+ console.log(chalk6.bold(`
13477
14433
  By collection:`));
13478
14434
  for (const c of stats.by_collection)
13479
- console.log(` ${chalk4.bold(c.collection)} ${chalk4.gray(`${c.count}`)}`);
14435
+ console.log(` ${chalk6.bold(c.collection)} ${chalk6.gray(`${c.count}`)}`);
13480
14436
  }
13481
14437
  }
13482
14438
  } catch (e) {
@@ -13492,12 +14448,12 @@ program2.command("recent [n]").description("Show recently used prompts (default:
13492
14448
  return;
13493
14449
  }
13494
14450
  if (prompts.length === 0) {
13495
- console.log(chalk4.gray("No recently used prompts."));
14451
+ console.log(chalk6.gray("No recently used prompts."));
13496
14452
  return;
13497
14453
  }
13498
14454
  for (const p of prompts) {
13499
- const ago = chalk4.gray(new Date(p.last_used_at).toLocaleString());
13500
- console.log(`${chalk4.bold(p.id)} ${chalk4.green(p.slug)} ${p.title} ${ago}`);
14455
+ const ago = chalk6.gray(new Date(p.last_used_at).toLocaleString());
14456
+ console.log(`${chalk6.bold(p.id)} ${chalk6.green(p.slug)} ${p.title} ${ago}`);
13501
14457
  }
13502
14458
  } catch (e) {
13503
14459
  handleError(program2, e);
@@ -13512,27 +14468,27 @@ program2.command("lint").description("Check prompt quality: missing descriptions
13512
14468
  return;
13513
14469
  }
13514
14470
  if (results.length === 0) {
13515
- console.log(chalk4.green("\u2713 All prompts pass lint."));
14471
+ console.log(chalk6.green("\u2713 All prompts pass lint."));
13516
14472
  return;
13517
14473
  }
13518
14474
  let errors = 0, warns = 0, infos = 0;
13519
14475
  for (const { prompt: p, issues } of results) {
13520
14476
  console.log(`
13521
- ${chalk4.bold(p.slug)} ${chalk4.gray(p.id)}`);
14477
+ ${chalk6.bold(p.slug)} ${chalk6.gray(p.id)}`);
13522
14478
  for (const issue of issues) {
13523
14479
  if (issue.severity === "error") {
13524
- console.log(chalk4.red(` \u2717 [${issue.rule}] ${issue.message}`));
14480
+ console.log(chalk6.red(` \u2717 [${issue.rule}] ${issue.message}`));
13525
14481
  errors++;
13526
14482
  } else if (issue.severity === "warn") {
13527
- console.log(chalk4.yellow(` \u26A0 [${issue.rule}] ${issue.message}`));
14483
+ console.log(chalk6.yellow(` \u26A0 [${issue.rule}] ${issue.message}`));
13528
14484
  warns++;
13529
14485
  } else {
13530
- console.log(chalk4.gray(` \u2139 [${issue.rule}] ${issue.message}`));
14486
+ console.log(chalk6.gray(` \u2139 [${issue.rule}] ${issue.message}`));
13531
14487
  infos++;
13532
14488
  }
13533
14489
  }
13534
14490
  }
13535
- console.log(chalk4.bold(`
14491
+ console.log(chalk6.bold(`
13536
14492
  ${results.length} prompt(s) with issues \u2014 ${errors} errors, ${warns} warnings, ${infos} info`));
13537
14493
  if (errors > 0)
13538
14494
  process.exit(1);
@@ -13553,24 +14509,24 @@ program2.command("stale [days]").description("List prompts not used in N days (d
13553
14509
  return;
13554
14510
  }
13555
14511
  if (expired.length > 0) {
13556
- console.log(chalk4.red(`
14512
+ console.log(chalk6.red(`
13557
14513
  Expired (${expired.length}):`));
13558
14514
  for (const p of expired)
13559
- console.log(chalk4.red(` \u2717 ${p.slug}`) + chalk4.gray(` expired ${new Date(p.expires_at).toLocaleDateString()}`));
14515
+ console.log(chalk6.red(` \u2717 ${p.slug}`) + chalk6.gray(` expired ${new Date(p.expires_at).toLocaleDateString()}`));
13560
14516
  }
13561
14517
  if (stale.length === 0 && expired.length === 0) {
13562
- console.log(chalk4.green(`No stale prompts (threshold: ${threshold} days).`));
14518
+ console.log(chalk6.green(`No stale prompts (threshold: ${threshold} days).`));
13563
14519
  return;
13564
14520
  }
13565
14521
  if (stale.length > 0) {
13566
- console.log(chalk4.bold(`
14522
+ console.log(chalk6.bold(`
13567
14523
  Stale prompts (not used in ${threshold}+ days):`));
13568
14524
  for (const p of stale) {
13569
- const last = p.last_used_at ? chalk4.gray(new Date(p.last_used_at).toLocaleDateString()) : chalk4.red("never");
13570
- console.log(` ${chalk4.green(p.slug)} ${chalk4.gray(`${p.use_count}\xD7`)} last used: ${last}`);
14525
+ const last = p.last_used_at ? chalk6.gray(new Date(p.last_used_at).toLocaleDateString()) : chalk6.red("never");
14526
+ console.log(` ${chalk6.green(p.slug)} ${chalk6.gray(`${p.use_count}\xD7`)} last used: ${last}`);
13571
14527
  }
13572
14528
  }
13573
- console.log(chalk4.gray(`
14529
+ console.log(chalk6.gray(`
13574
14530
  ${stale.length} stale prompt(s)`));
13575
14531
  } catch (e) {
13576
14532
  handleError(program2, e);
@@ -13582,7 +14538,7 @@ program2.command("pin <id>").description("Pin a prompt so it always appears firs
13582
14538
  if (isJson(program2))
13583
14539
  output(program2, p);
13584
14540
  else
13585
- console.log(chalk4.yellow(`\uD83D\uDCCC Pinned ${chalk4.bold(p.slug)}`));
14541
+ console.log(chalk6.yellow(`\uD83D\uDCCC Pinned ${chalk6.bold(p.slug)}`));
13586
14542
  } catch (e) {
13587
14543
  handleError(program2, e);
13588
14544
  }
@@ -13593,7 +14549,7 @@ program2.command("unpin <id>").description("Unpin a prompt").action((id) => {
13593
14549
  if (isJson(program2))
13594
14550
  output(program2, p);
13595
14551
  else
13596
- console.log(chalk4.gray(`Unpinned ${chalk4.bold(p.slug)}`));
14552
+ console.log(chalk6.gray(`Unpinned ${chalk6.bold(p.slug)}`));
13597
14553
  } catch (e) {
13598
14554
  handleError(program2, e);
13599
14555
  }
@@ -13625,7 +14581,7 @@ program2.command("copy <id>").description("Copy prompt body to clipboard and inc
13625
14581
  if (isJson(program2))
13626
14582
  output(program2, { copied: true, id: prompt.id, slug: prompt.slug });
13627
14583
  else
13628
- console.log(chalk4.green(`Copied ${chalk4.bold(prompt.slug)} to clipboard`));
14584
+ console.log(chalk6.green(`Copied ${chalk6.bold(prompt.slug)} to clipboard`));
13629
14585
  } catch (e) {
13630
14586
  handleError(program2, e);
13631
14587
  }
@@ -13637,9 +14593,9 @@ projectCmd.command("create <name>").description("Create a new project").option("
13637
14593
  if (isJson(program2))
13638
14594
  output(program2, project);
13639
14595
  else {
13640
- console.log(`${chalk4.green("Created")} project ${chalk4.bold(project.name)} \u2014 ${chalk4.gray(project.slug)}`);
14596
+ console.log(`${chalk6.green("Created")} project ${chalk6.bold(project.name)} \u2014 ${chalk6.gray(project.slug)}`);
13641
14597
  if (project.description)
13642
- console.log(chalk4.gray(` ${project.description}`));
14598
+ console.log(chalk6.gray(` ${project.description}`));
13643
14599
  }
13644
14600
  } catch (e) {
13645
14601
  handleError(program2, e);
@@ -13653,13 +14609,13 @@ projectCmd.command("list").description("List all projects").action(() => {
13653
14609
  return;
13654
14610
  }
13655
14611
  if (projects.length === 0) {
13656
- console.log(chalk4.gray("No projects."));
14612
+ console.log(chalk6.gray("No projects."));
13657
14613
  return;
13658
14614
  }
13659
14615
  for (const p of projects) {
13660
- console.log(`${chalk4.bold(p.name)} ${chalk4.gray(p.slug)} ${chalk4.cyan(`${p.prompt_count} prompt(s)`)}`);
14616
+ console.log(`${chalk6.bold(p.name)} ${chalk6.gray(p.slug)} ${chalk6.cyan(`${p.prompt_count} prompt(s)`)}`);
13661
14617
  if (p.description)
13662
- console.log(chalk4.gray(` ${p.description}`));
14618
+ console.log(chalk6.gray(` ${p.description}`));
13663
14619
  }
13664
14620
  } catch (e) {
13665
14621
  handleError(program2, e);
@@ -13670,7 +14626,7 @@ projectCmd.command("get <id>").description("Get project details").action((id) =>
13670
14626
  const project = getProject(id);
13671
14627
  if (!project)
13672
14628
  handleError(program2, `Project not found: ${id}`);
13673
- output(program2, isJson(program2) ? project : `${chalk4.bold(project.name)} ${chalk4.gray(project.slug)} ${chalk4.cyan(`${project.prompt_count} prompt(s)`)}`);
14629
+ output(program2, isJson(program2) ? project : `${chalk6.bold(project.name)} ${chalk6.gray(project.slug)} ${chalk6.cyan(`${project.prompt_count} prompt(s)`)}`);
13674
14630
  } catch (e) {
13675
14631
  handleError(program2, e);
13676
14632
  }
@@ -13686,15 +14642,15 @@ projectCmd.command("prompts <id>").description("List all prompts for a project (
13686
14642
  return;
13687
14643
  }
13688
14644
  if (prompts.length === 0) {
13689
- console.log(chalk4.gray("No prompts."));
14645
+ console.log(chalk6.gray("No prompts."));
13690
14646
  return;
13691
14647
  }
13692
- console.log(chalk4.bold(`Prompts for project: ${project.name}`));
14648
+ console.log(chalk6.bold(`Prompts for project: ${project.name}`));
13693
14649
  for (const p of prompts) {
13694
- const scope = p.project_id ? chalk4.cyan(" [project]") : chalk4.gray(" [global]");
14650
+ const scope = p.project_id ? chalk6.cyan(" [project]") : chalk6.gray(" [global]");
13695
14651
  console.log(fmtPrompt(p) + scope);
13696
14652
  }
13697
- console.log(chalk4.gray(`
14653
+ console.log(chalk6.gray(`
13698
14654
  ${prompts.length} prompt(s)`));
13699
14655
  } catch (e) {
13700
14656
  handleError(program2, e);
@@ -13708,14 +14664,14 @@ projectCmd.command("delete <id>").description("Delete a project (prompts become
13708
14664
  if (!opts.yes && !isJson(program2)) {
13709
14665
  const { createInterface } = await import("readline");
13710
14666
  const rl = createInterface({ input: process.stdin, output: process.stdout });
13711
- await new Promise((resolve) => {
13712
- rl.question(chalk4.yellow(`Delete project "${project.name}"? Prompts will become global. [y/N] `), (ans) => {
14667
+ await new Promise((resolve2) => {
14668
+ rl.question(chalk6.yellow(`Delete project "${project.name}"? Prompts will become global. [y/N] `), (ans) => {
13713
14669
  rl.close();
13714
14670
  if (ans.toLowerCase() !== "y") {
13715
14671
  console.log("Cancelled.");
13716
14672
  process.exit(0);
13717
14673
  }
13718
- resolve();
14674
+ resolve2();
13719
14675
  });
13720
14676
  });
13721
14677
  }
@@ -13723,7 +14679,7 @@ projectCmd.command("delete <id>").description("Delete a project (prompts become
13723
14679
  if (isJson(program2))
13724
14680
  output(program2, { deleted: true, id: project.id });
13725
14681
  else
13726
- console.log(chalk4.red(`Deleted project ${project.name}`));
14682
+ console.log(chalk6.red(`Deleted project ${project.name}`));
13727
14683
  } catch (e) {
13728
14684
  handleError(program2, e);
13729
14685
  }
@@ -13736,15 +14692,15 @@ program2.command("audit").description("Check for orphaned project refs, empty co
13736
14692
  return;
13737
14693
  }
13738
14694
  if (report.issues.length === 0) {
13739
- console.log(chalk4.green("\u2713 No audit issues found."));
14695
+ console.log(chalk6.green("\u2713 No audit issues found."));
13740
14696
  return;
13741
14697
  }
13742
14698
  for (const issue of report.issues) {
13743
- const sym = issue.severity === "error" ? chalk4.red("\u2717") : issue.severity === "warn" ? chalk4.yellow("\u26A0") : chalk4.gray("\u2139");
13744
- const slug = issue.slug ? chalk4.green(` ${issue.slug}`) : "";
14699
+ const sym = issue.severity === "error" ? chalk6.red("\u2717") : issue.severity === "warn" ? chalk6.yellow("\u26A0") : chalk6.gray("\u2139");
14700
+ const slug = issue.slug ? chalk6.green(` ${issue.slug}`) : "";
13745
14701
  console.log(`${sym}${slug} ${issue.message}`);
13746
14702
  }
13747
- console.log(chalk4.bold(`
14703
+ console.log(chalk6.bold(`
13748
14704
  ${report.issues.length} issue(s) \u2014 ${report.errors} errors, ${report.warnings} warnings, ${report.info} info`));
13749
14705
  if (report.errors > 0)
13750
14706
  process.exit(1);
@@ -13761,12 +14717,12 @@ program2.command("unused").description("List prompts that have never been used (
13761
14717
  return;
13762
14718
  }
13763
14719
  if (unused.length === 0) {
13764
- console.log(chalk4.green("All prompts have been used at least once."));
14720
+ console.log(chalk6.green("All prompts have been used at least once."));
13765
14721
  return;
13766
14722
  }
13767
- console.log(chalk4.bold(`Unused prompts (${unused.length}):`));
14723
+ console.log(chalk6.bold(`Unused prompts (${unused.length}):`));
13768
14724
  for (const p of unused) {
13769
- console.log(` ${fmtPrompt(p)} ${chalk4.gray(`created ${new Date(p.created_at).toLocaleDateString()}`)}`);
14725
+ console.log(` ${fmtPrompt(p)} ${chalk6.gray(`created ${new Date(p.created_at).toLocaleDateString()}`)}`);
13770
14726
  }
13771
14727
  } catch (e) {
13772
14728
  handleError(program2, e);
@@ -13780,12 +14736,12 @@ program2.command("trending").description("Most used prompts in the last N days")
13780
14736
  return;
13781
14737
  }
13782
14738
  if (results.length === 0) {
13783
- console.log(chalk4.gray("No usage data yet."));
14739
+ console.log(chalk6.gray("No usage data yet."));
13784
14740
  return;
13785
14741
  }
13786
- console.log(chalk4.bold(`Trending (last ${opts["days"] ?? "7"} days):`));
14742
+ console.log(chalk6.bold(`Trending (last ${opts["days"] ?? "7"} days):`));
13787
14743
  for (const r of results) {
13788
- console.log(` ${chalk4.green(r.slug)} ${chalk4.bold(String(r.uses))}\xD7 ${chalk4.gray(r.title)}`);
14744
+ console.log(` ${chalk6.green(r.slug)} ${chalk6.bold(String(r.uses))}\xD7 ${chalk6.gray(r.title)}`);
13789
14745
  }
13790
14746
  } catch (e) {
13791
14747
  handleError(program2, e);
@@ -13798,7 +14754,7 @@ program2.command("expire <id> [date]").description("Set expiry date for a prompt
13798
14754
  if (isJson(program2))
13799
14755
  output(program2, p);
13800
14756
  else
13801
- console.log(expiresAt ? chalk4.yellow(`Expires ${p.slug} on ${new Date(expiresAt).toLocaleDateString()}`) : chalk4.gray(`Cleared expiry for ${p.slug}`));
14757
+ console.log(expiresAt ? chalk6.yellow(`Expires ${p.slug} on ${new Date(expiresAt).toLocaleDateString()}`) : chalk6.gray(`Cleared expiry for ${p.slug}`));
13802
14758
  } catch (e) {
13803
14759
  handleError(program2, e);
13804
14760
  }
@@ -13821,7 +14777,7 @@ program2.command("duplicate <id>").description("Clone a prompt with a new slug")
13821
14777
  if (isJson(program2))
13822
14778
  output(program2, prompt);
13823
14779
  else
13824
- console.log(`${chalk4.green("Duplicated")} ${chalk4.bold(p.slug)} \u2192 ${chalk4.bold(prompt.slug)}`);
14780
+ console.log(`${chalk6.green("Duplicated")} ${chalk6.bold(p.slug)} \u2192 ${chalk6.bold(prompt.slug)}`);
13825
14781
  } catch (e) {
13826
14782
  handleError(program2, e);
13827
14783
  }
@@ -13834,7 +14790,7 @@ program2.command("chain <id> [next]").description("Set the next prompt in a chai
13834
14790
  if (isJson(program2))
13835
14791
  output(program2, p);
13836
14792
  else
13837
- console.log(nextSlug ? `${chalk4.green(p.slug)} \u2192 ${chalk4.bold(nextSlug)}` : chalk4.gray(`Cleared chain for ${p.slug}`));
14793
+ console.log(nextSlug ? `${chalk6.green(p.slug)} \u2192 ${chalk6.bold(nextSlug)}` : chalk6.gray(`Cleared chain for ${p.slug}`));
13838
14794
  return;
13839
14795
  }
13840
14796
  const prompt = getPrompt(id);
@@ -13852,7 +14808,7 @@ program2.command("chain <id> [next]").description("Set the next prompt in a chai
13852
14808
  output(program2, chain);
13853
14809
  return;
13854
14810
  }
13855
- console.log(chain.map((c) => chalk4.green(c.slug)).join(chalk4.gray(" \u2192 ")));
14811
+ console.log(chain.map((c) => chalk6.green(c.slug)).join(chalk6.gray(" \u2192 ")));
13856
14812
  } catch (e) {
13857
14813
  handleError(program2, e);
13858
14814
  }
@@ -13868,26 +14824,64 @@ program2.command("completion [shell]").description("Output shell completion scri
13868
14824
  }
13869
14825
  });
13870
14826
  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) => {
13871
- const { existsSync: existsSync5, mkdirSync: mkdirSync4 } = await import("fs");
13872
- const { resolve, join: join7 } = await import("path");
13873
- const watchDir = resolve(dir ?? join7(process.cwd(), ".prompts"));
14827
+ const { existsSync: existsSync5, mkdirSync: mkdirSync4, readFileSync: readFileSync3, statSync: statSync2 } = await import("fs");
14828
+ const { resolve: resolve2, join: join8, basename } = await import("path");
14829
+ const watchDir = resolve2(dir ?? join8(process.cwd(), ".prompts"));
13874
14830
  if (!existsSync5(watchDir))
13875
14831
  mkdirSync4(watchDir, { recursive: true });
13876
- console.log(chalk4.bold(`Watching ${watchDir} for .md changes\u2026`) + chalk4.gray(" (Ctrl+C to stop)"));
14832
+ console.log(chalk6.bold(`Watching ${watchDir} for .md changes\u2026`) + chalk6.gray(" (Ctrl+C to stop)"));
13877
14833
  const { importFromMarkdown: importFromMarkdown2 } = await Promise.resolve().then(() => (init_importer(), exports_importer));
13878
- const { readFileSync: readFileSync2 } = await import("fs");
14834
+ const { getPrompt: getPrompt2 } = await Promise.resolve().then(() => (init_prompts(), exports_prompts));
14835
+ const { searchPrompts: searchPrompts2 } = await Promise.resolve().then(() => (init_search(), exports_search));
14836
+ const knownFiles = new Map;
14837
+ try {
14838
+ const { readdirSync: readdirSync4 } = await import("fs");
14839
+ for (const f of readdirSync4(watchDir)) {
14840
+ if (f.endsWith(".md"))
14841
+ knownFiles.set(f, f.replace(/\.md$/, "").toLowerCase().replace(/[^a-z0-9]+/g, "-"));
14842
+ }
14843
+ } catch {}
13879
14844
  const fsWatch = (await import("fs")).watch;
13880
14845
  fsWatch(watchDir, { persistent: true }, async (_event, filename) => {
13881
14846
  if (!filename?.endsWith(".md"))
13882
14847
  return;
13883
- const filePath = join7(watchDir, filename);
14848
+ const filePath = join8(watchDir, filename);
14849
+ const ts = chalk6.gray(new Date().toLocaleTimeString());
13884
14850
  try {
13885
- const content = readFileSync2(filePath, "utf-8");
14851
+ const exists = existsSync5(filePath) && (() => {
14852
+ try {
14853
+ statSync2(filePath);
14854
+ return true;
14855
+ } catch {
14856
+ return false;
14857
+ }
14858
+ })();
14859
+ if (!exists) {
14860
+ const slug = knownFiles.get(filename);
14861
+ if (slug) {
14862
+ const prompt = getPrompt2(slug) ?? searchPrompts2(slug).find((r) => r.prompt.slug === slug)?.prompt;
14863
+ if (prompt) {
14864
+ const { updatePrompt: updatePrompt2 } = await Promise.resolve().then(() => (init_prompts(), exports_prompts));
14865
+ updatePrompt2(prompt.id, {
14866
+ description: `[watch: source file deleted at ${new Date().toISOString()}] ${prompt.description ?? ""}`.trim(),
14867
+ tags: [...new Set([...prompt.tags, "watch-deleted"])]
14868
+ });
14869
+ console.log(`${chalk6.red("Deleted")} ${chalk6.bold(filename.replace(".md", ""))} ${ts}`);
14870
+ }
14871
+ knownFiles.delete(filename);
14872
+ }
14873
+ return;
14874
+ }
14875
+ const content = readFileSync3(filePath, "utf-8");
13886
14876
  const result = importFromMarkdown2([{ filename, content }], opts["agent"]);
13887
- const action = result.created > 0 ? chalk4.green("Created") : chalk4.yellow("Updated");
13888
- console.log(`${action}: ${chalk4.bold(filename.replace(".md", ""))} ${chalk4.gray(new Date().toLocaleTimeString())}`);
14877
+ const action = result.created > 0 ? chalk6.green("Created") : chalk6.yellow("Updated");
14878
+ if (result.created > 0 || result.updated > 0) {
14879
+ const slug = basename(filename, ".md").toLowerCase().replace(/[^a-z0-9]+/g, "-");
14880
+ knownFiles.set(filename, slug);
14881
+ }
14882
+ console.log(`${action}: ${chalk6.bold(filename.replace(".md", ""))} ${ts}`);
13889
14883
  } catch {
13890
- console.error(chalk4.red(`Failed to import: ${filename}`));
14884
+ console.error(chalk6.red(`Failed to import: ${filename}`));
13891
14885
  }
13892
14886
  });
13893
14887
  await new Promise(() => {});
@@ -13900,17 +14894,17 @@ program2.command("import-slash-commands").description("Auto-scan .claude/command
13900
14894
  return;
13901
14895
  }
13902
14896
  if (scanned.length === 0) {
13903
- console.log(chalk4.gray("No slash command files found."));
14897
+ console.log(chalk6.gray("No slash command files found."));
13904
14898
  return;
13905
14899
  }
13906
- console.log(chalk4.bold(`Scanned ${scanned.length} file(s):`));
14900
+ console.log(chalk6.bold(`Scanned ${scanned.length} file(s):`));
13907
14901
  for (const s of scanned)
13908
- console.log(chalk4.gray(` ${s.source}/${s.file}`));
14902
+ console.log(chalk6.gray(` ${s.source}/${s.file}`));
13909
14903
  console.log(`
13910
- ${chalk4.green(`Created: ${imported.created}`)} ${chalk4.yellow(`Updated: ${imported.updated}`)}`);
14904
+ ${chalk6.green(`Created: ${imported.created}`)} ${chalk6.yellow(`Updated: ${imported.updated}`)}`);
13911
14905
  if (imported.errors.length > 0) {
13912
14906
  for (const e of imported.errors)
13913
- console.error(chalk4.red(` \u2717 ${e.item}: ${e.error}`));
14907
+ console.error(chalk6.red(` \u2717 ${e.item}: ${e.error}`));
13914
14908
  }
13915
14909
  } catch (e) {
13916
14910
  handleError(program2, e);
@@ -13924,14 +14918,14 @@ program2.command("remove <id>").alias("rm").alias("uninstall").description("Remo
13924
14918
  if (!opts.yes && !isJson(program2)) {
13925
14919
  const { createInterface } = await import("readline");
13926
14920
  const rl = createInterface({ input: process.stdin, output: process.stdout });
13927
- await new Promise((resolve) => {
13928
- rl.question(chalk4.yellow(`Remove "${prompt.slug}"? [y/N] `), (ans) => {
14921
+ await new Promise((resolve2) => {
14922
+ rl.question(chalk6.yellow(`Remove "${prompt.slug}"? [y/N] `), (ans) => {
13929
14923
  rl.close();
13930
14924
  if (ans.toLowerCase() !== "y") {
13931
14925
  console.log("Cancelled.");
13932
14926
  process.exit(0);
13933
14927
  }
13934
- resolve();
14928
+ resolve2();
13935
14929
  });
13936
14930
  });
13937
14931
  }
@@ -13939,7 +14933,7 @@ program2.command("remove <id>").alias("rm").alias("uninstall").description("Remo
13939
14933
  if (isJson(program2))
13940
14934
  output(program2, { deleted: true, id: prompt.id });
13941
14935
  else
13942
- console.log(chalk4.red(`Removed ${prompt.slug}`));
14936
+ console.log(chalk6.red(`Removed ${prompt.slug}`));
13943
14937
  } catch (e) {
13944
14938
  handleError(program2, e);
13945
14939
  }
@@ -13959,7 +14953,7 @@ scheduleCmd.command("add <id> <cron>").description("Schedule a prompt to run on
13959
14953
  output(program2, schedule);
13960
14954
  return;
13961
14955
  }
13962
- console.log(chalk4.green(`Scheduled "${prompt.slug}" [${schedule.id}]`));
14956
+ console.log(chalk6.green(`Scheduled "${prompt.slug}" [${schedule.id}]`));
13963
14957
  console.log(` Cron: ${cron}`);
13964
14958
  console.log(` Next run: ${schedule.next_run_at}`);
13965
14959
  } catch (e) {
@@ -13974,11 +14968,11 @@ scheduleCmd.command("list [id]").description("List schedules, optionally filtere
13974
14968
  return;
13975
14969
  }
13976
14970
  if (!schedules.length) {
13977
- console.log(chalk4.gray("No schedules."));
14971
+ console.log(chalk6.gray("No schedules."));
13978
14972
  return;
13979
14973
  }
13980
14974
  for (const s of schedules) {
13981
- console.log(`${chalk4.bold(s.id)} ${chalk4.cyan(s.prompt_slug)} cron:${s.cron} next:${s.next_run_at} runs:${s.run_count}`);
14975
+ console.log(`${chalk6.bold(s.id)} ${chalk6.cyan(s.prompt_slug)} cron:${s.cron} next:${s.next_run_at} runs:${s.run_count}`);
13982
14976
  }
13983
14977
  } catch (e) {
13984
14978
  handleError(program2, e);
@@ -13990,7 +14984,7 @@ scheduleCmd.command("remove <scheduleId>").alias("delete").description("Remove a
13990
14984
  if (isJson(program2))
13991
14985
  output(program2, { deleted: true, id: scheduleId });
13992
14986
  else
13993
- console.log(chalk4.red(`Removed schedule ${scheduleId}`));
14987
+ console.log(chalk6.red(`Removed schedule ${scheduleId}`));
13994
14988
  } catch (e) {
13995
14989
  handleError(program2, e);
13996
14990
  }
@@ -13999,7 +14993,7 @@ scheduleCmd.command("due").description("Show and execute all due schedules").opt
13999
14993
  try {
14000
14994
  const due = getDueSchedules();
14001
14995
  if (!due.length) {
14002
- console.log(chalk4.gray("No prompts due."));
14996
+ console.log(chalk6.gray("No prompts due."));
14003
14997
  return;
14004
14998
  }
14005
14999
  if (isJson(program2)) {
@@ -14007,13 +15001,13 @@ scheduleCmd.command("due").description("Show and execute all due schedules").opt
14007
15001
  return;
14008
15002
  }
14009
15003
  for (const d of due) {
14010
- console.log(chalk4.bold(`
15004
+ console.log(chalk6.bold(`
14011
15005
  [${d.id}] ${d.prompt_slug}`));
14012
- console.log(chalk4.gray(`Next run: ${d.next_run_at} | Runs: ${d.run_count}`));
14013
- console.log(chalk4.white(d.rendered));
15006
+ console.log(chalk6.gray(`Next run: ${d.next_run_at} | Runs: ${d.run_count}`));
15007
+ console.log(chalk6.white(d.rendered));
14014
15008
  }
14015
15009
  if (!opts.dryRun)
14016
- console.log(chalk4.green(`
15010
+ console.log(chalk6.green(`
14017
15011
  \u2713 Marked ${due.length} schedule(s) as ran.`));
14018
15012
  } catch (e) {
14019
15013
  handleError(program2, e);
@@ -14036,7 +15030,7 @@ scheduleCmd.command("next <cron>").description("Preview when a cron expression w
14036
15030
  output(program2, { cron, next_runs: runs });
14037
15031
  return;
14038
15032
  }
14039
- console.log(chalk4.bold(`Next ${count} runs for "${cron}":`));
15033
+ console.log(chalk6.bold(`Next ${count} runs for "${cron}":`));
14040
15034
  for (const r of runs)
14041
15035
  console.log(` ${r}`);
14042
15036
  } catch (e) {