@yusukeshib/pi-stash 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -16
- package/extensions/index.ts +53 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-stash
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A per-session stash of reusable prompt fragments for the [Pi coding agent](https://pi.dev).
|
|
4
4
|
|
|
5
5
|
Jot a prompt down whenever you think of it mid-task, then pop it into the editor
|
|
6
6
|
when you actually need it. Think of it as a stack-like scratchpad for prompts —
|
|
@@ -50,7 +50,10 @@ pi install npm:@yusukeshib/pi-stash
|
|
|
50
50
|
|---------|--------|
|
|
51
51
|
| `/stash <text>` | **Push** — save the given text onto the stash. |
|
|
52
52
|
| `/stash` | **Pop** — pick a saved entry, insert it into the editor, and remove it from the stash. Run repeatedly to stack several fragments together. |
|
|
53
|
-
| `/stash-clear` | Delete every stashed entry (with confirm). |
|
|
53
|
+
| `/stash-clear` | Delete every stashed entry in this session (with confirm). |
|
|
54
|
+
|
|
55
|
+
While the stash is non-empty, a red `stash:N` badge is shown in the footer so
|
|
56
|
+
you never forget you have prompts parked.
|
|
54
57
|
|
|
55
58
|
## How it works
|
|
56
59
|
|
|
@@ -65,21 +68,13 @@ build up by hand during real work:
|
|
|
65
68
|
|
|
66
69
|
## Storage
|
|
67
70
|
|
|
68
|
-
Entries
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
~/.pi/agent/prompt-stash.json
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Override the location with the `PI_STASH_PATH` environment variable:
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
export PI_STASH_PATH="$HOME/.config/pi/stash.json"
|
|
79
|
-
```
|
|
71
|
+
Entries are stored **inside the session itself** as custom session entries —
|
|
72
|
+
each Pi session has its own independent stash. It survives restarts and
|
|
73
|
+
`/resume`, and follows branching (`/fork`, `/clone`) correctly: a forked
|
|
74
|
+
session sees the stash as it was at the fork point.
|
|
80
75
|
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
Nothing is written outside the session file, and the stash does not
|
|
77
|
+
participate in the LLM context.
|
|
83
78
|
|
|
84
79
|
## License
|
|
85
80
|
|
package/extensions/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* pi-stash — a
|
|
3
|
-
* agent. Jot a prompt down whenever you think of it, then later pull
|
|
4
|
-
* list and pop one into the editor to compose your next message.
|
|
2
|
+
* pi-stash — a per-session stash of reusable prompt fragments for the Pi
|
|
3
|
+
* coding agent. Jot a prompt down whenever you think of it, then later pull
|
|
4
|
+
* up the list and pop one into the editor to compose your next message.
|
|
5
5
|
*
|
|
6
6
|
* Unlike file-based prompt templates (static `/name` commands), this is an
|
|
7
7
|
* ad-hoc, mutable, stack-like backlog you build up by hand during real work.
|
|
@@ -12,43 +12,25 @@
|
|
|
12
12
|
* remove it from the stash. Run repeatedly to stack fragments.
|
|
13
13
|
* /stash-clear Delete every entry (with confirm).
|
|
14
14
|
*
|
|
15
|
-
* Storage:
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* Storage: stash entries are persisted INSIDE the session itself via custom
|
|
16
|
+
* session entries (`pi.appendEntry`). Each session has its own stash, it
|
|
17
|
+
* survives restarts/resume, and it follows branching (fork/clone) correctly.
|
|
18
|
+
*
|
|
19
|
+
* While the stash is non-empty, a red badge with the entry count is shown in
|
|
20
|
+
* the footer so you don't forget about pending prompts.
|
|
19
21
|
*/
|
|
20
22
|
|
|
21
|
-
import { type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
22
|
-
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
23
|
-
import { homedir } from "node:os";
|
|
24
|
-
import { dirname, join } from "node:path";
|
|
23
|
+
import { type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
25
24
|
|
|
26
25
|
interface StashEntry {
|
|
27
26
|
text: string;
|
|
28
27
|
addedAt: number;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
const
|
|
30
|
+
const CUSTOM_TYPE = "pi-stash-state";
|
|
31
|
+
const STATUS_KEY = "pi-stash";
|
|
32
32
|
const PREVIEW_LEN = 72;
|
|
33
33
|
|
|
34
|
-
function load(): StashEntry[] {
|
|
35
|
-
try {
|
|
36
|
-
const raw = readFileSync(STORE_PATH, "utf8");
|
|
37
|
-
const parsed = JSON.parse(raw);
|
|
38
|
-
if (Array.isArray(parsed)) {
|
|
39
|
-
return parsed.filter((e): e is StashEntry => e && typeof e.text === "string");
|
|
40
|
-
}
|
|
41
|
-
} catch {
|
|
42
|
-
// missing or corrupt → empty stash
|
|
43
|
-
}
|
|
44
|
-
return [];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function save(entries: StashEntry[]): void {
|
|
48
|
-
mkdirSync(dirname(STORE_PATH), { recursive: true });
|
|
49
|
-
writeFileSync(STORE_PATH, `${JSON.stringify(entries, null, 2)}\n`, "utf8");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
34
|
/** One-line, length-bounded preview for select menus. */
|
|
53
35
|
function preview(text: string, index: number): string {
|
|
54
36
|
const oneLine = text.replace(/\s+/g, " ").trim();
|
|
@@ -58,6 +40,39 @@ function preview(text: string, index: number): string {
|
|
|
58
40
|
}
|
|
59
41
|
|
|
60
42
|
export default function (pi: ExtensionAPI) {
|
|
43
|
+
// In-memory stash for the current session; persisted as custom entries.
|
|
44
|
+
let entries: StashEntry[] = [];
|
|
45
|
+
|
|
46
|
+
function persist(): void {
|
|
47
|
+
pi.appendEntry(CUSTOM_TYPE, { entries });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Red, hard-to-miss footer badge while the stash is non-empty. */
|
|
51
|
+
function updateStatus(ctx: ExtensionContext): void {
|
|
52
|
+
if (entries.length > 0) {
|
|
53
|
+
// White on red background (raw ANSI so it stays red in any theme).
|
|
54
|
+
ctx.ui.setStatus(STATUS_KEY, `\x1b[41m\x1b[97m\x1b[1m stash:${entries.length} \x1b[0m`);
|
|
55
|
+
} else {
|
|
56
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Restore stash from the session (last persisted state on the active path).
|
|
61
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
62
|
+
entries = [];
|
|
63
|
+
for (const entry of ctx.sessionManager.getEntries()) {
|
|
64
|
+
if (entry.type === "custom" && entry.customType === CUSTOM_TYPE) {
|
|
65
|
+
const data = entry.data as { entries?: unknown } | undefined;
|
|
66
|
+
if (data && Array.isArray(data.entries)) {
|
|
67
|
+
entries = data.entries.filter(
|
|
68
|
+
(e): e is StashEntry => !!e && typeof (e as StashEntry).text === "string",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
updateStatus(ctx);
|
|
74
|
+
});
|
|
75
|
+
|
|
61
76
|
pi.registerCommand("stash", {
|
|
62
77
|
description: "Push text (with arg) or pop an entry into the editor (no arg)",
|
|
63
78
|
handler: async (args, ctx) => {
|
|
@@ -65,19 +80,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
65
80
|
|
|
66
81
|
// PUSH: /stash <text>
|
|
67
82
|
if (text) {
|
|
68
|
-
const entries = load();
|
|
69
83
|
if (entries.some((e) => e.text === text)) {
|
|
70
84
|
ctx.ui.notify("Already in stash", "info");
|
|
71
85
|
return;
|
|
72
86
|
}
|
|
73
87
|
entries.push({ text, addedAt: Date.now() });
|
|
74
|
-
|
|
88
|
+
persist();
|
|
89
|
+
updateStatus(ctx);
|
|
75
90
|
ctx.ui.notify(`Stashed (${entries.length} total)`, "info");
|
|
76
91
|
return;
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
// POP: /stash → pick, insert into editor, remove from stash
|
|
80
|
-
const entries = load();
|
|
81
95
|
if (entries.length === 0) {
|
|
82
96
|
ctx.ui.notify("Stash is empty. Add one with /stash <text>", "info");
|
|
83
97
|
return;
|
|
@@ -95,21 +109,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
95
109
|
|
|
96
110
|
// pop = remove the chosen entry
|
|
97
111
|
entries.splice(idx, 1);
|
|
98
|
-
|
|
112
|
+
persist();
|
|
113
|
+
updateStatus(ctx);
|
|
99
114
|
},
|
|
100
115
|
});
|
|
101
116
|
|
|
102
117
|
pi.registerCommand("stash-clear", {
|
|
103
|
-
description: "Delete every stashed prompt",
|
|
118
|
+
description: "Delete every stashed prompt in this session",
|
|
104
119
|
handler: async (_args, ctx) => {
|
|
105
|
-
const entries = load();
|
|
106
120
|
if (entries.length === 0) {
|
|
107
121
|
ctx.ui.notify("Stash is already empty", "info");
|
|
108
122
|
return;
|
|
109
123
|
}
|
|
110
124
|
const ok = await ctx.ui.confirm("Clear the entire stash?", `${entries.length} entries will be deleted`);
|
|
111
125
|
if (!ok) return;
|
|
112
|
-
|
|
126
|
+
entries = [];
|
|
127
|
+
persist();
|
|
128
|
+
updateStatus(ctx);
|
|
113
129
|
ctx.ui.notify("Stash cleared", "info");
|
|
114
130
|
},
|
|
115
131
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yusukeshib/pi-stash",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A personal stash of reusable prompt fragments for the Pi coding agent. Push prompts you think of mid-task, then pop them into the editor when you need them.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|