@sreetej510/pi-prompt-manager 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +74 -0
- package/package.json +44 -0
- package/src/command.ts +137 -0
- package/src/component.ts +130 -0
- package/src/index.ts +25 -0
- package/src/storage.ts +28 -0
- package/src/types.ts +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 omega
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# @sreetej510/pi-prompt-manager
|
|
2
|
+
|
|
3
|
+
A [pi](https://github.com/earendil-works/pi) coding agent extension to quickly save, manage,
|
|
4
|
+
and paste reusable prompts without retyping them.
|
|
5
|
+
|
|
6
|
+
## Commands
|
|
7
|
+
|
|
8
|
+
| Command | Effect |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `/prompt` | Open the prompt manager (select · edit · delete · add) |
|
|
11
|
+
| `/prompt add [name]` | Create a new saved prompt directly (opens a multi-line editor) |
|
|
12
|
+
|
|
13
|
+
## Keyboard shortcuts (inside `/prompt`)
|
|
14
|
+
|
|
15
|
+
| Key | Action |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `↑` / `↓` / `j` / `k` | Navigate the list |
|
|
18
|
+
| `Enter` | Paste the selected prompt into the editor |
|
|
19
|
+
| `e` | Edit the selected prompt |
|
|
20
|
+
| `d` | Delete the selected prompt (with confirmation) |
|
|
21
|
+
| `a` | Add a new prompt |
|
|
22
|
+
| `Esc` | Close without action |
|
|
23
|
+
|
|
24
|
+
## Storage
|
|
25
|
+
|
|
26
|
+
Saved prompts are stored as JSON at `<pi-agent-dir>/prompt-manager.json`:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"prompts": [
|
|
31
|
+
{ "id": "...", "name": "...", "content": "...", "createdAt": 0, "updatedAt": 0 }
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install -g @sreetej510/pi-prompt-manager
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then add it to your pi `settings.json`:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"packages": ["npm:@sreetej510/pi-prompt-manager"]
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or, for local development, point at the file directly:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"extensions": ["/absolute/path/to/pi-extensions/extensions/pi-prompt-manager/src/index.ts"]
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## File layout
|
|
59
|
+
|
|
60
|
+
| File | Responsibility |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `src/index.ts` | Extension entry point |
|
|
63
|
+
| `src/command.ts` | `/prompt` command handler + add/edit/delete flow |
|
|
64
|
+
| `src/component.ts` | List-manager TUI component |
|
|
65
|
+
| `src/storage.ts` | `prompt-manager.json` load/save helpers |
|
|
66
|
+
| `src/types.ts` | Shared TypeScript types |
|
|
67
|
+
|
|
68
|
+
## Development
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm install
|
|
72
|
+
npm run --workspace @sreetej510/pi-prompt-manager check # biome + typecheck
|
|
73
|
+
npm run --workspace @sreetej510/pi-prompt-manager format
|
|
74
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sreetej510/pi-prompt-manager",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Pi extension to quickly save, manage, and paste reusable prompts without retyping.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"private": false,
|
|
8
|
+
"keywords": [
|
|
9
|
+
"pi-package",
|
|
10
|
+
"pi-extension",
|
|
11
|
+
"pi",
|
|
12
|
+
"prompts",
|
|
13
|
+
"snippets"
|
|
14
|
+
],
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"pi": {
|
|
21
|
+
"extensions": [
|
|
22
|
+
"./src/index.ts"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"check": "biome check . && npm run typecheck",
|
|
27
|
+
"format": "biome check --write .",
|
|
28
|
+
"typecheck": "tsc --noEmit"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@biomejs/biome": "^2.5.3",
|
|
32
|
+
"@earendil-works/pi-coding-agent": "^0.80.3",
|
|
33
|
+
"@earendil-works/pi-tui": "^0.80.3",
|
|
34
|
+
"typescript": "^5.7.3"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/Sreetej510/pi-extensions",
|
|
39
|
+
"directory": "extensions/pi-prompt-manager"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/command.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { PromptManagerComponent } from "./component.js";
|
|
3
|
+
import { loadPrompts, newId, savePrompts } from "./storage.js";
|
|
4
|
+
import type { ListResult } from "./types.js";
|
|
5
|
+
|
|
6
|
+
type PromptUi = ExtensionCommandContext["ui"];
|
|
7
|
+
|
|
8
|
+
async function addPrompt(ui: PromptUi, nameHint?: string): Promise<void> {
|
|
9
|
+
let name = nameHint?.trim();
|
|
10
|
+
|
|
11
|
+
if (!name) {
|
|
12
|
+
const entered = await ui.input("Prompt name:", "e.g. Code Review Template");
|
|
13
|
+
if (!entered?.trim()) {
|
|
14
|
+
ui.notify("Cancelled — no name given.", "info");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
name = entered.trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const content = await ui.editor(`New prompt: ${name}`, "");
|
|
21
|
+
if (!content?.trim()) {
|
|
22
|
+
ui.notify("Cancelled — empty content not saved.", "info");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const all = await loadPrompts();
|
|
27
|
+
const existIdx = all.findIndex((p) => p.name.toLowerCase() === name!.toLowerCase());
|
|
28
|
+
|
|
29
|
+
if (existIdx >= 0) {
|
|
30
|
+
const ok = await ui.confirm("Name already exists", `A prompt named "${name}" already exists. Overwrite it?`);
|
|
31
|
+
if (!ok) return;
|
|
32
|
+
all[existIdx] = {
|
|
33
|
+
...all[existIdx]!,
|
|
34
|
+
content: content.trim(),
|
|
35
|
+
updatedAt: Date.now(),
|
|
36
|
+
};
|
|
37
|
+
} else {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
all.push({
|
|
40
|
+
id: newId(),
|
|
41
|
+
name,
|
|
42
|
+
content: content.trim(),
|
|
43
|
+
createdAt: now,
|
|
44
|
+
updatedAt: now,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await savePrompts(all);
|
|
49
|
+
ui.notify(`Prompt "${name}" saved.`, "info");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function registerPromptCommand(pi: ExtensionAPI): void {
|
|
53
|
+
pi.registerCommand("prompt", {
|
|
54
|
+
description: "Manage saved prompts — /prompt | /prompt add [name]",
|
|
55
|
+
|
|
56
|
+
getArgumentCompletions: (prefix) => {
|
|
57
|
+
const opts = [{ value: "add", label: "add — create a new prompt" }];
|
|
58
|
+
const filtered = opts.filter((o) => o.value.startsWith(prefix));
|
|
59
|
+
return filtered.length > 0 ? filtered : null;
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
handler: async (args, ctx) => {
|
|
63
|
+
if (ctx.mode !== "tui") {
|
|
64
|
+
ctx.ui.notify("/prompt requires interactive mode", "error");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const ui = ctx.ui;
|
|
69
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
70
|
+
const sub = parts[0] ?? "";
|
|
71
|
+
|
|
72
|
+
if (sub === "add") {
|
|
73
|
+
const nameArg = parts.slice(1).join(" ") || undefined;
|
|
74
|
+
await addPrompt(ui, nameArg);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (sub !== "") {
|
|
79
|
+
ui.notify("Usage: /prompt | /prompt add [name]", "warning");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let all = await loadPrompts();
|
|
84
|
+
let sel = 0;
|
|
85
|
+
|
|
86
|
+
for (;;) {
|
|
87
|
+
const r = await ui.custom<ListResult>(
|
|
88
|
+
(_tui, theme, _kb, done) => new PromptManagerComponent(all, sel, theme, done),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (!r || r.action === "close") break;
|
|
92
|
+
|
|
93
|
+
if (r.action === "paste") {
|
|
94
|
+
const p = all[r.index];
|
|
95
|
+
if (p) ui.pasteToEditor(p.content);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (r.action === "add") {
|
|
100
|
+
await addPrompt(ui);
|
|
101
|
+
all = await loadPrompts();
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (r.action === "edit") {
|
|
106
|
+
sel = r.index;
|
|
107
|
+
const p = all[r.index];
|
|
108
|
+
if (!p) continue;
|
|
109
|
+
const txt = await ui.editor(`Edit: ${p.name}`, p.content);
|
|
110
|
+
if (txt?.trim()) {
|
|
111
|
+
all[r.index] = {
|
|
112
|
+
...p,
|
|
113
|
+
content: txt.trim(),
|
|
114
|
+
updatedAt: Date.now(),
|
|
115
|
+
};
|
|
116
|
+
await savePrompts(all);
|
|
117
|
+
ui.notify(`"${p.name}" updated.`, "info");
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (r.action === "delete") {
|
|
123
|
+
sel = r.index;
|
|
124
|
+
const p = all[r.index];
|
|
125
|
+
if (!p) continue;
|
|
126
|
+
const ok = await ui.confirm("Delete prompt", `Delete "${p.name}"? This cannot be undone.`);
|
|
127
|
+
if (ok) {
|
|
128
|
+
all = all.filter((_, i) => i !== r.index);
|
|
129
|
+
sel = Math.max(0, sel - 1);
|
|
130
|
+
await savePrompts(all);
|
|
131
|
+
ui.notify(`"${p.name}" deleted.`, "info");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
package/src/component.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import type { ListResult, SavedPrompt } from "./types.js";
|
|
4
|
+
|
|
5
|
+
export class PromptManagerComponent {
|
|
6
|
+
private selectedIndex: number;
|
|
7
|
+
private cache?: { w: number; lines: string[] };
|
|
8
|
+
|
|
9
|
+
constructor(
|
|
10
|
+
private prompts: SavedPrompt[],
|
|
11
|
+
initialIndex: number,
|
|
12
|
+
private theme: Theme,
|
|
13
|
+
private done: (result: ListResult) => void,
|
|
14
|
+
) {
|
|
15
|
+
this.selectedIndex = prompts.length > 0 ? Math.max(0, Math.min(initialIndex, prompts.length - 1)) : 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
handleInput(data: string): void {
|
|
19
|
+
const n = this.prompts.length;
|
|
20
|
+
|
|
21
|
+
if (matchesKey(data, "up") || data === "k") {
|
|
22
|
+
if (this.selectedIndex > 0) {
|
|
23
|
+
this.selectedIndex--;
|
|
24
|
+
this.invalidate();
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (matchesKey(data, "down") || data === "j") {
|
|
29
|
+
if (this.selectedIndex < n - 1) {
|
|
30
|
+
this.selectedIndex++;
|
|
31
|
+
this.invalidate();
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (matchesKey(data, "enter") && n > 0) {
|
|
37
|
+
this.done({ action: "paste", index: this.selectedIndex });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if ((data === "e" || data === "E") && n > 0) {
|
|
41
|
+
this.done({ action: "edit", index: this.selectedIndex });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if ((data === "d" || data === "D") && n > 0) {
|
|
45
|
+
this.done({ action: "delete", index: this.selectedIndex });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (data === "a" || data === "A") {
|
|
49
|
+
this.done({ action: "add" });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
53
|
+
this.done({ action: "close" });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
render(width: number): string[] {
|
|
59
|
+
if (this.cache?.w === width) return this.cache.lines;
|
|
60
|
+
|
|
61
|
+
const th = this.theme;
|
|
62
|
+
const lines: string[] = [];
|
|
63
|
+
|
|
64
|
+
const TITLE = " Prompt Manager ";
|
|
65
|
+
const rightDashes = Math.max(0, width - TITLE.length - 3);
|
|
66
|
+
lines.push(
|
|
67
|
+
truncateToWidth(
|
|
68
|
+
th.fg("borderMuted", "─".repeat(3)) + th.fg("accent", TITLE) + th.fg("borderMuted", "─".repeat(rightDashes)),
|
|
69
|
+
width,
|
|
70
|
+
),
|
|
71
|
+
);
|
|
72
|
+
lines.push("");
|
|
73
|
+
|
|
74
|
+
if (this.prompts.length === 0) {
|
|
75
|
+
lines.push(truncateToWidth(" " + th.fg("dim", "No prompts saved yet. Press [a] to add one."), width));
|
|
76
|
+
} else {
|
|
77
|
+
const MAX_VISIBLE = 10;
|
|
78
|
+
const half = Math.floor(MAX_VISIBLE / 2);
|
|
79
|
+
const start = Math.max(0, Math.min(this.selectedIndex - half, this.prompts.length - MAX_VISIBLE));
|
|
80
|
+
const end = Math.min(this.prompts.length, start + MAX_VISIBLE);
|
|
81
|
+
|
|
82
|
+
if (start > 0) {
|
|
83
|
+
lines.push(truncateToWidth(" " + th.fg("dim", `↑ ${start} more above`), width));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (let i = start; i < end; i++) {
|
|
87
|
+
const p = this.prompts[i]!;
|
|
88
|
+
const isSel = i === this.selectedIndex;
|
|
89
|
+
|
|
90
|
+
const arrow = isSel ? th.fg("accent", "❯ ") : " ";
|
|
91
|
+
const nameStyled = isSel ? th.fg("text", th.bold(p.name)) : th.fg("muted", p.name);
|
|
92
|
+
|
|
93
|
+
let row = arrow + nameStyled;
|
|
94
|
+
|
|
95
|
+
if (isSel && p.content) {
|
|
96
|
+
const firstLine = p.content.split("\n")[0]?.trim() ?? "";
|
|
97
|
+
if (firstLine) {
|
|
98
|
+
const avail = width - 2 - p.name.length - 4;
|
|
99
|
+
if (avail > 8) {
|
|
100
|
+
const preview = firstLine.length > avail ? firstLine.slice(0, avail - 1) + "…" : firstLine;
|
|
101
|
+
row += " " + th.fg("dim", preview);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
lines.push(truncateToWidth(row, width));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (end < this.prompts.length) {
|
|
110
|
+
lines.push(truncateToWidth(" " + th.fg("dim", `↓ ${this.prompts.length - end} more below`), width));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
lines.push("");
|
|
115
|
+
|
|
116
|
+
const hints =
|
|
117
|
+
this.prompts.length > 0
|
|
118
|
+
? "[↑↓/jk] navigate [Enter] paste [e] edit [d] delete [a] add [Esc] close"
|
|
119
|
+
: "[a] add a new prompt [Esc] close";
|
|
120
|
+
lines.push(truncateToWidth(" " + th.fg("dim", hints), width));
|
|
121
|
+
lines.push("");
|
|
122
|
+
|
|
123
|
+
this.cache = { w: width, lines };
|
|
124
|
+
return lines;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
invalidate(): void {
|
|
128
|
+
this.cache = undefined;
|
|
129
|
+
}
|
|
130
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Manager Extension
|
|
3
|
+
*
|
|
4
|
+
* Quickly save, manage, and paste reusable prompts without retyping.
|
|
5
|
+
*
|
|
6
|
+
* Commands
|
|
7
|
+
* ────────
|
|
8
|
+
* /prompt Open the prompt manager (select · edit · delete · add)
|
|
9
|
+
* /prompt add [name] Create a new saved prompt directly (opens multi-line editor)
|
|
10
|
+
*
|
|
11
|
+
* Storage: <pi-agent-dir>/prompt-manager.json
|
|
12
|
+
*
|
|
13
|
+
* File layout:
|
|
14
|
+
* index.ts extension entry point (this file)
|
|
15
|
+
* command.ts /prompt command handler
|
|
16
|
+
* component.ts list-manager TUI component
|
|
17
|
+
* storage.ts prompt-manager.json load/save helpers
|
|
18
|
+
* types.ts shared TypeScript types
|
|
19
|
+
*/
|
|
20
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
21
|
+
import { registerPromptCommand } from "./command.js";
|
|
22
|
+
|
|
23
|
+
export default function promptManagerExtension(pi: ExtensionAPI) {
|
|
24
|
+
registerPromptCommand(pi);
|
|
25
|
+
}
|
package/src/storage.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import type { SavedPrompt } from "./types.js";
|
|
5
|
+
|
|
6
|
+
function dataFilePath(): string {
|
|
7
|
+
return join(getAgentDir(), "prompt-manager.json");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function loadPrompts(): Promise<SavedPrompt[]> {
|
|
11
|
+
try {
|
|
12
|
+
const raw = await readFile(dataFilePath(), "utf8");
|
|
13
|
+
const parsed = JSON.parse(raw) as { prompts?: unknown };
|
|
14
|
+
return Array.isArray(parsed.prompts) ? (parsed.prompts as SavedPrompt[]) : [];
|
|
15
|
+
} catch {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function savePrompts(prompts: SavedPrompt[]): Promise<void> {
|
|
21
|
+
const path = dataFilePath();
|
|
22
|
+
await mkdir(dirname(path), { recursive: true });
|
|
23
|
+
await writeFile(path, JSON.stringify({ prompts }, null, 2), "utf8");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function newId(): string {
|
|
27
|
+
return Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
|
|
28
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface SavedPrompt {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
content: string;
|
|
5
|
+
createdAt: number;
|
|
6
|
+
updatedAt: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type ListResult =
|
|
10
|
+
| { action: "close" }
|
|
11
|
+
| { action: "paste"; index: number }
|
|
12
|
+
| { action: "edit"; index: number }
|
|
13
|
+
| { action: "delete"; index: number }
|
|
14
|
+
| { action: "add" };
|