@nicknisi/pi-stash 0.1.1 → 0.1.3
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 +8 -8
- package/dist/index.d.ts +16 -0
- package/dist/index.js +60 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +34 -0
- package/index.ts +6 -6
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# @nicknisi/pi-stash
|
|
2
2
|
|
|
3
|
-
Replicates Claude Code's `ctrl+s`
|
|
3
|
+
Replicates Claude Code's message stash using `ctrl+shift+s` to avoid conflicts with Pi's built-in `ctrl+s` shortcuts. Press `ctrl+shift+s` to stash whatever you've typed in the input editor, go do something else, then press `ctrl+shift+s` again with an empty editor to pop the most recent stash back. Stashes live on a stack, so multiple drafts can be parked and restored LIFO.
|
|
4
4
|
|
|
5
5
|
## What it adds
|
|
6
6
|
|
|
7
|
-
- **Keybinding:** `ctrl+s`
|
|
8
|
-
- **Widget:** a single-line widget above the editor, visible whenever the stash stack is non-empty, showing a 60-char preview of the most recent stash, the count of additional stashes (`(+N more)`), and a `ctrl+s to restore` hint
|
|
7
|
+
- **Keybinding:** `ctrl+shift+s` toggles stash/restore. See the behavior table below.
|
|
8
|
+
- **Widget:** a single-line widget above the editor, visible whenever the stash stack is non-empty, showing a 60-char preview of the most recent stash, the count of additional stashes (`(+N more)`), and a `ctrl+shift+s to restore` hint
|
|
9
9
|
- **Events hooked:** `before_agent_start` — after a message is submitted and the agent starts, the most recent stash is auto-restored into the now-empty editor (mirrors Claude Code)
|
|
10
10
|
- No slash commands, no tools, no custom entry types.
|
|
11
11
|
|
|
12
12
|
### Keybinding behavior
|
|
13
13
|
|
|
14
|
-
| Editor state on `ctrl+s`
|
|
14
|
+
| Editor state on `ctrl+shift+s` | Action |
|
|
15
15
|
| ----------------------------------------- | ------------------------------------------ |
|
|
16
16
|
| Contains text | Push text onto the stack, clear the editor |
|
|
17
17
|
| Empty (or whitespace) and stack non-empty | Pop the most recent stash into the editor |
|
|
@@ -21,9 +21,9 @@ Replicates Claude Code's `ctrl+s` message stash. Press `ctrl+s` to stash whateve
|
|
|
21
21
|
|
|
22
22
|
```text
|
|
23
23
|
(type a long prompt, then need to ask something else first)
|
|
24
|
-
ctrl+s
|
|
24
|
+
ctrl+shift+s "⧉ stashed: Refactor the auth module to..." · editor cleared
|
|
25
25
|
(ask the other question; on submit, the stashed draft is restored automatically)
|
|
26
|
-
ctrl+s
|
|
26
|
+
ctrl+shift+s (with empty editor) pops the stash back manually
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
The stash is in-memory only (per session, per process). It is not persisted across restarts.
|
|
@@ -41,9 +41,9 @@ None. No config files, no options, no environment variables.
|
|
|
41
41
|
|
|
42
42
|
- UI-gated: every handler checks `ctx.hasUI`; the extension is a no-op in headless/non-TUI mode.
|
|
43
43
|
- Depends on the editor text APIs (`ctx.ui.getEditorText` / `setEditorText`) and the widget system (`ctx.ui.setWidget`) — these are stable pi extension APIs, but a pi version that changes editor or widget semantics could affect it.
|
|
44
|
-
- The `before_agent_start` auto-restore pops a stash whenever a message is sent with an empty editor. If you intentionally sent a quick command and wanted the stash to stay parked, it will still be restored into the editor (you can `ctrl+s` it back). This matches Claude Code's behavior.
|
|
44
|
+
- The `before_agent_start` auto-restore pops a stash whenever a message is sent with an empty editor. If you intentionally sent a quick command and wanted the stash to stay parked, it will still be restored into the editor (you can `ctrl+shift+s` it back). This matches Claude Code's behavior.
|
|
45
45
|
- Stash stack is module-local state; it does not survive session reload or extension reload.
|
|
46
|
-
-
|
|
46
|
+
- The terminal must distinguish `ctrl+shift+s` from `ctrl+s`, for example through the Kitty keyboard protocol. Legacy terminals may need a key mapping. The widget preview uses the Unicode glyphs `⧉` and `…`.
|
|
47
47
|
|
|
48
48
|
## Install
|
|
49
49
|
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Stash Extension
|
|
3
|
+
*
|
|
4
|
+
* Replicates Claude Code's message stash: press ctrl+shift+s to stash
|
|
5
|
+
* whatever you've typed in the input box, do something else, then press
|
|
6
|
+
* ctrl+shift+s again (with an empty input) to restore it.
|
|
7
|
+
*
|
|
8
|
+
* - ctrl+shift+s with text in the editor → pushes it onto a stack and clears
|
|
9
|
+
* - ctrl+shift+s with an empty editor → pops the most recent stash back
|
|
10
|
+
* - sending another message → auto-restores the most recent stash
|
|
11
|
+
*
|
|
12
|
+
* A widget above the editor shows how many messages are stashed with a
|
|
13
|
+
* one-line preview of the most recent one.
|
|
14
|
+
*/
|
|
15
|
+
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
16
|
+
export default function stash(pi: ExtensionAPI): void;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Stash Extension
|
|
3
|
+
*
|
|
4
|
+
* Replicates Claude Code's message stash: press ctrl+shift+s to stash
|
|
5
|
+
* whatever you've typed in the input box, do something else, then press
|
|
6
|
+
* ctrl+shift+s again (with an empty input) to restore it.
|
|
7
|
+
*
|
|
8
|
+
* - ctrl+shift+s with text in the editor → pushes it onto a stack and clears
|
|
9
|
+
* - ctrl+shift+s with an empty editor → pops the most recent stash back
|
|
10
|
+
* - sending another message → auto-restores the most recent stash
|
|
11
|
+
*
|
|
12
|
+
* A widget above the editor shows how many messages are stashed with a
|
|
13
|
+
* one-line preview of the most recent one.
|
|
14
|
+
*/
|
|
15
|
+
const WIDGET_KEY = 'message-stash';
|
|
16
|
+
export default function stash(pi) {
|
|
17
|
+
const stack = [];
|
|
18
|
+
const updateWidget = (ctx) => {
|
|
19
|
+
if (!ctx.hasUI)
|
|
20
|
+
return;
|
|
21
|
+
if (stack.length === 0) {
|
|
22
|
+
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const theme = ctx.ui.theme;
|
|
26
|
+
const latest = stack[stack.length - 1];
|
|
27
|
+
const firstLine = latest.split('\n')[0] ?? '';
|
|
28
|
+
const preview = firstLine.length > 60 ? `${firstLine.slice(0, 60)}…` : firstLine;
|
|
29
|
+
const count = stack.length > 1 ? theme.fg('dim', ` (+${stack.length - 1} more)`) : '';
|
|
30
|
+
ctx.ui.setWidget(WIDGET_KEY, [
|
|
31
|
+
`${theme.fg('accent', '⧉ stashed:')} ${theme.fg('muted', preview)}${count} ${theme.fg('dim', '· ctrl+shift+s to restore')}`,
|
|
32
|
+
]);
|
|
33
|
+
};
|
|
34
|
+
pi.registerShortcut('ctrl+shift+s', {
|
|
35
|
+
description: 'Stash / restore the typed message',
|
|
36
|
+
handler: async (ctx) => {
|
|
37
|
+
if (!ctx.hasUI)
|
|
38
|
+
return;
|
|
39
|
+
const text = ctx.ui.getEditorText();
|
|
40
|
+
if (text.trim().length > 0) {
|
|
41
|
+
stack.push(text);
|
|
42
|
+
ctx.ui.setEditorText('');
|
|
43
|
+
}
|
|
44
|
+
else if (stack.length > 0) {
|
|
45
|
+
ctx.ui.setEditorText(stack.pop());
|
|
46
|
+
}
|
|
47
|
+
updateWidget(ctx);
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
// After a message is submitted, auto-restore the most recent stash into
|
|
51
|
+
// the now-empty editor (mirrors Claude Code's behavior).
|
|
52
|
+
pi.on('before_agent_start', async (_event, ctx) => {
|
|
53
|
+
if (!ctx.hasUI || stack.length === 0)
|
|
54
|
+
return;
|
|
55
|
+
if (ctx.ui.getEditorText().trim().length > 0)
|
|
56
|
+
return;
|
|
57
|
+
ctx.ui.setEditorText(stack.pop());
|
|
58
|
+
updateWidget(ctx);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { expect, it, vi } from 'vitest';
|
|
2
|
+
import stash from './index.js';
|
|
3
|
+
it('stashes and restores drafts with Ctrl+Shift+S and shows the matching hint', async () => {
|
|
4
|
+
const registerShortcut = vi.fn();
|
|
5
|
+
stash({ registerShortcut, on: vi.fn() });
|
|
6
|
+
expect(registerShortcut).toHaveBeenCalledOnce();
|
|
7
|
+
const [key, { handler }] = registerShortcut.mock.calls[0];
|
|
8
|
+
expect(key).toBe('ctrl+shift+s');
|
|
9
|
+
let text = 'first draft';
|
|
10
|
+
const setWidget = vi.fn();
|
|
11
|
+
const ctx = {
|
|
12
|
+
hasUI: true,
|
|
13
|
+
ui: {
|
|
14
|
+
getEditorText: () => text,
|
|
15
|
+
setEditorText: (value) => {
|
|
16
|
+
text = value;
|
|
17
|
+
},
|
|
18
|
+
setWidget,
|
|
19
|
+
theme: { fg: (_color, value) => value },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
await handler(ctx);
|
|
23
|
+
expect(text).toBe('');
|
|
24
|
+
expect(setWidget).toHaveBeenLastCalledWith('message-stash', [expect.stringContaining('ctrl+shift+s to restore')]);
|
|
25
|
+
text = 'second draft';
|
|
26
|
+
await handler(ctx);
|
|
27
|
+
expect(text).toBe('');
|
|
28
|
+
await handler(ctx);
|
|
29
|
+
expect(text).toBe('second draft');
|
|
30
|
+
text = '';
|
|
31
|
+
await handler(ctx);
|
|
32
|
+
expect(text).toBe('first draft');
|
|
33
|
+
expect(setWidget).toHaveBeenLastCalledWith('message-stash', undefined);
|
|
34
|
+
});
|
package/index.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Message Stash Extension
|
|
3
3
|
*
|
|
4
|
-
* Replicates Claude Code's
|
|
4
|
+
* Replicates Claude Code's message stash: press ctrl+shift+s to stash
|
|
5
5
|
* whatever you've typed in the input box, do something else, then press
|
|
6
|
-
* ctrl+s again (with an empty input) to restore it.
|
|
6
|
+
* ctrl+shift+s again (with an empty input) to restore it.
|
|
7
7
|
*
|
|
8
|
-
* - ctrl+s with text in the editor → pushes it onto a stack and clears
|
|
9
|
-
* - ctrl+s with an empty editor → pops the most recent stash back
|
|
8
|
+
* - ctrl+shift+s with text in the editor → pushes it onto a stack and clears
|
|
9
|
+
* - ctrl+shift+s with an empty editor → pops the most recent stash back
|
|
10
10
|
* - sending another message → auto-restores the most recent stash
|
|
11
11
|
*
|
|
12
12
|
* A widget above the editor shows how many messages are stashed with a
|
|
@@ -32,11 +32,11 @@ export default function stash(pi: ExtensionAPI) {
|
|
|
32
32
|
const preview = firstLine.length > 60 ? `${firstLine.slice(0, 60)}…` : firstLine;
|
|
33
33
|
const count = stack.length > 1 ? theme.fg('dim', ` (+${stack.length - 1} more)`) : '';
|
|
34
34
|
ctx.ui.setWidget(WIDGET_KEY, [
|
|
35
|
-
`${theme.fg('accent', '⧉ stashed:')} ${theme.fg('muted', preview)}${count} ${theme.fg('dim', '· ctrl+s to restore')}`,
|
|
35
|
+
`${theme.fg('accent', '⧉ stashed:')} ${theme.fg('muted', preview)}${count} ${theme.fg('dim', '· ctrl+shift+s to restore')}`,
|
|
36
36
|
]);
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
pi.registerShortcut('ctrl+s', {
|
|
39
|
+
pi.registerShortcut('ctrl+shift+s', {
|
|
40
40
|
description: 'Stash / restore the typed message',
|
|
41
41
|
handler: async (ctx) => {
|
|
42
42
|
if (!ctx.hasUI) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nicknisi/pi-stash",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Stash and restore the current prompt draft",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|
|
@@ -15,11 +15,15 @@
|
|
|
15
15
|
"directory": "packages/stash"
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
|
+
"dist",
|
|
18
19
|
"index.ts"
|
|
19
20
|
],
|
|
20
21
|
"type": "module",
|
|
21
22
|
"exports": {
|
|
22
|
-
".":
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
}
|
|
23
27
|
},
|
|
24
28
|
"peerDependencies": {
|
|
25
29
|
"@earendil-works/pi-coding-agent": "*"
|