pi-fast-resume 1.0.2 → 1.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/README.md +29 -5
- package/fast-resume.ts +92 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,7 +107,11 @@ Reload with `/reload` after any install method.
|
|
|
107
107
|
|
|
108
108
|
### Keyboard shortcut
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
There are two ways to bind a key to the fast resume picker:
|
|
111
|
+
|
|
112
|
+
**Option 1: Rebind `app.session.resume`** (hijack mode only)
|
|
113
|
+
|
|
114
|
+
In hijack mode (default), the fast picker replaces `/resume` — bind `app.session.resume` in `~/.pi/agent/keybindings.json`:
|
|
111
115
|
|
|
112
116
|
```json
|
|
113
117
|
{
|
|
@@ -115,6 +119,18 @@ In hijack mode (default), the fast picker replaces `/resume` — so bind `app.se
|
|
|
115
119
|
}
|
|
116
120
|
```
|
|
117
121
|
|
|
122
|
+
**Option 2: Standalone shortcut** (works regardless of hijack mode)
|
|
123
|
+
|
|
124
|
+
Set the `shortcut` key in `~/.pi/agent/extensions/pi-fast-resume.json`:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"shortcut": "ctrl+shift+f"
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This registers an independent shortcut that opens the fast picker without overriding the built-in `/resume`. Works in both hijack and non-hijack modes.
|
|
133
|
+
|
|
118
134
|
See [keybindings.md](https://github.com/earendil-works/pi-coding-agent/blob/main/docs/keybindings.md) for the key format and all available actions.
|
|
119
135
|
|
|
120
136
|
### Picker controls
|
|
@@ -190,17 +206,25 @@ However, pi-fast-resume can **prototype-patch** `InteractiveMode.showSessionSele
|
|
|
190
206
|
- `/fast-resume` is not registered (no duplicate command)
|
|
191
207
|
- `pi -r` / `pi --resume` are **not** affected (they run before the interactive mode starts)
|
|
192
208
|
|
|
193
|
-
###
|
|
209
|
+
### Config options
|
|
210
|
+
|
|
211
|
+
All options go in `~/.pi/agent/extensions/pi-fast-resume.json`:
|
|
212
|
+
|
|
213
|
+
| Key | Type | Default | Description |
|
|
214
|
+
| --- | ---- | ------- | ----------- |
|
|
215
|
+
| `hijackResume` | `boolean` | `true` | When `true`, `/resume` and `app.session.resume` open the fast picker. Set `false` to use the built-in picker instead and keep `/fast-resume` as a separate command. |
|
|
216
|
+
| `shortcut` | `string` | (none) | Register a standalone keybinding to open the fast picker. Works regardless of `hijackResume`. Example: `"ctrl+shift+f"` or `"alt+u"`.
|
|
194
217
|
|
|
195
|
-
|
|
218
|
+
Example:
|
|
196
219
|
|
|
197
220
|
```json
|
|
198
221
|
{
|
|
199
|
-
"hijackResume": false
|
|
222
|
+
"hijackResume": false,
|
|
223
|
+
"shortcut": "alt+u"
|
|
200
224
|
}
|
|
201
225
|
```
|
|
202
226
|
|
|
203
|
-
|
|
227
|
+
Reload with `/reload` after changing config.
|
|
204
228
|
|
|
205
229
|
### How it works
|
|
206
230
|
|
package/fast-resume.ts
CHANGED
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
45
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
46
|
+
import type { KeyId } from "@earendil-works/pi-tui";
|
|
46
47
|
import {
|
|
47
48
|
DynamicBorder,
|
|
48
49
|
InteractiveMode,
|
|
@@ -93,10 +94,12 @@ import type { PickerScope } from "./src/picker-state.js";
|
|
|
93
94
|
const HOME = homedir();
|
|
94
95
|
|
|
95
96
|
// Config — read from ~/.pi/agent/extensions/pi-fast-resume.json
|
|
96
|
-
// Example: { "hijackResume": false }
|
|
97
|
+
// Example: { "hijackResume": false, "shortcut": "alt+u" }
|
|
97
98
|
// By default hijackResume is true — /resume opens the fast picker
|
|
99
|
+
// Set shortcut to register a standalone shortcut (e.g. "ctrl+shift+f")
|
|
98
100
|
interface FastResumeConfig {
|
|
99
101
|
hijackResume?: boolean;
|
|
102
|
+
shortcut?: string;
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
const CONFIG_PATH = join(HOME, ".pi", "agent", "extensions", "pi-fast-resume.json");
|
|
@@ -991,9 +994,52 @@ async function showFastResumePicker(
|
|
|
991
994
|
}
|
|
992
995
|
}
|
|
993
996
|
|
|
997
|
+
// Stored reference to the extension runner, captured via prototype patch on
|
|
998
|
+
// InteractiveMode.prototype.setupExtensionShortcuts. Used by the shortcut
|
|
999
|
+
// handler to create an ExtensionCommandContext with switchSession(), since
|
|
1000
|
+
// pi.registerShortcut handlers only receive ExtensionContext.
|
|
1001
|
+
let storedExtensionRunner: any = null;
|
|
1002
|
+
|
|
994
1003
|
// Reference to the original showSessionSelector, saved before patching
|
|
995
1004
|
let origShowSessionSelector: ((this: InteractiveMode) => void) | null = null;
|
|
996
1005
|
|
|
1006
|
+
// Reference to the original setupExtensionShortcuts, saved before patching
|
|
1007
|
+
let origSetupExtensionShortcuts: Function | null = null;
|
|
1008
|
+
|
|
1009
|
+
function patchSetupExtensionShortcuts(): void {
|
|
1010
|
+
if (origSetupExtensionShortcuts !== null) return; // Already patched
|
|
1011
|
+
const proto = InteractiveMode.prototype as any;
|
|
1012
|
+
if (
|
|
1013
|
+
!InteractiveMode ||
|
|
1014
|
+
typeof InteractiveMode !== "function" ||
|
|
1015
|
+
typeof proto.setupExtensionShortcuts !== "function"
|
|
1016
|
+
) {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
origSetupExtensionShortcuts = proto.setupExtensionShortcuts;
|
|
1020
|
+
proto.setupExtensionShortcuts = function (
|
|
1021
|
+
this: InteractiveMode,
|
|
1022
|
+
extensionRunner: any,
|
|
1023
|
+
) {
|
|
1024
|
+
storedExtensionRunner = extensionRunner;
|
|
1025
|
+
origSetupExtensionShortcuts!.call(this, extensionRunner);
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
function unpatchSetupExtensionShortcuts(): void {
|
|
1030
|
+
if (origSetupExtensionShortcuts === null) return;
|
|
1031
|
+
const proto = InteractiveMode.prototype as any;
|
|
1032
|
+
if (
|
|
1033
|
+
InteractiveMode &&
|
|
1034
|
+
typeof InteractiveMode === "function" &&
|
|
1035
|
+
typeof proto.setupExtensionShortcuts === "function"
|
|
1036
|
+
) {
|
|
1037
|
+
proto.setupExtensionShortcuts = origSetupExtensionShortcuts;
|
|
1038
|
+
}
|
|
1039
|
+
origSetupExtensionShortcuts = null;
|
|
1040
|
+
storedExtensionRunner = null;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
997
1043
|
function installResumeHijack(): void {
|
|
998
1044
|
if (origShowSessionSelector !== null) return; // Already patched
|
|
999
1045
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- prototype patching requires any cast for private access
|
|
@@ -1057,17 +1103,55 @@ export default function (pi: ExtensionAPI) {
|
|
|
1057
1103
|
});
|
|
1058
1104
|
}
|
|
1059
1105
|
|
|
1060
|
-
//
|
|
1061
|
-
// ExtensionContext (no switchSession),
|
|
1062
|
-
//
|
|
1063
|
-
//
|
|
1064
|
-
//
|
|
1065
|
-
//
|
|
1106
|
+
// Register a standalone keyboard shortcut for the fast resume picker.
|
|
1107
|
+
// pi.registerShortcut handlers receive ExtensionContext (no switchSession),
|
|
1108
|
+
// so we capture the extension runner via a prototype patch on
|
|
1109
|
+
// InteractiveMode.prototype.setupExtensionShortcuts and use it to create
|
|
1110
|
+
// an ExtensionCommandContext inside the handler.
|
|
1111
|
+
//
|
|
1112
|
+
// Users can rebind the key via pi-fast-resume.json:
|
|
1113
|
+
// { "shortcut": "alt+u" }
|
|
1114
|
+
//
|
|
1115
|
+
// In hijack mode, app.session.resume also opens the fast picker (rebindable
|
|
1116
|
+
// in ~/.pi/agent/keybindings.json). The shortcut config is an additional
|
|
1117
|
+
// independent binding that does not override the built-in /resume.
|
|
1118
|
+
const shortcut = config.shortcut;
|
|
1119
|
+
if (shortcut) {
|
|
1120
|
+
// Patch setupExtensionShortcuts so we can capture the extension runner.
|
|
1121
|
+
// This runs before setupExtensionShortcuts is called (during extension
|
|
1122
|
+
// load, which precedes the shortcut setup phase).
|
|
1123
|
+
patchSetupExtensionShortcuts();
|
|
1124
|
+
|
|
1125
|
+
pi.registerShortcut(shortcut as KeyId, {
|
|
1126
|
+
description: "Fast session resume",
|
|
1127
|
+
handler: async (ctx) => {
|
|
1128
|
+
// Use the stored extension runner to get a full command context
|
|
1129
|
+
// with switchSession(), since the shortcut handler ctx (ExtensionContext)
|
|
1130
|
+
// does not include session-switching methods.
|
|
1131
|
+
if (
|
|
1132
|
+
!storedExtensionRunner ||
|
|
1133
|
+
typeof storedExtensionRunner.createCommandContext !== "function"
|
|
1134
|
+
) {
|
|
1135
|
+
ctx.ui.notify(
|
|
1136
|
+
"Fast resume shortcut: extension runner not available. Try reloading with /reload.",
|
|
1137
|
+
"error",
|
|
1138
|
+
);
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
const cmdCtx =
|
|
1142
|
+
storedExtensionRunner.createCommandContext() as ExtensionCommandContext;
|
|
1143
|
+
await showFastResumePicker(cmdCtx);
|
|
1144
|
+
},
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1066
1147
|
|
|
1067
|
-
// Clean up
|
|
1148
|
+
// Clean up prototype patches on session shutdown (reload, quit, session switch)
|
|
1068
1149
|
pi.on("session_shutdown", () => {
|
|
1069
1150
|
if (hijackResume) {
|
|
1070
1151
|
uninstallResumeHijack();
|
|
1071
1152
|
}
|
|
1153
|
+
if (shortcut) {
|
|
1154
|
+
unpatchSetupExtensionShortcuts();
|
|
1155
|
+
}
|
|
1072
1156
|
});
|
|
1073
1157
|
}
|
package/package.json
CHANGED