pi-goosedump 0.7.0 → 0.7.2
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/index.ts +180 -10
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -49,6 +49,7 @@ interface GoosedumpListing {
|
|
|
49
49
|
provider: string;
|
|
50
50
|
nativeId: string;
|
|
51
51
|
cwd: string;
|
|
52
|
+
parent: string | null;
|
|
52
53
|
label: string | null;
|
|
53
54
|
path: string | null;
|
|
54
55
|
}
|
|
@@ -273,6 +274,7 @@ function goosedumpList(provider = 'pi'): GoosedumpListing[] {
|
|
|
273
274
|
provider: entry.provider,
|
|
274
275
|
nativeId: entry.native_id,
|
|
275
276
|
cwd: entry.cwd,
|
|
277
|
+
parent: entry.parent,
|
|
276
278
|
label: entry.label ?? null,
|
|
277
279
|
// Only pi sessions map back to a local file we can resume.
|
|
278
280
|
path: provider === 'pi' ? piSessionFilePath(entry.native_id) : null,
|
|
@@ -414,6 +416,13 @@ function goosedumpCompact(
|
|
|
414
416
|
return renderCompactSummary(json);
|
|
415
417
|
}
|
|
416
418
|
|
|
419
|
+
function goosedumpDelete(id: string, options: { dryRun?: boolean } = {}): string {
|
|
420
|
+
const args = ['delete'];
|
|
421
|
+
if (options.dryRun) args.push('--dry-run');
|
|
422
|
+
args.push(id);
|
|
423
|
+
return runGoosedump(args);
|
|
424
|
+
}
|
|
425
|
+
|
|
417
426
|
export interface GoosedumpIntegrationOptions {
|
|
418
427
|
registerTool?: boolean;
|
|
419
428
|
overrideDefaultCompaction?: boolean;
|
|
@@ -662,19 +671,96 @@ async function runSessionBrowser(
|
|
|
662
671
|
|
|
663
672
|
const OVERLAY_WIDTH = 72;
|
|
664
673
|
|
|
674
|
+
// Build tree from flat listings using parent relationships.
|
|
675
|
+
let listingIds = new Set(listings.map((l) => l.id));
|
|
676
|
+
let roots = listings.filter((l) => l.parent === null || !listingIds.has(l.parent));
|
|
677
|
+
let childrenMap = new Map<string, GoosedumpListing[]>();
|
|
678
|
+
for (const l of listings) {
|
|
679
|
+
if (l.parent !== null) {
|
|
680
|
+
const siblings = childrenMap.get(l.parent) ?? [];
|
|
681
|
+
siblings.push(l);
|
|
682
|
+
childrenMap.set(l.parent, siblings);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function rebuildTree(): void {
|
|
687
|
+
listingIds = new Set(listings.map((l) => l.id));
|
|
688
|
+
roots = listings.filter((l) => l.parent === null || !listingIds.has(l.parent));
|
|
689
|
+
childrenMap = new Map<string, GoosedumpListing[]>();
|
|
690
|
+
for (const l of listings) {
|
|
691
|
+
if (l.parent !== null) {
|
|
692
|
+
const siblings = childrenMap.get(l.parent) ?? [];
|
|
693
|
+
siblings.push(l);
|
|
694
|
+
childrenMap.set(l.parent, siblings);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function truncateCwd(cwd: string, maxLen: number): string {
|
|
700
|
+
if (cwd.length <= maxLen) return cwd;
|
|
701
|
+
const head = Math.floor(maxLen / 2) + 2;
|
|
702
|
+
const tail = maxLen - head - 3;
|
|
703
|
+
if (tail < 4) return cwd.slice(0, maxLen - 3) + '...';
|
|
704
|
+
return cwd.slice(0, head) + '...' + cwd.slice(cwd.length - tail);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function flattenTree(
|
|
708
|
+
nodes: GoosedumpListing[],
|
|
709
|
+
expanded: Set<string>,
|
|
710
|
+
depth: number,
|
|
711
|
+
): { listing: GoosedumpListing; depth: number; hasChildren: boolean }[] {
|
|
712
|
+
const result: { listing: GoosedumpListing; depth: number; hasChildren: boolean }[] = [];
|
|
713
|
+
for (const node of nodes) {
|
|
714
|
+
const children = childrenMap.get(node.id) ?? [];
|
|
715
|
+
result.push({ listing: node, depth, hasChildren: children.length > 0 });
|
|
716
|
+
if (children.length > 0 && expanded.has(node.id)) {
|
|
717
|
+
result.push(...flattenTree(children, expanded, depth + 1));
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return result;
|
|
721
|
+
}
|
|
722
|
+
|
|
665
723
|
await ctx.ui.custom<void>(
|
|
666
724
|
(tui, theme, _kb, done) => {
|
|
725
|
+
let expandedNodes = new Set<string>();
|
|
726
|
+
let displayList = flattenTree(roots, expandedNodes, 0);
|
|
667
727
|
let selectedIndex = 0;
|
|
668
728
|
let listScroll = 0;
|
|
669
729
|
let mode: 'list' | 'compact' = 'list';
|
|
670
730
|
let compactLines: string[] = [];
|
|
671
731
|
let compactScroll = 0;
|
|
672
732
|
let compactTitle = '';
|
|
733
|
+
let pendingDeletionId: string | null = null;
|
|
673
734
|
|
|
674
735
|
const COMPACT_VIEWPORT = 20;
|
|
675
736
|
const LIST_VIEWPORT = 20;
|
|
676
737
|
const wrapWidth = OVERLAY_WIDTH - 4;
|
|
677
738
|
|
|
739
|
+
function rebuildDisplayList(): void {
|
|
740
|
+
displayList = flattenTree(roots, expandedNodes, 0);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function executeDeletion(): void {
|
|
744
|
+
try {
|
|
745
|
+
const id = pendingDeletionId!;
|
|
746
|
+
goosedumpDelete(id);
|
|
747
|
+
// Remove from listings and rebuild tree
|
|
748
|
+
const idx = listings.findIndex((l) => l.id === id);
|
|
749
|
+
if (idx >= 0) listings.splice(idx, 1);
|
|
750
|
+
rebuildTree();
|
|
751
|
+
rebuildDisplayList();
|
|
752
|
+
if (selectedIndex >= displayList.length) {
|
|
753
|
+
selectedIndex = Math.max(0, displayList.length - 1);
|
|
754
|
+
}
|
|
755
|
+
pendingDeletionId = null;
|
|
756
|
+
} catch (err) {
|
|
757
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
758
|
+
ctx.ui.notify(`Failed to delete session: ${msg}`, 'error');
|
|
759
|
+
pendingDeletionId = null;
|
|
760
|
+
}
|
|
761
|
+
tui.requestRender();
|
|
762
|
+
}
|
|
763
|
+
|
|
678
764
|
return {
|
|
679
765
|
render(width: number): string[] {
|
|
680
766
|
const innerW = width - 4;
|
|
@@ -723,15 +809,38 @@ async function runSessionBrowser(
|
|
|
723
809
|
lines.push(makeRow('', innerW, border));
|
|
724
810
|
|
|
725
811
|
// Session list
|
|
726
|
-
const total =
|
|
727
|
-
const window =
|
|
812
|
+
const total = displayList.length;
|
|
813
|
+
const window = displayList.slice(listScroll, listScroll + LIST_VIEWPORT);
|
|
728
814
|
for (let i = 0; i < window.length; i++) {
|
|
729
815
|
const absolute = listScroll + i;
|
|
730
|
-
const
|
|
816
|
+
const entry = window[i];
|
|
731
817
|
const isSelected = absolute === selectedIndex;
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
const
|
|
818
|
+
const deletePending =
|
|
819
|
+
pendingDeletionId !== null && entry.listing.id === pendingDeletionId;
|
|
820
|
+
const cursor = deletePending ? dim('␡ ') : isSelected ? accent('▶') : ' ';
|
|
821
|
+
const indent = ' '.repeat(entry.depth * 2);
|
|
822
|
+
const toggle = entry.hasChildren
|
|
823
|
+
? expandedNodes.has(entry.listing.id)
|
|
824
|
+
? muted('−')
|
|
825
|
+
: muted('+')
|
|
826
|
+
: ' ';
|
|
827
|
+
const idText = deletePending
|
|
828
|
+
? dim(entry.listing.id)
|
|
829
|
+
: isSelected
|
|
830
|
+
? accent(entry.listing.id)
|
|
831
|
+
: text(entry.listing.id);
|
|
832
|
+
|
|
833
|
+
// Prefix: " ▶" (3) or "␡ " (3) + indent + toggle + " " + id (8) + " "
|
|
834
|
+
const prefixLen = 3 + entry.depth * 2 + 1 + 1 + 8 + 1;
|
|
835
|
+
const cwdMax = Math.max(8, innerW - prefixLen);
|
|
836
|
+
const cwdTruncated = truncateCwd(entry.listing.cwd, cwdMax);
|
|
837
|
+
const cwdText = deletePending
|
|
838
|
+
? muted(cwdTruncated)
|
|
839
|
+
: isSelected
|
|
840
|
+
? accent(cwdTruncated)
|
|
841
|
+
: muted(cwdTruncated);
|
|
842
|
+
|
|
843
|
+
const line = ` ${cursor}${indent}${toggle} ${idText} ${cwdText}`;
|
|
735
844
|
lines.push(makeRow(line, innerW, border));
|
|
736
845
|
}
|
|
737
846
|
|
|
@@ -740,11 +849,25 @@ async function runSessionBrowser(
|
|
|
740
849
|
lines.push(makeRow(dim(` (${position})`), innerW, border));
|
|
741
850
|
}
|
|
742
851
|
|
|
852
|
+
// Deletion confirmation
|
|
853
|
+
if (pendingDeletionId) {
|
|
854
|
+
lines.push(makeRow('', innerW, border));
|
|
855
|
+
lines.push(
|
|
856
|
+
makeRow(
|
|
857
|
+
dim(` Delete session ${pendingDeletionId}? enter=confirm esc=cancel`),
|
|
858
|
+
innerW,
|
|
859
|
+
border,
|
|
860
|
+
),
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
|
|
743
864
|
// Footer
|
|
744
865
|
lines.push(makeRow('', innerW, border));
|
|
745
866
|
lines.push(
|
|
746
867
|
makeRow(
|
|
747
|
-
dim(
|
|
868
|
+
dim(
|
|
869
|
+
'↑↓ navigate space/+/− fold enter compact shift+enter resume ctrl+d delete esc close',
|
|
870
|
+
),
|
|
748
871
|
innerW,
|
|
749
872
|
border,
|
|
750
873
|
),
|
|
@@ -800,7 +923,7 @@ async function runSessionBrowser(
|
|
|
800
923
|
}
|
|
801
924
|
|
|
802
925
|
if (matchesKey(data, Key.down)) {
|
|
803
|
-
selectedIndex = Math.min(
|
|
926
|
+
selectedIndex = Math.min(displayList.length - 1, selectedIndex + 1);
|
|
804
927
|
if (selectedIndex >= listScroll + LIST_VIEWPORT) {
|
|
805
928
|
listScroll = selectedIndex - LIST_VIEWPORT + 1;
|
|
806
929
|
}
|
|
@@ -808,14 +931,61 @@ async function runSessionBrowser(
|
|
|
808
931
|
return;
|
|
809
932
|
}
|
|
810
933
|
|
|
934
|
+
// Deletion confirmation: enter confirms, esc cancels
|
|
935
|
+
if (pendingDeletionId) {
|
|
936
|
+
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl('c'))) {
|
|
937
|
+
pendingDeletionId = null;
|
|
938
|
+
tui.requestRender();
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
if (matchesKey(data, Key.enter) || matchesKey(data, Key.return)) {
|
|
942
|
+
executeDeletion();
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Ctrl+D marks session for deletion
|
|
949
|
+
if (matchesKey(data, Key.ctrl('d'))) {
|
|
950
|
+
const listing = displayList[selectedIndex]?.listing;
|
|
951
|
+
if (listing) pendingDeletionId = listing.id;
|
|
952
|
+
tui.requestRender();
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Expand/collapse: space, +, or - key
|
|
957
|
+
const entry = displayList[selectedIndex];
|
|
958
|
+
if (entry && entry.hasChildren && (data === ' ' || data === '+' || data === '-')) {
|
|
959
|
+
const id = entry.listing.id;
|
|
960
|
+
if (data === '+') {
|
|
961
|
+
expandedNodes.add(id);
|
|
962
|
+
} else if (data === '-') {
|
|
963
|
+
expandedNodes.delete(id);
|
|
964
|
+
} else {
|
|
965
|
+
// space = toggle
|
|
966
|
+
if (expandedNodes.has(id)) {
|
|
967
|
+
expandedNodes.delete(id);
|
|
968
|
+
} else {
|
|
969
|
+
expandedNodes.add(id);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
rebuildDisplayList();
|
|
973
|
+
// Keep selection on the same entry
|
|
974
|
+
const newIndex = displayList.findIndex((d) => d.listing.id === id);
|
|
975
|
+
if (newIndex >= 0) selectedIndex = newIndex;
|
|
976
|
+
if (selectedIndex < listScroll) listScroll = selectedIndex;
|
|
977
|
+
tui.requestRender();
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
|
|
811
981
|
if (matchesKey(data, Key.shift(Key.enter))) {
|
|
812
|
-
resumePath =
|
|
982
|
+
resumePath = displayList[selectedIndex]?.listing.path;
|
|
813
983
|
done(undefined);
|
|
814
984
|
return;
|
|
815
985
|
}
|
|
816
986
|
|
|
817
987
|
if (matchesKey(data, Key.enter) || matchesKey(data, Key.return)) {
|
|
818
|
-
const listing =
|
|
988
|
+
const listing = displayList[selectedIndex]?.listing;
|
|
819
989
|
if (!listing) return;
|
|
820
990
|
compactTitle = listing.id;
|
|
821
991
|
compactScroll = 0;
|