@yusukeshib/pi-stash 0.1.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/LICENSE +21 -0
- package/README.md +86 -0
- package/assets/demo.png +0 -0
- package/extensions/index.ts +116 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yusukeshib
|
|
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,86 @@
|
|
|
1
|
+
# pi-stash
|
|
2
|
+
|
|
3
|
+
A personal stash of reusable prompt fragments for the [Pi coding agent](https://pi.dev).
|
|
4
|
+
|
|
5
|
+
Jot a prompt down whenever you think of it mid-task, then pop it into the editor
|
|
6
|
+
when you actually need it. Think of it as a stack-like scratchpad for prompts —
|
|
7
|
+
the ad-hoc, mutable companion to static prompt templates.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Why
|
|
12
|
+
|
|
13
|
+
You're deep in a task and a thought hits you: *"oh, I should also have it rename
|
|
14
|
+
that module"* or *"remember to ask for tests after this."*
|
|
15
|
+
|
|
16
|
+
What do you do with that thought right now?
|
|
17
|
+
|
|
18
|
+
- **Queue it as a follow-up?** Too eager — follow-ups fire automatically the
|
|
19
|
+
moment the current turn ends, whether or not you're ready, and in an order you
|
|
20
|
+
don't fully control.
|
|
21
|
+
- **Keep it in your head?** You'll forget it the second the agent comes back
|
|
22
|
+
with something interesting.
|
|
23
|
+
- **Paste it into a scratch file?** Now you're juggling another window.
|
|
24
|
+
|
|
25
|
+
Sometimes you just want to **park a prompt somewhere** and then **pull it out
|
|
26
|
+
whenever you feel like it** — on your own timing, by hand. Not auto-run, not
|
|
27
|
+
queued the instant the turn ends, just stashed and waiting until *you* decide
|
|
28
|
+
it's time.
|
|
29
|
+
|
|
30
|
+
That's the whole point of `pi-stash`:
|
|
31
|
+
|
|
32
|
+
- **Push** the thought the moment it strikes, so you don't lose it or break flow.
|
|
33
|
+
- **Pop** it back into the editor at exactly the moment you want it — not a
|
|
34
|
+
second sooner.
|
|
35
|
+
- **Pick any one, in any order.** Your stash is a menu, not a queue. Pop the
|
|
36
|
+
third thing first, skip the rest, come back for them later — follow-ups can't
|
|
37
|
+
do that.
|
|
38
|
+
|
|
39
|
+
You stay in control of *what* gets said, *when*, and *in what order*.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pi install npm:@yusukeshib/pi-stash
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Commands
|
|
48
|
+
|
|
49
|
+
| Command | Action |
|
|
50
|
+
|---------|--------|
|
|
51
|
+
| `/stash <text>` | **Push** — save the given text onto the stash. |
|
|
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). |
|
|
54
|
+
|
|
55
|
+
## How it works
|
|
56
|
+
|
|
57
|
+
`pi-stash` is not a set of predefined `/name` templates. It is a backlog you
|
|
58
|
+
build up by hand during real work:
|
|
59
|
+
|
|
60
|
+
1. While working, you think "I should also ask it to update the docs" — instead
|
|
61
|
+
of derailing now, run `/stash update the docs and changelog`.
|
|
62
|
+
2. Later, when you're ready, run `/stash`, pick that entry, and it drops into
|
|
63
|
+
the editor. The entry is popped (removed) so the stash stays current.
|
|
64
|
+
3. Pop multiple entries in a row to compose a larger prompt from fragments.
|
|
65
|
+
|
|
66
|
+
## Storage
|
|
67
|
+
|
|
68
|
+
Entries live in a single global JSON file, **shared across every Pi session on
|
|
69
|
+
the machine** (not per-project, not per-session):
|
|
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
|
+
```
|
|
80
|
+
|
|
81
|
+
> Note: the store is read-modify-written on each command. If two sessions edit
|
|
82
|
+
> the stash at the exact same moment, the last write wins.
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT © yusukeshib
|
package/assets/demo.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-stash — a personal stash of reusable prompt fragments for the Pi coding
|
|
3
|
+
* agent. Jot a prompt down whenever you think of it, then later pull up the
|
|
4
|
+
* list and pop one into the editor to compose your next message.
|
|
5
|
+
*
|
|
6
|
+
* Unlike file-based prompt templates (static `/name` commands), this is an
|
|
7
|
+
* ad-hoc, mutable, stack-like backlog you build up by hand during real work.
|
|
8
|
+
*
|
|
9
|
+
* Commands:
|
|
10
|
+
* /stash <text> Push: save the given text onto the stash.
|
|
11
|
+
* /stash Pop: pick a saved entry → insert it into the editor and
|
|
12
|
+
* remove it from the stash. Run repeatedly to stack fragments.
|
|
13
|
+
* /stash-clear Delete every entry (with confirm).
|
|
14
|
+
*
|
|
15
|
+
* Storage: a single global JSON file, shared across every Pi session on the
|
|
16
|
+
* machine (not per-project, not per-session). Default location:
|
|
17
|
+
* ~/.pi/agent/prompt-stash.json
|
|
18
|
+
* Override with the PI_STASH_PATH environment variable.
|
|
19
|
+
*/
|
|
20
|
+
|
|
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";
|
|
25
|
+
|
|
26
|
+
interface StashEntry {
|
|
27
|
+
text: string;
|
|
28
|
+
addedAt: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const STORE_PATH = process.env.PI_STASH_PATH || join(homedir(), ".pi", "agent", "prompt-stash.json");
|
|
32
|
+
const PREVIEW_LEN = 72;
|
|
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
|
+
/** One-line, length-bounded preview for select menus. */
|
|
53
|
+
function preview(text: string, index: number): string {
|
|
54
|
+
const oneLine = text.replace(/\s+/g, " ").trim();
|
|
55
|
+
const body = oneLine.length > PREVIEW_LEN ? `${oneLine.slice(0, PREVIEW_LEN - 1)}…` : oneLine;
|
|
56
|
+
// Number prefix keeps labels unique so indexOf() round-trips reliably.
|
|
57
|
+
return `${String(index + 1).padStart(2, " ")}. ${body}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default function (pi: ExtensionAPI) {
|
|
61
|
+
pi.registerCommand("stash", {
|
|
62
|
+
description: "Push text (with arg) or pop an entry into the editor (no arg)",
|
|
63
|
+
handler: async (args, ctx) => {
|
|
64
|
+
const text = (args ?? "").trim();
|
|
65
|
+
|
|
66
|
+
// PUSH: /stash <text>
|
|
67
|
+
if (text) {
|
|
68
|
+
const entries = load();
|
|
69
|
+
if (entries.some((e) => e.text === text)) {
|
|
70
|
+
ctx.ui.notify("Already in stash", "info");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
entries.push({ text, addedAt: Date.now() });
|
|
74
|
+
save(entries);
|
|
75
|
+
ctx.ui.notify(`Stashed (${entries.length} total)`, "info");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// POP: /stash → pick, insert into editor, remove from stash
|
|
80
|
+
const entries = load();
|
|
81
|
+
if (entries.length === 0) {
|
|
82
|
+
ctx.ui.notify("Stash is empty. Add one with /stash <text>", "info");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const labels = entries.map((e, i) => preview(e.text, i));
|
|
86
|
+
const choice = await ctx.ui.select("Pop prompt:", labels);
|
|
87
|
+
if (choice === undefined) return; // cancelled / timed out
|
|
88
|
+
const idx = labels.indexOf(choice);
|
|
89
|
+
if (idx < 0) return;
|
|
90
|
+
|
|
91
|
+
const chosen = entries[idx].text;
|
|
92
|
+
const current = ctx.ui.getEditorText() ?? "";
|
|
93
|
+
const next = current.trim().length > 0 ? `${current}\n${chosen}` : chosen;
|
|
94
|
+
ctx.ui.setEditorText(next);
|
|
95
|
+
|
|
96
|
+
// pop = remove the chosen entry
|
|
97
|
+
entries.splice(idx, 1);
|
|
98
|
+
save(entries);
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
pi.registerCommand("stash-clear", {
|
|
103
|
+
description: "Delete every stashed prompt",
|
|
104
|
+
handler: async (_args, ctx) => {
|
|
105
|
+
const entries = load();
|
|
106
|
+
if (entries.length === 0) {
|
|
107
|
+
ctx.ui.notify("Stash is already empty", "info");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const ok = await ctx.ui.confirm("Clear the entire stash?", `${entries.length} entries will be deleted`);
|
|
111
|
+
if (!ok) return;
|
|
112
|
+
save([]);
|
|
113
|
+
ctx.ui.notify("Stash cleared", "info");
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yusukeshib/pi-stash",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "yusukeshib",
|
|
8
|
+
"homepage": "https://github.com/yusukeshib/pi-stash#readme",
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/yusukeshib/pi-stash/issues"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/yusukeshib/pi-stash.git"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"pi-package",
|
|
18
|
+
"pi",
|
|
19
|
+
"pi-coding-agent",
|
|
20
|
+
"pi-extension",
|
|
21
|
+
"prompt",
|
|
22
|
+
"stash",
|
|
23
|
+
"snippets",
|
|
24
|
+
"productivity"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"extensions",
|
|
31
|
+
"assets",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"pi": {
|
|
36
|
+
"extensions": [
|
|
37
|
+
"extensions/index.ts"
|
|
38
|
+
],
|
|
39
|
+
"image": "https://raw.githubusercontent.com/yusukeshib/pi-stash/main/assets/demo.png"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"prepack": "npm run typecheck"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
50
|
+
"@types/node": "^20.0.0",
|
|
51
|
+
"typescript": "^5.5.0"
|
|
52
|
+
}
|
|
53
|
+
}
|