pisesh 0.1.12 → 0.1.13
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/CHANGELOG.md +24 -11
- package/README.md +12 -6
- package/bin/pisesh +119 -26
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.1.13] - 2026-08-01
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Press `o` to resume with the model and thinking recorded in the session while `Enter` uses current defaults.
|
|
9
|
+
- Show the current-default and original-session resume modes in the session details view.
|
|
10
|
+
- Support `PI_AGENT_DIR` and `PI_SESSION_DIR`, including flat custom session directories.
|
|
11
|
+
- Add `pisesh --version` and `pisesh --clean-favorites`; press `x` in the TUI to remove stale favorites.
|
|
12
|
+
- Report successful orphan-call repairs and stop with a visible error when a session cannot be repaired safely.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Resume favorite sessions with the current default provider, model, and thinking level instead of restoring stale values recorded in the session.
|
|
16
|
+
- Fall back to pi's native session restore when the settings file is unavailable or invalid.
|
|
17
|
+
|
|
5
18
|
## [0.1.12] - 2026-07-20
|
|
6
19
|
|
|
7
20
|
### Fixed
|
|
@@ -14,39 +27,39 @@ All notable changes to this project will be documented in this file.
|
|
|
14
27
|
- `/sesh` now launches the CLI bundled with the pi package instead of requiring `pisesh` on the user's `PATH`.
|
|
15
28
|
- Clarified that the standalone `pisesh` shell command requires `npm install -g pisesh`.
|
|
16
29
|
|
|
17
|
-
## [0.1.10]
|
|
30
|
+
## [0.1.10] - 2026-06-09
|
|
18
31
|
|
|
19
32
|
### Fixed
|
|
20
|
-
- **Resume no longer crashes on sessions interrupted mid-tool-call.** If a session ended while a tool call was still in flight (e.g. an image generation cut off by a usage limit or a closed window), its transcript held a tool call with no recorded result. On resume, pi would re-fire that dead tool and the spawned process could exit non-zero
|
|
33
|
+
- **Resume no longer crashes on sessions interrupted mid-tool-call.** If a session ended while a tool call was still in flight (e.g. an image generation cut off by a usage limit or a closed window), its transcript held a tool call with no recorded result. On resume, pi would re-fire that dead tool and the spawned process could exit non-zero, surfacing as `pisesh exited with code 1`, repeatedly. `resumeSession` now heals the target session first: every orphaned tool call gets a synthetic `[interrupted]` result injected before pi is spawned, so the resumed history is always well-formed and no dead tool is re-run. Idempotent, never throws, and writes a one-time `.bak-orphanheal` backup of the session before modifying it.
|
|
21
34
|
|
|
22
|
-
## [0.1.9]
|
|
35
|
+
## [0.1.9] - 2026-06-03
|
|
23
36
|
|
|
24
37
|
### Fixed
|
|
25
38
|
- Name / cwd editor (`e`) now has a real caret. Arrow keys (and `Home` / `End`) move inside the text you already typed, so you can insert or delete in the middle instead of only at the end. Backspace, forward `Delete`, and inserts all act at the caret. Cursor moves by whole code points, so CJK and emoji never get split.
|
|
26
39
|
|
|
27
|
-
## [0.1.4]
|
|
40
|
+
## [0.1.4] - 2026-06-03
|
|
28
41
|
|
|
29
42
|
### Added
|
|
30
|
-
- **`Here` tab**
|
|
31
|
-
- **Inline rename (`e`)**
|
|
32
|
-
- **Edit cwd (`p`)**
|
|
43
|
+
- **`Here` tab** filters the list to sessions whose effective cwd matches the directory pisesh was launched from. Tab order is now **★ Favorites → Today → Here → All**.
|
|
44
|
+
- **Inline rename (`e`)** sets a custom display title that overrides the first-prompt label; renamed sessions are marked with a cyan `✎` in the list.
|
|
45
|
+
- **Edit cwd (`p`)** provides an arrow-key directory browser to re-point the working directory pi resumes into (also drives the `Here` filter).
|
|
33
46
|
- Per-session overrides (custom title / cwd) persist to `~/.pi/agent/pisesh-meta.json`, keyed by session id. Session jsonl files remain read-only.
|
|
34
47
|
- README terminal screenshots for the list view, rename panel, and cwd browser.
|
|
35
48
|
|
|
36
|
-
## [0.1.0]
|
|
49
|
+
## [0.1.0] - 2026-05-31
|
|
37
50
|
|
|
38
51
|
Initial release.
|
|
39
52
|
|
|
40
53
|
### Added
|
|
41
|
-
- `pisesh` CLI binary
|
|
54
|
+
- `pisesh` CLI binary with a keyboard-driven TUI that lists every pi session under `~/.pi/agent/sessions/`
|
|
42
55
|
- Tabs: **★ Favorites**, **Today**, **All**
|
|
43
56
|
- Star / unstar with `f` or Space; favorites persist to `~/.pi/agent/favorites.json`
|
|
44
57
|
- Search across id / project / first user prompt with `/`
|
|
45
58
|
- Session details view (`d`): full prompt, file path, byte size, timestamps
|
|
46
59
|
- `Enter` resumes the selected session via `pi --session <id> --session-dir <dir>` in the original cwd
|
|
47
60
|
- `[NOW]` badge marks the session belonging to the pi instance that spawned pisesh (set via `PISESH_CURRENT_SESSION` env var)
|
|
48
|
-
- Alternate screen buffer (`\x1b[?1049h`)
|
|
61
|
+
- Alternate screen buffer (`\x1b[?1049h`) so exit restores terminal byte-for-byte; no scrollback pollution
|
|
49
62
|
- CJK-aware truncation and padding (Hangul / CJK ideographs / emoji counted as 2 cells)
|
|
50
63
|
- Signal handlers (`SIGINT`, `SIGTERM`, `exit`) restore cursor + main buffer on unexpected exit
|
|
51
64
|
- Non-TUI CLI: `--list`, `--json`, `--star <id>`, `--unstar <id>`, `--help`
|
|
52
|
-
- Pi extension at `extensions/sesh.ts`
|
|
65
|
+
- Pi extension at `extensions/sesh.ts` registers `/sesh` slash command which spawns pisesh inside pi via `ui.custom` + `tui.stop()`
|
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ Pi accumulates sessions across many working directories: your home, several proj
|
|
|
45
45
|
- You re-open the wrong session and pollute it with unrelated context
|
|
46
46
|
- You waste time searching by timestamp guessing
|
|
47
47
|
|
|
48
|
-
pisesh is a **single-file Node script** (no dependencies, ~
|
|
48
|
+
pisesh is a **single-file Node script** (no dependencies, ~1,100 LoC) that gives you everything `pi --resume` doesn't.
|
|
49
49
|
|
|
50
50
|
### Value at a glance
|
|
51
51
|
|
|
@@ -61,7 +61,7 @@ pisesh is a **single-file Node script** (no dependencies, ~900 LoC) that gives y
|
|
|
61
61
|
| Read Korean / Chinese / Japanese prompts | Display-width-aware truncation; columns never blow up on CJK |
|
|
62
62
|
| Open from anywhere | Run as standalone `pisesh` shell command, or `/sesh` inside pi |
|
|
63
63
|
| Zero install pain | No build step, no native deps, runs on Node 18+ everywhere |
|
|
64
|
-
| Trust it with your history |
|
|
64
|
+
| Trust it with your history | favorites and overrides stay in sidecar JSON; session history changes only for orphan-call repair |
|
|
65
65
|
|
|
66
66
|
## Getting started
|
|
67
67
|
|
|
@@ -100,7 +100,9 @@ For local pi testing, run `pi install .` from the cloned repository so the exten
|
|
|
100
100
|
| `↑` `↓` / `j` `k` | move cursor |
|
|
101
101
|
| `Tab` / `h` / `l` | switch tab (`★ Favorites` → `Today` → `Here` → `All`) |
|
|
102
102
|
| `f` / `Space` | star / unstar the selected session |
|
|
103
|
-
| `
|
|
103
|
+
| `x` | remove favorites whose session files no longer exist |
|
|
104
|
+
| `Enter` | resume using the current default model and thinking settings |
|
|
105
|
+
| `o` | resume using the model and thinking recorded in the session |
|
|
104
106
|
| `e` | edit name: set a custom display title, shown with `✎` in the list |
|
|
105
107
|
| `p` | edit cwd with an arrow-key directory browser; sets the resume / `Here` dir |
|
|
106
108
|
| `d` | session details (full prompt, file, byte size, timestamps) |
|
|
@@ -120,6 +122,8 @@ pisesh --list # print starred session IDs (one per line)
|
|
|
120
122
|
pisesh --json # full favorites file as JSON
|
|
121
123
|
pisesh --star <partial-uuid> # star a session from a script
|
|
122
124
|
pisesh --unstar <partial-uuid> # unstar
|
|
125
|
+
pisesh --clean-favorites # remove favorites whose sessions are gone
|
|
126
|
+
pisesh --version # print installed version
|
|
123
127
|
pisesh --help
|
|
124
128
|
```
|
|
125
129
|
|
|
@@ -138,6 +142,8 @@ pisesh --help
|
|
|
138
142
|
| Storage | Two JSON files: `~/.pi/agent/favorites.json` (starred ids) + `~/.pi/agent/pisesh-meta.json` (per-session title / cwd overrides) |
|
|
139
143
|
| Session discovery | Direct filesystem scan of `~/.pi/agent/sessions/<projectSlug>/*.jsonl`; first 96 KB parsed |
|
|
140
144
|
| Process model | Slash command pauses pi's TUI, spawns pisesh with inherited stdio, restarts pi on exit |
|
|
145
|
+
| Resume settings | `Enter` uses current defaults; `o` preserves the model and thinking recorded in the session |
|
|
146
|
+
| Custom paths | Honors `PI_AGENT_DIR` and `PI_SESSION_DIR`, including a flat custom session directory |
|
|
141
147
|
|
|
142
148
|
### What it explicitly does **not** depend on
|
|
143
149
|
|
|
@@ -150,9 +156,9 @@ pisesh --help
|
|
|
150
156
|
|
|
151
157
|
| What | Where |
|
|
152
158
|
| ---------- | ----------------------------------------------------------- |
|
|
153
|
-
| Favorites | `~/.pi/agent/favorites.json`
|
|
154
|
-
| Overrides |
|
|
155
|
-
| Sessions |
|
|
159
|
+
| Favorites | `$PI_AGENT_DIR/favorites.json` (defaults to `~/.pi/agent/favorites.json`) |
|
|
160
|
+
| Overrides | `$PI_AGENT_DIR/pisesh-meta.json` (per-session custom title / cwd, keyed by session id) |
|
|
161
|
+
| Sessions | `$PI_SESSION_DIR`, or `$PI_AGENT_DIR/sessions` by default (repaired only when an orphaned tool call would break resume) |
|
|
156
162
|
|
|
157
163
|
Favorites file shape:
|
|
158
164
|
|
package/bin/pisesh
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// pisesh — pi session bookmark & resume TUI
|
|
3
3
|
// Tabs: ★ Favorites / Today / Here / All
|
|
4
|
-
// Storage:
|
|
5
|
-
//
|
|
6
|
-
// Sessions:
|
|
4
|
+
// Storage: $PI_AGENT_DIR/favorites.json (starred session ids)
|
|
5
|
+
// $PI_AGENT_DIR/pisesh-meta.json (per-session title / cwd overrides)
|
|
6
|
+
// Sessions: $PI_SESSION_DIR or $PI_AGENT_DIR/sessions
|
|
7
7
|
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
@@ -15,9 +15,20 @@ const readline = require('readline');
|
|
|
15
15
|
|
|
16
16
|
// ── Paths ─────────────────────────────────────────────
|
|
17
17
|
const HOME = os.homedir();
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
18
|
+
const AGENT_DIR = path.resolve(process.env.PI_AGENT_DIR || path.join(HOME, '.pi/agent'));
|
|
19
|
+
const SESSIONS_ROOT = path.resolve(process.env.PI_SESSION_DIR || path.join(AGENT_DIR, 'sessions'));
|
|
20
|
+
const FAV_FILE = path.join(AGENT_DIR, 'favorites.json');
|
|
21
|
+
const META_FILE = path.join(AGENT_DIR, 'pisesh-meta.json');
|
|
22
|
+
const SETTINGS_FILE = path.join(AGENT_DIR, 'settings.json');
|
|
23
|
+
const VERSION = (() => {
|
|
24
|
+
for (const file of [
|
|
25
|
+
path.resolve(__dirname, '../package.json'),
|
|
26
|
+
path.join(AGENT_DIR, 'npm/node_modules/pisesh/package.json'),
|
|
27
|
+
]) {
|
|
28
|
+
try { return JSON.parse(fs.readFileSync(file, 'utf8')).version; } catch {}
|
|
29
|
+
}
|
|
30
|
+
return 'unknown';
|
|
31
|
+
})();
|
|
21
32
|
|
|
22
33
|
// Current session id (set by the /sesh extension before spawning). Lets us
|
|
23
34
|
// flag the currently-attached session in the list with a [NOW] badge.
|
|
@@ -177,16 +188,29 @@ function readSessionMeta(file) {
|
|
|
177
188
|
}
|
|
178
189
|
|
|
179
190
|
function scanSessions() {
|
|
180
|
-
let
|
|
181
|
-
try {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
191
|
+
let rootEntries;
|
|
192
|
+
try { rootEntries = fs.readdirSync(SESSIONS_ROOT, { withFileTypes: true }); }
|
|
193
|
+
catch { return []; }
|
|
194
|
+
|
|
195
|
+
const projectDirs = rootEntries
|
|
196
|
+
.filter(entry => entry.isDirectory())
|
|
197
|
+
.map(entry => ({
|
|
198
|
+
name: entry.name,
|
|
199
|
+
path: path.join(SESSIONS_ROOT, entry.name),
|
|
200
|
+
label: decodeProjectSlug(entry.name),
|
|
201
|
+
}));
|
|
202
|
+
if (rootEntries.some(entry => entry.isFile() && entry.name.endsWith('.jsonl'))) {
|
|
203
|
+
projectDirs.unshift({
|
|
204
|
+
name: path.basename(SESSIONS_ROOT),
|
|
205
|
+
path: SESSIONS_ROOT,
|
|
206
|
+
label: CURRENT_CWD || SESSIONS_ROOT,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
185
209
|
|
|
186
210
|
const results = [];
|
|
187
211
|
for (const d of projectDirs) {
|
|
188
|
-
const projectPath =
|
|
189
|
-
const projectLabel =
|
|
212
|
+
const projectPath = d.path;
|
|
213
|
+
const projectLabel = d.label;
|
|
190
214
|
let files;
|
|
191
215
|
try { files = fs.readdirSync(projectPath).filter(f => f.endsWith('.jsonl')); }
|
|
192
216
|
catch { continue; }
|
|
@@ -225,12 +249,23 @@ function scanSessions() {
|
|
|
225
249
|
return results;
|
|
226
250
|
}
|
|
227
251
|
|
|
252
|
+
function cleanStaleFavorites(knownSessions = sessions) {
|
|
253
|
+
const knownIds = new Set(knownSessions.map(session => session.id));
|
|
254
|
+
const stale = [...favorites].filter(id => !knownIds.has(id));
|
|
255
|
+
if (stale.length > 0) {
|
|
256
|
+
for (const id of stale) favorites.delete(id);
|
|
257
|
+
saveFavorites(favorites);
|
|
258
|
+
}
|
|
259
|
+
return stale;
|
|
260
|
+
}
|
|
261
|
+
|
|
228
262
|
// ── State ─────────────────────────────────────────────
|
|
229
263
|
let sessions = [];
|
|
230
264
|
let tabs = ['★ Favorites', 'Today', 'Here', 'All'];
|
|
231
265
|
let tabIdx = 0;
|
|
232
266
|
let cursor = 0;
|
|
233
267
|
let filter = '';
|
|
268
|
+
let notice = '';
|
|
234
269
|
let mode = 'list'; // list | filter | details | edit | browse
|
|
235
270
|
// edit mode state (title text editor)
|
|
236
271
|
let editField = ''; // 'title' | 'cwd'
|
|
@@ -450,12 +485,15 @@ function renderList() {
|
|
|
450
485
|
out += ' ' + A.D + '↑↓' + A.R + ' move '
|
|
451
486
|
+ A.D + 'Tab' + A.R + ' tab '
|
|
452
487
|
+ A.D + 'f' + A.R + ' star '
|
|
453
|
-
+ A.D + '
|
|
488
|
+
+ A.D + 'x' + A.R + ' clean '
|
|
489
|
+
+ A.D + 'Enter' + A.R + ' current '
|
|
490
|
+
+ A.D + 'o' + A.R + ' original '
|
|
454
491
|
+ A.D + 'e' + A.R + ' edit name '
|
|
455
492
|
+ A.D + 'p' + A.R + ' edit cwd '
|
|
456
493
|
+ A.D + 'd' + A.R + ' details '
|
|
457
494
|
+ A.D + '/' + A.R + ' search '
|
|
458
495
|
+ A.D + 'q' + A.R + ' quit' + A.R + '\n';
|
|
496
|
+
if (notice) out += ' ' + A.cyn + notice + A.R + '\n';
|
|
459
497
|
|
|
460
498
|
process.stdout.write(out);
|
|
461
499
|
}
|
|
@@ -524,6 +562,11 @@ function renderDetails() {
|
|
|
524
562
|
out += row('cwd', s.effectiveCwd + (s.cwdOverride ? A.cyn + ' (custom)' + A.R : ''), A.mag);
|
|
525
563
|
if (s.cwdOverride) out += row('recorded', s.cwd, A.gry);
|
|
526
564
|
out += row('project', s.project);
|
|
565
|
+
const defaults = loadResumeDefaults();
|
|
566
|
+
out += row('Enter uses', defaults
|
|
567
|
+
? `${defaults.model || 'session model'} / thinking ${defaults.thinking || 'session value'}`
|
|
568
|
+
: 'session model and thinking', A.grn);
|
|
569
|
+
out += row('o uses', 'session-recorded model and thinking', A.gry);
|
|
527
570
|
out += row('size', fmtSize(s.size));
|
|
528
571
|
out += row('file', s.file, A.gry);
|
|
529
572
|
out += '\n ' + A.D + 'first prompt:' + A.R + '\n';
|
|
@@ -532,7 +575,8 @@ function renderDetails() {
|
|
|
532
575
|
for (const line of wrapped) out += ' ' + line + '\n';
|
|
533
576
|
|
|
534
577
|
out += '\n' + A.gry + '─'.repeat(Math.max(1, W - 1)) + A.R + '\n';
|
|
535
|
-
out += ' ' + A.D + 'Enter' + A.R + '
|
|
578
|
+
out += ' ' + A.D + 'Enter' + A.R + ' current defaults '
|
|
579
|
+
+ A.D + 'o' + A.R + ' original settings '
|
|
536
580
|
+ A.D + 'f' + A.R + ' star '
|
|
537
581
|
+ A.D + 'e' + A.R + ' edit name '
|
|
538
582
|
+ A.D + 'p' + A.R + ' edit cwd '
|
|
@@ -618,7 +662,12 @@ function handleList(str, key) {
|
|
|
618
662
|
saveFavorites(favorites);
|
|
619
663
|
}
|
|
620
664
|
}
|
|
621
|
-
else if (k === 'return') resumeSession(list[cursor]);
|
|
665
|
+
else if (k === 'return') resumeSession(list[cursor], true);
|
|
666
|
+
else if (k === 'o') resumeSession(list[cursor], false);
|
|
667
|
+
else if (k === 'x') {
|
|
668
|
+
const removed = cleanStaleFavorites(sessions).length;
|
|
669
|
+
notice = removed ? `removed ${removed} stale favorite${removed === 1 ? '' : 's'}` : 'no stale favorites';
|
|
670
|
+
}
|
|
622
671
|
else if (k === 'd') { if (list[cursor]) mode = 'details'; }
|
|
623
672
|
else if (k === 'e') startEdit(list[cursor], 'title');
|
|
624
673
|
else if (k === 'p') startBrowse(list[cursor]);
|
|
@@ -644,7 +693,8 @@ function handleDetails(str, key) {
|
|
|
644
693
|
const s = visibleSessions()[cursor];
|
|
645
694
|
if (k === 'escape' || k === 'q' || k === 'd') { mode = 'list'; return; }
|
|
646
695
|
if (key.ctrl && k === 'c') return quit();
|
|
647
|
-
if (k === 'return') return resumeSession(s);
|
|
696
|
+
if (k === 'return') return resumeSession(s, true);
|
|
697
|
+
if (k === 'o') return resumeSession(s, false);
|
|
648
698
|
if (k === 'f' && s) {
|
|
649
699
|
if (s.favored) favorites.delete(s.id); else favorites.add(s.id);
|
|
650
700
|
saveFavorites(favorites);
|
|
@@ -845,7 +895,7 @@ function tryCopy(text) {
|
|
|
845
895
|
// as "pisesh exited with code N". We inject a synthetic "[interrupted]"
|
|
846
896
|
// toolResult for unfinished valid turns, but not errored/aborted assistant turns
|
|
847
897
|
// that pi omits during replay. Old incompatible synthetic results are removed.
|
|
848
|
-
// Idempotent
|
|
898
|
+
// Idempotent. Throws when the session cannot be read, backed up, or written.
|
|
849
899
|
function healOrphanedToolCalls(file) {
|
|
850
900
|
try {
|
|
851
901
|
const lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean);
|
|
@@ -912,29 +962,61 @@ function healOrphanedToolCalls(file) {
|
|
|
912
962
|
}
|
|
913
963
|
const changed = removedParents.size + inserted;
|
|
914
964
|
if (changed > 0) {
|
|
915
|
-
|
|
965
|
+
const bak = file + '.bak-orphanheal';
|
|
966
|
+
if (!fs.existsSync(bak)) fs.copyFileSync(file, bak);
|
|
916
967
|
fs.writeFileSync(file, out.join('\n') + '\n');
|
|
917
968
|
}
|
|
918
969
|
return changed;
|
|
919
|
-
} catch {
|
|
970
|
+
} catch (error) {
|
|
971
|
+
throw new Error(`session repair failed: ${error.message}`);
|
|
972
|
+
}
|
|
920
973
|
}
|
|
921
974
|
|
|
922
|
-
function
|
|
975
|
+
function loadResumeDefaults(settingsFile = SETTINGS_FILE) {
|
|
976
|
+
try {
|
|
977
|
+
const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
|
978
|
+
return {
|
|
979
|
+
model: settings.defaultProvider && settings.defaultModel
|
|
980
|
+
? `${settings.defaultProvider}/${settings.defaultModel}`
|
|
981
|
+
: '',
|
|
982
|
+
thinking: settings.defaultThinkingLevel || '',
|
|
983
|
+
};
|
|
984
|
+
} catch { return null; }
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function buildResumeArgs(s, useCurrentDefaults = true, settingsFile = SETTINGS_FILE) {
|
|
988
|
+
const args = ['--session', s.id, '--session-dir', path.dirname(s.file)];
|
|
989
|
+
const defaults = useCurrentDefaults && loadResumeDefaults(settingsFile);
|
|
990
|
+
if (defaults?.model) args.push('--model', defaults.model);
|
|
991
|
+
if (defaults?.thinking) args.push('--thinking', defaults.thinking);
|
|
992
|
+
return args;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
function resumeSession(s, useCurrentDefaults = true) {
|
|
923
996
|
if (!s) return;
|
|
924
997
|
// Heal orphaned tool calls before handing off, so resume can't crash the
|
|
925
998
|
// spawned pi (see healOrphanedToolCalls).
|
|
926
|
-
|
|
999
|
+
let repaired;
|
|
1000
|
+
try { repaired = healOrphanedToolCalls(s.file); }
|
|
1001
|
+
catch (error) {
|
|
1002
|
+
process.stdout.write(A.exitAlt + A.showC);
|
|
1003
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
1004
|
+
console.error(`pisesh: ${error.message}`);
|
|
1005
|
+
process.exit(1);
|
|
1006
|
+
}
|
|
927
1007
|
// Leave alt screen + show cursor so the spawned pi takes over a clean
|
|
928
1008
|
// main-buffer terminal (it will manage its own alt screen).
|
|
929
1009
|
process.stdout.write(A.exitAlt + A.showC);
|
|
930
1010
|
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
931
1011
|
process.stdin.pause();
|
|
1012
|
+
if (repaired > 0) {
|
|
1013
|
+
console.log(`pisesh: repaired ${repaired} orphaned session entr${repaired === 1 ? 'y' : 'ies'}; backup: ${s.file}.bak-orphanheal`);
|
|
1014
|
+
}
|
|
932
1015
|
|
|
933
|
-
const projectDir = path.dirname(s.file);
|
|
934
1016
|
// Resume in the session's effective cwd (user override wins over recorded),
|
|
935
1017
|
// falling back to pisesh's own cwd if that directory no longer exists.
|
|
936
1018
|
const targetCwd = s.effectiveCwd;
|
|
937
|
-
const child = spawn('pi',
|
|
1019
|
+
const child = spawn('pi', buildResumeArgs(s, useCurrentDefaults), {
|
|
938
1020
|
stdio: 'inherit',
|
|
939
1021
|
cwd: targetCwd && fs.existsSync(targetCwd) ? targetCwd : process.cwd(),
|
|
940
1022
|
});
|
|
@@ -964,13 +1046,17 @@ Usage:
|
|
|
964
1046
|
pisesh --json Dump favorites file as JSON
|
|
965
1047
|
pisesh --star <id> Add a session id (partial UUID ok) to favorites
|
|
966
1048
|
pisesh --unstar <id> Remove a session id from favorites
|
|
1049
|
+
pisesh --clean-favorites Remove favorites whose sessions no longer exist
|
|
1050
|
+
pisesh --version, -v Print the installed version
|
|
967
1051
|
pisesh --help, -h This help
|
|
968
1052
|
|
|
969
1053
|
TUI keys:
|
|
970
1054
|
↑↓ / j k move cursor
|
|
971
1055
|
Tab / h l switch tab (★ Favorites → Today → Here → All)
|
|
972
1056
|
f / Space toggle favorite
|
|
973
|
-
|
|
1057
|
+
x remove favorites whose sessions no longer exist
|
|
1058
|
+
Enter resume with current default model and thinking settings
|
|
1059
|
+
o resume with the session-recorded model and thinking settings
|
|
974
1060
|
e edit name (custom display title)
|
|
975
1061
|
p edit cwd — arrow-key directory browser
|
|
976
1062
|
(↑↓ move, →/Enter open dir, ← parent, s select, Esc cancel)
|
|
@@ -1000,6 +1086,13 @@ function main() {
|
|
|
1000
1086
|
const argv = process.argv.slice(2);
|
|
1001
1087
|
|
|
1002
1088
|
if (argv.includes('-h') || argv.includes('--help')) { printHelp(); return; }
|
|
1089
|
+
if (argv.includes('-v') || argv.includes('--version')) { console.log(VERSION); return; }
|
|
1090
|
+
if (argv.includes('--clean-favorites')) {
|
|
1091
|
+
sessions = scanSessions();
|
|
1092
|
+
const stale = cleanStaleFavorites(sessions);
|
|
1093
|
+
console.log(`removed ${stale.length} stale favorite${stale.length === 1 ? '' : 's'}`);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1003
1096
|
if (argv.includes('-l') || argv.includes('--list')) {
|
|
1004
1097
|
[...favorites].forEach(id => console.log(id));
|
|
1005
1098
|
return;
|
|
@@ -1041,4 +1134,4 @@ function main() {
|
|
|
1041
1134
|
}
|
|
1042
1135
|
|
|
1043
1136
|
if (require.main === module) main();
|
|
1044
|
-
else module.exports = { healOrphanedToolCalls };
|
|
1137
|
+
else module.exports = { buildResumeArgs, cleanStaleFavorites, healOrphanedToolCalls, loadResumeDefaults };
|