@hasna/prompts 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAMrC,wBAAgB,SAAS,IAAI,MAAM,CAsBlC;AAED,wBAAgB,WAAW,IAAI,QAAQ,CAmBtC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAGD,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAqID,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0B5E;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAM5C;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkC3E"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAMrC,wBAAgB,SAAS,IAAI,MAAM,CAsBlC;AAED,wBAAgB,WAAW,IAAI,QAAQ,CAmBtC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAGD,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAyJD,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0B5E;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAM5C;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkC3E"}
@@ -6,6 +6,14 @@ export declare function listPrompts(filter?: ListPromptsFilter): Prompt[];
6
6
  export declare function updatePrompt(idOrSlug: string, input: UpdatePromptInput): Prompt;
7
7
  export declare function deletePrompt(idOrSlug: string): void;
8
8
  export declare function usePrompt(idOrSlug: string): Prompt;
9
+ export declare function getTrending(days?: number, limit?: number): Array<{
10
+ id: string;
11
+ slug: string;
12
+ title: string;
13
+ uses: number;
14
+ }>;
15
+ export declare function setExpiry(idOrSlug: string, expiresAt: string | null): Prompt;
16
+ export declare function setNextPrompt(idOrSlug: string, nextSlug: string | null): Prompt;
9
17
  export declare function pinPrompt(idOrSlug: string, pinned: boolean): Prompt;
10
18
  export declare function upsertPrompt(input: CreatePromptInput, force?: boolean): {
11
19
  prompt: Prompt;
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/db/prompts.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,MAAM,EACN,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EAGlB,MAAM,mBAAmB,CAAA;AA2B1B,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CA0C7D;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOzD;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAItD;AAED,wBAAgB,WAAW,CAAC,MAAM,GAAE,iBAAsB,GAAG,MAAM,EAAE,CA4CpE;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,MAAM,CA8C/E;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAInD;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQlD;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAKnE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,UAAQ,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAE,CA6BtI;AAED,wBAAgB,cAAc;;;;;YAOJ,MAAM;cAAQ,MAAM;cAAQ,MAAM;eAAS,MAAM;mBAAa,MAAM;;;YAGpE,MAAM;cAAQ,MAAM;cAAQ,MAAM;eAAS,MAAM;sBAAgB,MAAM;;;oBAG/D,MAAM;eAAS,MAAM;;;gBAGzB,MAAM;eAAS,MAAM;;EAGlD"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/db/prompts.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,MAAM,EACN,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EAGlB,MAAM,mBAAmB,CAAA;AA6B1B,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CA0C7D;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOzD;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAItD;AAED,wBAAgB,WAAW,CAAC,MAAM,GAAE,iBAAsB,GAAG,MAAM,EAAE,CA4CpE;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAiD/E;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAInD;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CASlD;AAED,wBAAgB,WAAW,CAAC,IAAI,SAAI,EAAE,KAAK,SAAK,GAAG,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAYlH;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAK5E;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAK/E;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAKnE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,UAAQ,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAE,CA6BtI;AAED,wBAAgB,cAAc;;;;;YAOJ,MAAM;cAAQ,MAAM;cAAQ,MAAM;eAAS,MAAM;mBAAa,MAAM;;;YAGpE,MAAM;cAAQ,MAAM;cAAQ,MAAM;eAAS,MAAM;sBAAgB,MAAM;;;oBAG/D,MAAM;eAAS,MAAM;;;gBAGzB,MAAM;eAAS,MAAM;;EAGlD"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { createPrompt, getPrompt, requirePrompt, listPrompts, updatePrompt, deletePrompt, usePrompt, upsertPrompt, getPromptStats, pinPrompt } from "./db/prompts.js";
1
+ export { createPrompt, getPrompt, requirePrompt, listPrompts, updatePrompt, deletePrompt, usePrompt, upsertPrompt, getPromptStats, pinPrompt, setNextPrompt } from "./db/prompts.js";
2
2
  export { listVersions, getVersion, restoreVersion } from "./db/versions.js";
3
3
  export { listCollections, getCollection, ensureCollection, movePrompt } from "./db/collections.js";
4
4
  export { registerAgent, listAgents } from "./db/agents.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AACrK,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAC3E,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAClG,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAGzF,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAG5D,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACvG,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGrD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAGzD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAGzE,YAAY,EACV,MAAM,EACN,aAAa,EACb,UAAU,EACV,KAAK,EACL,OAAO,EACP,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,WAAW,GACZ,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACpL,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAC3E,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAClG,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAGzF,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAG5D,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACvG,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGrD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAGzD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAGzE,YAAY,EACV,MAAM,EACN,aAAa,EACb,UAAU,EACV,KAAK,EACL,OAAO,EACP,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,WAAW,GACZ,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,kBAAkB,CAAA"}
package/dist/index.js CHANGED
@@ -128,6 +128,26 @@ function runMigrations(db) {
128
128
  CREATE INDEX IF NOT EXISTS idx_prompts_project_id ON prompts(project_id);
129
129
  `
130
130
  },
131
+ {
132
+ name: "005_chaining",
133
+ sql: `ALTER TABLE prompts ADD COLUMN next_prompt TEXT;`
134
+ },
135
+ {
136
+ name: "006_expiry",
137
+ sql: `ALTER TABLE prompts ADD COLUMN expires_at TEXT;`
138
+ },
139
+ {
140
+ name: "007_usage_log",
141
+ sql: `
142
+ CREATE TABLE IF NOT EXISTS usage_log (
143
+ id TEXT PRIMARY KEY,
144
+ prompt_id TEXT NOT NULL REFERENCES prompts(id) ON DELETE CASCADE,
145
+ used_at TEXT NOT NULL DEFAULT (datetime('now'))
146
+ );
147
+ CREATE INDEX IF NOT EXISTS idx_usage_log_prompt_id ON usage_log(prompt_id);
148
+ CREATE INDEX IF NOT EXISTS idx_usage_log_used_at ON usage_log(used_at);
149
+ `
150
+ },
131
151
  {
132
152
  name: "002_fts5",
133
153
  sql: `
@@ -431,6 +451,8 @@ function rowToPrompt(row) {
431
451
  tags: JSON.parse(row["tags"] || "[]"),
432
452
  variables: JSON.parse(row["variables"] || "[]"),
433
453
  pinned: Boolean(row["pinned"]),
454
+ next_prompt: row["next_prompt"] ?? null,
455
+ expires_at: row["expires_at"] ?? null,
434
456
  project_id: row["project_id"] ?? null,
435
457
  is_template: Boolean(row["is_template"]),
436
458
  source: row["source"],
@@ -529,6 +551,7 @@ function updatePrompt(idOrSlug, input) {
529
551
  description = COALESCE(?, description),
530
552
  collection = COALESCE(?, collection),
531
553
  tags = COALESCE(?, tags),
554
+ next_prompt = CASE WHEN ? IS NOT NULL THEN ? ELSE next_prompt END,
532
555
  variables = ?,
533
556
  is_template = ?,
534
557
  version = version + 1,
@@ -539,6 +562,8 @@ function updatePrompt(idOrSlug, input) {
539
562
  input.description ?? null,
540
563
  input.collection ?? null,
541
564
  input.tags ? JSON.stringify(input.tags) : null,
565
+ "next_prompt" in input ? input.next_prompt ?? "" : null,
566
+ "next_prompt" in input ? input.next_prompt ?? null : null,
542
567
  variables,
543
568
  is_template,
544
569
  prompt.id,
@@ -561,6 +586,13 @@ function usePrompt(idOrSlug) {
561
586
  const db = getDatabase();
562
587
  const prompt = requirePrompt(idOrSlug);
563
588
  db.run("UPDATE prompts SET use_count = use_count + 1, last_used_at = datetime('now') WHERE id = ?", [prompt.id]);
589
+ db.run("INSERT INTO usage_log (id, prompt_id) VALUES (?, ?)", [generateId("UL"), prompt.id]);
590
+ return requirePrompt(prompt.id);
591
+ }
592
+ function setNextPrompt(idOrSlug, nextSlug) {
593
+ const db = getDatabase();
594
+ const prompt = requirePrompt(idOrSlug);
595
+ db.run("UPDATE prompts SET next_prompt = ?, updated_at = datetime('now') WHERE id = ?", [nextSlug, prompt.id]);
564
596
  return requirePrompt(prompt.id);
565
597
  }
566
598
  function pinPrompt(idOrSlug, pinned) {
@@ -732,6 +764,8 @@ function rowToSearchResult(row, snippet) {
732
764
  tags: JSON.parse(row["tags"] || "[]"),
733
765
  variables: JSON.parse(row["variables"] || "[]"),
734
766
  pinned: Boolean(row["pinned"]),
767
+ next_prompt: row["next_prompt"] ?? null,
768
+ expires_at: row["expires_at"] ?? null,
735
769
  project_id: row["project_id"] ?? null,
736
770
  is_template: Boolean(row["is_template"]),
737
771
  source: row["source"],
@@ -860,6 +894,7 @@ export {
860
894
  upsertPrompt,
861
895
  updatePrompt,
862
896
  uniqueSlug,
897
+ setNextPrompt,
863
898
  searchPrompts,
864
899
  restoreVersion,
865
900
  requirePrompt,
@@ -0,0 +1,16 @@
1
+ export interface AuditIssue {
2
+ type: "orphaned-project" | "empty-collection" | "missing-version-history" | "near-duplicate-slug" | "expired";
3
+ severity: "error" | "warn" | "info";
4
+ prompt_id?: string;
5
+ slug?: string;
6
+ message: string;
7
+ }
8
+ export interface AuditReport {
9
+ issues: AuditIssue[];
10
+ errors: number;
11
+ warnings: number;
12
+ info: number;
13
+ checked_at: string;
14
+ }
15
+ export declare function runAudit(): AuditReport;
16
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/lib/audit.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,kBAAkB,GAAG,kBAAkB,GAAG,yBAAyB,GAAG,qBAAqB,GAAG,SAAS,CAAA;IAC7G,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;IACnC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,UAAU,EAAE,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,QAAQ,IAAI,WAAW,CAuFtC"}
@@ -0,0 +1,3 @@
1
+ export declare function generateZshCompletion(): string;
2
+ export declare function generateBashCompletion(): string;
3
+ //# sourceMappingURL=completion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completion.d.ts","sourceRoot":"","sources":["../../src/lib/completion.ts"],"names":[],"mappings":"AAYA,wBAAgB,qBAAqB,IAAI,MAAM,CAkE9C;AAED,wBAAgB,sBAAsB,IAAI,MAAM,CAgD/C"}
@@ -0,0 +1,8 @@
1
+ export interface DiffLine {
2
+ type: "added" | "removed" | "unchanged";
3
+ content: string;
4
+ lineNum?: number;
5
+ }
6
+ export declare function diffTexts(a: string, b: string): DiffLine[];
7
+ export declare function formatDiff(lines: DiffLine[]): string;
8
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/lib/diff.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,WAAW,CAAA;IACvC,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,QAAQ,EAAE,CAmC1D;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAMpD"}
@@ -31,6 +31,14 @@ export declare function importFromMarkdown(files: Array<{
31
31
  filename: string;
32
32
  content: string;
33
33
  }>, changedBy?: string): ImportResult;
34
+ export interface SlashCommandScanResult {
35
+ scanned: Array<{
36
+ source: string;
37
+ file: string;
38
+ }>;
39
+ imported: ImportResult;
40
+ }
41
+ export declare function scanAndImportSlashCommands(rootDir: string, changedBy?: string): SlashCommandScanResult;
34
42
  export declare function importFromClaudeCommands(files: Array<{
35
43
  filename: string;
36
44
  content: string;
@@ -1 +1 @@
1
- {"version":3,"file":"importer.d.ts","sourceRoot":"","sources":["../../src/lib/importer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAqB,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAElE,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC/C;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,CA0BpF;AAED,wBAAgB,YAAY,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG;IACjD,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAGA;AAaD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAYvD;AAED,wBAAgB,qBAAqB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAMvG;AAGD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CA8B1F;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,CAKxH;AAKD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,EACnD,SAAS,CAAC,EAAE,MAAM,GACjB,YAAY,CAad"}
1
+ {"version":3,"file":"importer.d.ts","sourceRoot":"","sources":["../../src/lib/importer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAqB,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAElE,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC/C;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,CA0BpF;AAED,wBAAgB,YAAY,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG;IACjD,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAGA;AAaD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAYvD;AAED,wBAAgB,qBAAqB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAMvG;AAGD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CA8B1F;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,CAKxH;AAGD,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAChD,QAAQ,EAAE,YAAY,CAAA;CACvB;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,GACjB,sBAAsB,CAgDxB;AAKD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,EACnD,SAAS,CAAC,EAAE,MAAM,GACjB,YAAY,CAad"}
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/lib/search.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAwCxE,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,IAAI,CAAC,iBAAiB,EAAE,GAAG,CAAM,GACxC,YAAY,EAAE,CAwEhB;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,YAAY,EAAE,CAmCvE"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/lib/search.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AA0CxE,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,IAAI,CAAC,iBAAiB,EAAE,GAAG,CAAM,GACxC,YAAY,EAAE,CAwEhB;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,YAAY,EAAE,CAmCvE"}
package/dist/mcp/index.js CHANGED
@@ -4118,6 +4118,26 @@ function runMigrations(db) {
4118
4118
  CREATE INDEX IF NOT EXISTS idx_prompts_project_id ON prompts(project_id);
4119
4119
  `
4120
4120
  },
4121
+ {
4122
+ name: "005_chaining",
4123
+ sql: `ALTER TABLE prompts ADD COLUMN next_prompt TEXT;`
4124
+ },
4125
+ {
4126
+ name: "006_expiry",
4127
+ sql: `ALTER TABLE prompts ADD COLUMN expires_at TEXT;`
4128
+ },
4129
+ {
4130
+ name: "007_usage_log",
4131
+ sql: `
4132
+ CREATE TABLE IF NOT EXISTS usage_log (
4133
+ id TEXT PRIMARY KEY,
4134
+ prompt_id TEXT NOT NULL REFERENCES prompts(id) ON DELETE CASCADE,
4135
+ used_at TEXT NOT NULL DEFAULT (datetime('now'))
4136
+ );
4137
+ CREATE INDEX IF NOT EXISTS idx_usage_log_prompt_id ON usage_log(prompt_id);
4138
+ CREATE INDEX IF NOT EXISTS idx_usage_log_used_at ON usage_log(used_at);
4139
+ `
4140
+ },
4121
4141
  {
4122
4142
  name: "002_fts5",
4123
4143
  sql: `
@@ -4413,6 +4433,8 @@ function rowToPrompt(row) {
4413
4433
  tags: JSON.parse(row["tags"] || "[]"),
4414
4434
  variables: JSON.parse(row["variables"] || "[]"),
4415
4435
  pinned: Boolean(row["pinned"]),
4436
+ next_prompt: row["next_prompt"] ?? null,
4437
+ expires_at: row["expires_at"] ?? null,
4416
4438
  project_id: row["project_id"] ?? null,
4417
4439
  is_template: Boolean(row["is_template"]),
4418
4440
  source: row["source"],
@@ -4511,6 +4533,7 @@ function updatePrompt(idOrSlug, input) {
4511
4533
  description = COALESCE(?, description),
4512
4534
  collection = COALESCE(?, collection),
4513
4535
  tags = COALESCE(?, tags),
4536
+ next_prompt = CASE WHEN ? IS NOT NULL THEN ? ELSE next_prompt END,
4514
4537
  variables = ?,
4515
4538
  is_template = ?,
4516
4539
  version = version + 1,
@@ -4521,6 +4544,8 @@ function updatePrompt(idOrSlug, input) {
4521
4544
  input.description ?? null,
4522
4545
  input.collection ?? null,
4523
4546
  input.tags ? JSON.stringify(input.tags) : null,
4547
+ "next_prompt" in input ? input.next_prompt ?? "" : null,
4548
+ "next_prompt" in input ? input.next_prompt ?? null : null,
4524
4549
  variables,
4525
4550
  is_template,
4526
4551
  prompt.id,
@@ -4543,6 +4568,30 @@ function usePrompt(idOrSlug) {
4543
4568
  const db = getDatabase();
4544
4569
  const prompt = requirePrompt(idOrSlug);
4545
4570
  db.run("UPDATE prompts SET use_count = use_count + 1, last_used_at = datetime('now') WHERE id = ?", [prompt.id]);
4571
+ db.run("INSERT INTO usage_log (id, prompt_id) VALUES (?, ?)", [generateId("UL"), prompt.id]);
4572
+ return requirePrompt(prompt.id);
4573
+ }
4574
+ function getTrending(days = 7, limit = 10) {
4575
+ const db = getDatabase();
4576
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
4577
+ return db.query(`SELECT p.id, p.slug, p.title, COUNT(ul.id) as uses
4578
+ FROM usage_log ul
4579
+ JOIN prompts p ON p.id = ul.prompt_id
4580
+ WHERE ul.used_at >= ?
4581
+ GROUP BY p.id
4582
+ ORDER BY uses DESC
4583
+ LIMIT ?`).all(cutoff, limit);
4584
+ }
4585
+ function setExpiry(idOrSlug, expiresAt) {
4586
+ const db = getDatabase();
4587
+ const prompt = requirePrompt(idOrSlug);
4588
+ db.run("UPDATE prompts SET expires_at = ?, updated_at = datetime('now') WHERE id = ?", [expiresAt, prompt.id]);
4589
+ return requirePrompt(prompt.id);
4590
+ }
4591
+ function setNextPrompt(idOrSlug, nextSlug) {
4592
+ const db = getDatabase();
4593
+ const prompt = requirePrompt(idOrSlug);
4594
+ db.run("UPDATE prompts SET next_prompt = ?, updated_at = datetime('now') WHERE id = ?", [nextSlug, prompt.id]);
4546
4595
  return requirePrompt(prompt.id);
4547
4596
  }
4548
4597
  function pinPrompt(idOrSlug, pinned) {
@@ -4713,6 +4762,8 @@ function rowToSearchResult(row, snippet) {
4713
4762
  tags: JSON.parse(row["tags"] || "[]"),
4714
4763
  variables: JSON.parse(row["variables"] || "[]"),
4715
4764
  pinned: Boolean(row["pinned"]),
4765
+ next_prompt: row["next_prompt"] ?? null,
4766
+ expires_at: row["expires_at"] ?? null,
4716
4767
  project_id: row["project_id"] ?? null,
4717
4768
  is_template: Boolean(row["is_template"]),
4718
4769
  source: row["source"],
@@ -4836,6 +4887,77 @@ function exportToJson(collection) {
4836
4887
  const prompts = listPrompts({ collection, limit: 1e4 });
4837
4888
  return { prompts, exported_at: new Date().toISOString(), collection };
4838
4889
  }
4890
+ function markdownToImportItem(content, filename) {
4891
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n+([\s\S]*)$/);
4892
+ if (!frontmatterMatch) {
4893
+ if (!filename)
4894
+ return null;
4895
+ const title2 = filename.replace(/\.md$/, "").replace(/-/g, " ");
4896
+ return { title: title2, body: content.trim() };
4897
+ }
4898
+ const frontmatter = frontmatterMatch[1] ?? "";
4899
+ const body = (frontmatterMatch[2] ?? "").trim();
4900
+ const get = (key) => {
4901
+ const m = frontmatter.match(new RegExp(`^${key}:\\s*(.+)$`, "m"));
4902
+ return m ? (m[1] ?? "").trim() : null;
4903
+ };
4904
+ const title = get("title") ?? (filename?.replace(/\.md$/, "").replace(/-/g, " ") ?? "Untitled");
4905
+ const slug = get("slug") ?? undefined;
4906
+ const collection = get("collection") ?? undefined;
4907
+ const description = get("description") ?? undefined;
4908
+ const tagsStr = get("tags");
4909
+ let tags;
4910
+ if (tagsStr) {
4911
+ const inner = tagsStr.replace(/^\[/, "").replace(/\]$/, "");
4912
+ tags = inner.split(",").map((t) => t.trim()).filter(Boolean);
4913
+ }
4914
+ return { title, slug, body, collection, tags, description };
4915
+ }
4916
+ function scanAndImportSlashCommands(rootDir, changedBy) {
4917
+ const { existsSync: existsSync2, readdirSync, readFileSync } = __require("fs");
4918
+ const { join: join2 } = __require("path");
4919
+ const home = process.env["HOME"] ?? "~";
4920
+ const sources = [
4921
+ { dir: join2(rootDir, ".claude", "commands"), collection: "claude-commands", tags: ["claude", "slash-command"] },
4922
+ { dir: join2(home, ".claude", "commands"), collection: "claude-commands", tags: ["claude", "slash-command"] },
4923
+ { dir: join2(rootDir, ".codex", "skills"), collection: "codex-skills", tags: ["codex", "skill"] },
4924
+ { dir: join2(home, ".codex", "skills"), collection: "codex-skills", tags: ["codex", "skill"] },
4925
+ { dir: join2(rootDir, ".gemini", "extensions"), collection: "gemini-extensions", tags: ["gemini", "extension"] },
4926
+ { dir: join2(home, ".gemini", "extensions"), collection: "gemini-extensions", tags: ["gemini", "extension"] }
4927
+ ];
4928
+ const files = [];
4929
+ const scanned = [];
4930
+ for (const { dir, collection, tags } of sources) {
4931
+ if (!existsSync2(dir))
4932
+ continue;
4933
+ let entries;
4934
+ try {
4935
+ entries = readdirSync(dir);
4936
+ } catch {
4937
+ continue;
4938
+ }
4939
+ for (const entry of entries) {
4940
+ if (!entry.endsWith(".md"))
4941
+ continue;
4942
+ const filePath = join2(dir, entry);
4943
+ try {
4944
+ const content = readFileSync(filePath, "utf-8");
4945
+ files.push({ filename: entry, content, collection, tags });
4946
+ scanned.push({ source: dir, file: entry });
4947
+ } catch {}
4948
+ }
4949
+ }
4950
+ const items = files.map((f) => {
4951
+ const base = markdownToImportItem(f.content, f.filename);
4952
+ if (base)
4953
+ return { ...base, collection: base.collection ?? f.collection, tags: base.tags ?? f.tags };
4954
+ const name = f.filename.replace(/\.md$/, "");
4955
+ const title = name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
4956
+ return { title, slug: name, body: f.content.trim(), collection: f.collection, tags: f.tags };
4957
+ });
4958
+ const imported = importFromJson(items, changedBy);
4959
+ return { scanned, imported };
4960
+ }
4839
4961
 
4840
4962
  // src/lib/mementos.ts
4841
4963
  async function maybeSaveMemento(opts) {
@@ -4861,6 +4983,48 @@ async function maybeSaveMemento(opts) {
4861
4983
  } catch {}
4862
4984
  }
4863
4985
 
4986
+ // src/lib/diff.ts
4987
+ function diffTexts(a, b) {
4988
+ const aLines = a.split(`
4989
+ `);
4990
+ const bLines = b.split(`
4991
+ `);
4992
+ const m = aLines.length;
4993
+ const n = bLines.length;
4994
+ const lcs = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
4995
+ for (let i2 = 1;i2 <= m; i2++) {
4996
+ for (let j2 = 1;j2 <= n; j2++) {
4997
+ lcs[i2][j2] = aLines[i2 - 1] === bLines[j2 - 1] ? (lcs[i2 - 1][j2 - 1] ?? 0) + 1 : Math.max(lcs[i2 - 1][j2] ?? 0, lcs[i2][j2 - 1] ?? 0);
4998
+ }
4999
+ }
5000
+ const trace = [];
5001
+ let i = m, j = n;
5002
+ while (i > 0 || j > 0) {
5003
+ if (i > 0 && j > 0 && aLines[i - 1] === bLines[j - 1]) {
5004
+ trace.unshift({ type: "unchanged", content: aLines[i - 1] ?? "" });
5005
+ i--;
5006
+ j--;
5007
+ } else if (j > 0 && (i === 0 || (lcs[i][j - 1] ?? 0) >= (lcs[i - 1][j] ?? 0))) {
5008
+ trace.unshift({ type: "added", content: bLines[j - 1] ?? "" });
5009
+ j--;
5010
+ } else {
5011
+ trace.unshift({ type: "removed", content: aLines[i - 1] ?? "" });
5012
+ i--;
5013
+ }
5014
+ }
5015
+ return trace;
5016
+ }
5017
+ function formatDiff(lines) {
5018
+ return lines.map((l) => {
5019
+ if (l.type === "added")
5020
+ return `+ ${l.content}`;
5021
+ if (l.type === "removed")
5022
+ return `- ${l.content}`;
5023
+ return ` ${l.content}`;
5024
+ }).join(`
5025
+ `);
5026
+ }
5027
+
4864
5028
  // src/lib/lint.ts
4865
5029
  function lintPrompt(p) {
4866
5030
  const issues = [];
@@ -4895,6 +5059,83 @@ function lintAll(prompts) {
4895
5059
  return prompts.map((p) => ({ prompt: p, issues: lintPrompt(p) })).filter((r) => r.issues.length > 0);
4896
5060
  }
4897
5061
 
5062
+ // src/lib/audit.ts
5063
+ function runAudit() {
5064
+ const db = getDatabase();
5065
+ const issues = [];
5066
+ const orphaned = db.query(`
5067
+ SELECT p.id, p.slug FROM prompts p
5068
+ WHERE p.project_id IS NOT NULL
5069
+ AND NOT EXISTS (SELECT 1 FROM projects pr WHERE pr.id = p.project_id)
5070
+ `).all();
5071
+ for (const p of orphaned) {
5072
+ issues.push({
5073
+ type: "orphaned-project",
5074
+ severity: "error",
5075
+ prompt_id: p.id,
5076
+ slug: p.slug,
5077
+ message: `Prompt "${p.slug}" references a deleted project`
5078
+ });
5079
+ }
5080
+ const emptyCollections = db.query(`
5081
+ SELECT c.name FROM collections c
5082
+ WHERE NOT EXISTS (SELECT 1 FROM prompts p WHERE p.collection = c.name)
5083
+ AND c.name != 'default'
5084
+ `).all();
5085
+ for (const c of emptyCollections) {
5086
+ issues.push({
5087
+ type: "empty-collection",
5088
+ severity: "info",
5089
+ message: `Collection "${c.name}" has no prompts`
5090
+ });
5091
+ }
5092
+ const missingHistory = db.query(`
5093
+ SELECT p.id, p.slug FROM prompts p
5094
+ WHERE NOT EXISTS (SELECT 1 FROM prompt_versions v WHERE v.prompt_id = p.id)
5095
+ `).all();
5096
+ for (const p of missingHistory) {
5097
+ issues.push({
5098
+ type: "missing-version-history",
5099
+ severity: "warn",
5100
+ prompt_id: p.id,
5101
+ slug: p.slug,
5102
+ message: `Prompt "${p.slug}" has no version history entries`
5103
+ });
5104
+ }
5105
+ const slugs = db.query("SELECT id, slug FROM prompts").all();
5106
+ const seen = new Map;
5107
+ for (const { id, slug } of slugs) {
5108
+ const base = slug.replace(/-\d+$/, "");
5109
+ if (seen.has(base) && seen.get(base) !== id) {
5110
+ issues.push({
5111
+ type: "near-duplicate-slug",
5112
+ severity: "info",
5113
+ slug,
5114
+ message: `"${slug}" looks like a duplicate of "${base}" \u2014 consider merging`
5115
+ });
5116
+ } else {
5117
+ seen.set(base, id);
5118
+ }
5119
+ }
5120
+ const now = new Date().toISOString();
5121
+ const expired = db.query(`
5122
+ SELECT id, slug FROM prompts WHERE expires_at IS NOT NULL AND expires_at < ?
5123
+ `).all(now);
5124
+ for (const p of expired) {
5125
+ issues.push({
5126
+ type: "expired",
5127
+ severity: "warn",
5128
+ prompt_id: p.id,
5129
+ slug: p.slug,
5130
+ message: `Prompt "${p.slug}" has expired`
5131
+ });
5132
+ }
5133
+ const errors2 = issues.filter((i) => i.severity === "error").length;
5134
+ const warnings = issues.filter((i) => i.severity === "warn").length;
5135
+ const info = issues.filter((i) => i.severity === "info").length;
5136
+ return { issues, errors: errors2, warnings, info, checked_at: new Date().toISOString() };
5137
+ }
5138
+
4898
5139
  // src/mcp/index.ts
4899
5140
  var server = new McpServer({ name: "open-prompts", version: "0.1.0" });
4900
5141
  function ok(data) {
@@ -5128,6 +5369,17 @@ server.registerTool("prompts_import", {
5128
5369
  const results = importFromJson(prompts, changed_by);
5129
5370
  return ok(results);
5130
5371
  });
5372
+ server.registerTool("prompts_import_slash_commands", {
5373
+ description: "Auto-scan .claude/commands, .codex/skills, .gemini/extensions (both project and home dir) and import all .md files as prompts.",
5374
+ inputSchema: {
5375
+ dir: exports_external.string().optional().describe("Root directory to scan (default: cwd)"),
5376
+ changed_by: exports_external.string().optional()
5377
+ }
5378
+ }, async ({ dir, changed_by }) => {
5379
+ const rootDir = dir ?? process.cwd();
5380
+ const result = scanAndImportSlashCommands(rootDir, changed_by);
5381
+ return ok(result);
5382
+ });
5131
5383
  server.registerTool("prompts_update", {
5132
5384
  description: "Update an existing prompt's fields.",
5133
5385
  inputSchema: {
@@ -5183,9 +5435,10 @@ server.registerTool("prompts_save_from_session", {
5183
5435
  collection: exports_external.string().optional().describe("Collection to save into (default: 'sessions')"),
5184
5436
  description: exports_external.string().optional().describe("One-line description of what this prompt does"),
5185
5437
  agent: exports_external.string().optional().describe("Agent name saving this prompt"),
5186
- project: exports_external.string().optional().describe("Project name, slug, or ID to scope this prompt to")
5438
+ project: exports_external.string().optional().describe("Project name, slug, or ID to scope this prompt to"),
5439
+ pin: exports_external.boolean().optional().describe("Pin the prompt immediately so it surfaces first in all lists")
5187
5440
  }
5188
- }, async ({ title, body, slug, tags, collection, description, agent, project }) => {
5441
+ }, async ({ title, body, slug, tags, collection, description, agent, project, pin }) => {
5189
5442
  try {
5190
5443
  let project_id;
5191
5444
  if (project) {
@@ -5206,7 +5459,118 @@ server.registerTool("prompts_save_from_session", {
5206
5459
  changed_by: agent,
5207
5460
  project_id
5208
5461
  });
5209
- return ok({ ...prompt, _created: created, _tip: created ? `Saved as "${prompt.slug}". Use prompts_use("${prompt.slug}") to retrieve it.` : `Updated existing prompt "${prompt.slug}".` });
5462
+ if (pin)
5463
+ pinPrompt(prompt.id, true);
5464
+ const final = pin ? { ...prompt, pinned: true } : prompt;
5465
+ return ok({ ...final, _created: created, _tip: created ? `Saved as "${prompt.slug}". Use prompts_use("${prompt.slug}") to retrieve it.` : `Updated existing prompt "${prompt.slug}".` });
5466
+ } catch (e) {
5467
+ return err(e instanceof Error ? e.message : String(e));
5468
+ }
5469
+ });
5470
+ server.registerTool("prompts_audit", {
5471
+ description: "Run a full audit: orphaned project refs, empty collections, missing version history, near-duplicate slugs, expired prompts.",
5472
+ inputSchema: {}
5473
+ }, async () => ok(runAudit()));
5474
+ server.registerTool("prompts_unused", {
5475
+ description: "List prompts with use_count = 0 \u2014 never used. Good for library cleanup.",
5476
+ inputSchema: { collection: exports_external.string().optional(), limit: exports_external.number().optional().default(50) }
5477
+ }, async ({ collection, limit }) => {
5478
+ const all = listPrompts({ collection, limit: 1e4 });
5479
+ const unused = all.filter((p) => p.use_count === 0).slice(0, limit);
5480
+ return ok({ unused, count: unused.length });
5481
+ });
5482
+ server.registerTool("prompts_trending", {
5483
+ description: "Get most-used prompts in the last N days based on per-use log.",
5484
+ inputSchema: {
5485
+ days: exports_external.number().optional().default(7),
5486
+ limit: exports_external.number().optional().default(10)
5487
+ }
5488
+ }, async ({ days, limit }) => ok(getTrending(days, limit)));
5489
+ server.registerTool("prompts_set_expiry", {
5490
+ description: "Set or clear an expiry date on a prompt. Pass expires_at=null to clear.",
5491
+ inputSchema: {
5492
+ id: exports_external.string(),
5493
+ expires_at: exports_external.string().nullable().describe("ISO date string (e.g. 2026-12-31) or null to clear")
5494
+ }
5495
+ }, async ({ id, expires_at }) => {
5496
+ try {
5497
+ return ok(setExpiry(id, expires_at));
5498
+ } catch (e) {
5499
+ return err(e instanceof Error ? e.message : String(e));
5500
+ }
5501
+ });
5502
+ server.registerTool("prompts_duplicate", {
5503
+ description: "Clone a prompt with a new slug. Copies body, tags, collection, description. Version resets to 1.",
5504
+ inputSchema: {
5505
+ id: exports_external.string(),
5506
+ slug: exports_external.string().optional().describe("New slug (auto-generated if omitted)"),
5507
+ title: exports_external.string().optional().describe("New title (defaults to 'Copy of <original>')")
5508
+ }
5509
+ }, async ({ id, slug, title }) => {
5510
+ try {
5511
+ const source = getPrompt(id);
5512
+ if (!source)
5513
+ return err(`Prompt not found: ${id}`);
5514
+ const { prompt } = upsertPrompt({
5515
+ title: title ?? `Copy of ${source.title}`,
5516
+ slug,
5517
+ body: source.body,
5518
+ description: source.description ?? undefined,
5519
+ collection: source.collection,
5520
+ tags: source.tags,
5521
+ source: "manual"
5522
+ });
5523
+ return ok(prompt);
5524
+ } catch (e) {
5525
+ return err(e instanceof Error ? e.message : String(e));
5526
+ }
5527
+ });
5528
+ server.registerTool("prompts_diff", {
5529
+ description: "Show a line diff between two versions of a prompt body. v2 defaults to current version.",
5530
+ inputSchema: {
5531
+ id: exports_external.string(),
5532
+ v1: exports_external.number().describe("First version number"),
5533
+ v2: exports_external.number().optional().describe("Second version (default: current)")
5534
+ }
5535
+ }, async ({ id, v1, v2 }) => {
5536
+ try {
5537
+ const prompt = getPrompt(id);
5538
+ if (!prompt)
5539
+ return err(`Prompt not found: ${id}`);
5540
+ const versions = listVersions(prompt.id);
5541
+ const versionA = versions.find((v) => v.version === v1);
5542
+ if (!versionA)
5543
+ return err(`Version ${v1} not found`);
5544
+ const bodyB = v2 ? versions.find((v) => v.version === v2)?.body ?? null : prompt.body;
5545
+ if (bodyB === null)
5546
+ return err(`Version ${v2} not found`);
5547
+ const lines = diffTexts(versionA.body, bodyB);
5548
+ return ok({ lines, formatted: formatDiff(lines), v1, v2: v2 ?? prompt.version });
5549
+ } catch (e) {
5550
+ return err(e instanceof Error ? e.message : String(e));
5551
+ }
5552
+ });
5553
+ server.registerTool("prompts_chain", {
5554
+ description: "Set or get the next prompt in a chain. After using prompt A, the agent is suggested prompt B. Pass next_prompt=null to clear.",
5555
+ inputSchema: {
5556
+ id: exports_external.string().describe("Prompt ID or slug"),
5557
+ next_prompt: exports_external.string().nullable().optional().describe("Slug of the next prompt in the chain, or null to clear")
5558
+ }
5559
+ }, async ({ id, next_prompt }) => {
5560
+ try {
5561
+ if (next_prompt !== undefined) {
5562
+ const p = setNextPrompt(id, next_prompt ?? null);
5563
+ return ok(p);
5564
+ }
5565
+ const chain = [];
5566
+ let cur = getPrompt(id);
5567
+ const seen = new Set;
5568
+ while (cur && !seen.has(cur.id)) {
5569
+ chain.push({ id: cur.id, slug: cur.slug, title: cur.title });
5570
+ seen.add(cur.id);
5571
+ cur = cur.next_prompt ? getPrompt(cur.next_prompt) : null;
5572
+ }
5573
+ return ok(chain);
5210
5574
  } catch (e) {
5211
5575
  return err(e instanceof Error ? e.message : String(e));
5212
5576
  }