@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 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
+ ![pi-stash pop menu](./assets/demo.png)
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
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
+ }