pi-goosedump 0.4.0 → 0.5.1

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.
Files changed (2) hide show
  1. package/index.ts +261 -260
  2. package/package.json +2 -2
package/index.ts CHANGED
@@ -23,10 +23,11 @@ import { Type } from '@sinclair/typebox';
23
23
 
24
24
  const require = createRequire(import.meta.url);
25
25
 
26
- const GOOSEDUMP_VERSION = [0, 4, 2] as const;
26
+ const GOOSEDUMP_VERSION = [0, 5, 0] as const;
27
27
 
28
28
  interface GoosedumpListing {
29
29
  id: string;
30
+ path: string | null;
30
31
  }
31
32
 
32
33
  interface GoosedumpMessage {
@@ -36,15 +37,14 @@ interface GoosedumpMessage {
36
37
  score?: number;
37
38
  }
38
39
 
39
- interface GoosedumpContextResult {
40
+ interface GoosedumpSearchResult {
40
41
  messages: GoosedumpMessage[];
41
- totalCount: number;
42
42
  page: number;
43
43
  totalPages: number;
44
44
  }
45
45
 
46
46
  interface ListJson {
47
- sessions: { id: string }[];
47
+ sessions: { id: string; path?: string | null }[];
48
48
  }
49
49
 
50
50
  interface ToolCallJson {
@@ -86,10 +86,6 @@ interface SearchJson {
86
86
  hits: HitJson[];
87
87
  }
88
88
 
89
- interface GrepJson {
90
- hits: HitJson[];
91
- }
92
-
93
89
  interface CompactJson {
94
90
  goals: string[];
95
91
  files: { modified: string[]; created: string[]; read: string[] };
@@ -232,6 +228,14 @@ function formatMessagesFull(messages: GoosedumpMessage[]): string {
232
228
  return messages.map((m) => `[${m.id}] ${m.role}:\n${m.content}`).join('\n\n---\n\n');
233
229
  }
234
230
 
231
+ function textResult(text: string): AgentToolResult<void> {
232
+ return { content: [{ type: 'text', text }], details: undefined };
233
+ }
234
+
235
+ function missingContextId(action: string): AgentToolResult<void> {
236
+ return textResult(`contextId is required for ${action} when no current session is available`);
237
+ }
238
+
235
239
  function runGoosedump(args: string[]): string {
236
240
  return execFileSync(process.execPath, [binaryPath(), ...args], {
237
241
  encoding: 'utf-8',
@@ -240,62 +244,37 @@ function runGoosedump(args: string[]): string {
240
244
 
241
245
  function goosedumpList(): GoosedumpListing[] {
242
246
  const json = JSON.parse(runGoosedump(['list', 'pi:*'])) as ListJson;
243
- return (json.sessions ?? []).map((session) => ({ id: session.id }));
247
+ return (json.sessions ?? []).map((session) => ({
248
+ id: session.id,
249
+ path: session.path ?? null,
250
+ }));
244
251
  }
245
252
 
246
- function goosedumpContext(
253
+ function goosedumpSearch(
247
254
  contextId: string,
248
- options: {
249
- scope?: string;
250
- grep?: string;
251
- ids?: string;
252
- rank?: boolean;
253
- page?: number;
254
- } = {},
255
- ): GoosedumpContextResult {
255
+ query: string,
256
+ options: { scope?: string; page?: number } = {},
257
+ ): GoosedumpSearchResult {
256
258
  const scope = options.scope ?? 'lineage';
257
- const hasQuery = typeof options.grep === 'string';
258
- const isSearch = hasQuery && options.rank !== false;
259
- const target = `pi:${contextId}`;
260
- const args = hasQuery
261
- ? isSearch
262
- ? ['search', target, options.grep ?? '']
263
- : ['grep', target, options.grep ?? '']
264
- : ['show', target];
265
-
266
- if (options.ids) {
267
- args.push('--ids', options.ids);
268
- }
269
- if (isSearch && options.page) {
270
- args.push('--page', String(options.page));
271
- }
259
+ const args = ['search', `pi:${contextId}`, query];
260
+ if (options.page) args.push('--page', String(options.page));
272
261
  args.push('--scope', scope);
273
262
 
274
- const output = runGoosedump(args);
275
-
276
- if (hasQuery) {
277
- const json = JSON.parse(output) as SearchJson | GrepJson;
278
- const messages = (json.hits ?? []).map(hitMessage);
279
- const page = isSearch ? ((json as SearchJson).page ?? options.page ?? 1) : 1;
280
- const totalPages = isSearch ? ((json as SearchJson).totalPages ?? 1) : 1;
281
- return { messages, totalCount: messages.length, page, totalPages };
282
- }
283
-
284
- const json = JSON.parse(output) as ShowJson;
285
- const messages = (json.messages ?? []).map(showMessage);
286
- return { messages, totalCount: messages.length, page: options.page ?? 1, totalPages: 1 };
263
+ const json = JSON.parse(runGoosedump(args)) as SearchJson;
264
+ const messages = (json.hits ?? []).map(hitMessage);
265
+ return { messages, page: json.page ?? options.page ?? 1, totalPages: json.totalPages ?? 1 };
287
266
  }
288
267
 
289
- function goosedumpExpand(
268
+ function goosedumpShow(
290
269
  contextId: string,
291
- entryIds: string[],
292
- scope: string = 'lineage',
270
+ options: { scope?: string; ids?: string[] } = {},
293
271
  ): GoosedumpMessage[] {
294
- if (entryIds.length === 0) return [];
272
+ const scope = options.scope ?? 'lineage';
273
+ const args = ['show', `pi:${contextId}`];
274
+ if (options.ids && options.ids.length > 0) args.push('--ids', options.ids.join(','));
275
+ args.push('--scope', scope);
295
276
 
296
- const json = JSON.parse(
297
- runGoosedump(['show', `pi:${contextId}`, '--ids', entryIds.join(','), '--scope', scope]),
298
- ) as ShowJson;
277
+ const json = JSON.parse(runGoosedump(args)) as ShowJson;
299
278
  return (json.messages ?? []).map(showMessage);
300
279
  }
301
280
 
@@ -588,14 +567,204 @@ function buildGoosedumpCompaction(
588
567
  };
589
568
  }
590
569
 
570
+ async function runSessionBrowser(
571
+ ctx: ExtensionContext,
572
+ listings: GoosedumpListing[],
573
+ ): Promise<string | null | undefined> {
574
+ let resumePath: string | null | undefined;
575
+
576
+ await ctx.ui.custom<void>(
577
+ (tui, theme, _kb, done) => {
578
+ let selectedIndex = 0;
579
+ let mode: 'list' | 'compact' = 'list';
580
+ let compactLines: string[] = [];
581
+ let compactScroll = 0;
582
+ let compactTitle = '';
583
+
584
+ const COMPACT_VIEWPORT = 20;
585
+
586
+ return {
587
+ render(width: number): string[] {
588
+ const innerW = width - 4;
589
+ const border = theme.fg('border', '│');
590
+ const borderFg = (s: string) => theme.fg('border', s);
591
+ const dim = (s: string) => theme.fg('dim', s);
592
+ const accent = (s: string) => theme.fg('accent', s);
593
+ const text = (s: string) => theme.fg('text', s);
594
+ const muted = (s: string) => theme.fg('muted', s);
595
+
596
+ const lines: string[] = [];
597
+
598
+ if (mode === 'compact') {
599
+ const title = accent(` Compact: ${truncateToWidth(compactTitle, 40)} `);
600
+ const topFill = borderFg('─'.repeat(Math.max(0, width - 4 - visibleWidth(title))));
601
+ lines.push(`${borderFg('╭─')}${title}${topFill}${borderFg('─╮')}`);
602
+
603
+ const total = compactLines.length;
604
+ const maxScroll = Math.max(0, total - COMPACT_VIEWPORT);
605
+ if (compactScroll > maxScroll) compactScroll = maxScroll;
606
+ const window = compactLines.slice(compactScroll, compactScroll + COMPACT_VIEWPORT);
607
+ for (const row of window) {
608
+ lines.push(makeRow(text(truncateToWidth(row, innerW)), innerW, border));
609
+ }
610
+ for (let i = window.length; i < COMPACT_VIEWPORT; i++) {
611
+ lines.push(makeRow('', innerW, border));
612
+ }
613
+
614
+ const position =
615
+ total > COMPACT_VIEWPORT
616
+ ? `${compactScroll + 1}-${Math.min(compactScroll + COMPACT_VIEWPORT, total)} of ${total}`
617
+ : `${total} line${total === 1 ? '' : 's'}`;
618
+ lines.push(makeRow('', innerW, border));
619
+ lines.push(makeRow(dim(`↑↓ scroll esc back (${position})`), innerW, border));
620
+ lines.push(`${borderFg('╰')}${borderFg('─'.repeat(width - 2))}${borderFg('╯')}`);
621
+ return lines;
622
+ }
623
+
624
+ // Top border
625
+ const title = accent(' Goosedump Sessions ');
626
+ const topFill = borderFg('─'.repeat(Math.max(0, width - 4 - visibleWidth(title))));
627
+ lines.push(`${borderFg('╭─')}${title}${topFill}${borderFg('─╮')}`);
628
+
629
+ // Session count
630
+ const countInfo = `${listings.length} session${listings.length === 1 ? '' : 's'}`;
631
+ lines.push(makeRow(`${muted(countInfo)}`, innerW, border));
632
+
633
+ lines.push(makeRow('', innerW, border));
634
+
635
+ // Session list
636
+ const maxItems = Math.min(listings.length, 20);
637
+ for (let i = 0; i < maxItems; i++) {
638
+ const listing = listings[i];
639
+ const isSelected = i === selectedIndex;
640
+ const cursor = isSelected ? accent('▶') : ' ';
641
+ const idText = isSelected ? accent(listing.id) : text(listing.id);
642
+ const line = ` ${cursor} ${idText}`;
643
+ lines.push(makeRow(truncateToWidth(line, innerW), innerW, border));
644
+ }
645
+
646
+ if (listings.length > 20) {
647
+ lines.push(makeRow(dim(` ... and ${listings.length - 20} more`), innerW, border));
648
+ }
649
+
650
+ // Footer
651
+ lines.push(makeRow('', innerW, border));
652
+ lines.push(
653
+ makeRow(
654
+ dim('↑↓ navigate enter compact shift+enter resume esc close'),
655
+ innerW,
656
+ border,
657
+ ),
658
+ );
659
+
660
+ // Bottom border
661
+ lines.push(`${borderFg('╰')}${borderFg('─'.repeat(width - 2))}${borderFg('╯')}`);
662
+
663
+ return lines;
664
+ },
665
+
666
+ handleInput(data: string): void {
667
+ if (mode === 'compact') {
668
+ if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl('c'))) {
669
+ mode = 'list';
670
+ tui.requestRender();
671
+ return;
672
+ }
673
+ if (matchesKey(data, Key.up)) {
674
+ compactScroll = Math.max(0, compactScroll - 1);
675
+ tui.requestRender();
676
+ return;
677
+ }
678
+ if (matchesKey(data, Key.down)) {
679
+ compactScroll += 1;
680
+ tui.requestRender();
681
+ return;
682
+ }
683
+ if (matchesKey(data, Key.pageUp)) {
684
+ compactScroll = Math.max(0, compactScroll - COMPACT_VIEWPORT);
685
+ tui.requestRender();
686
+ return;
687
+ }
688
+ if (matchesKey(data, Key.pageDown)) {
689
+ compactScroll += COMPACT_VIEWPORT;
690
+ tui.requestRender();
691
+ return;
692
+ }
693
+ return;
694
+ }
695
+
696
+ if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl('c'))) {
697
+ done(undefined);
698
+ return;
699
+ }
700
+
701
+ if (matchesKey(data, Key.up)) {
702
+ selectedIndex = Math.max(0, selectedIndex - 1);
703
+ tui.requestRender();
704
+ return;
705
+ }
706
+
707
+ if (matchesKey(data, Key.down)) {
708
+ selectedIndex = Math.min(listings.length - 1, selectedIndex + 1);
709
+ tui.requestRender();
710
+ return;
711
+ }
712
+
713
+ if (matchesKey(data, Key.shift(Key.enter))) {
714
+ resumePath = listings[selectedIndex]?.path;
715
+ done(undefined);
716
+ return;
717
+ }
718
+
719
+ if (matchesKey(data, Key.enter) || matchesKey(data, Key.return)) {
720
+ const listing = listings[selectedIndex];
721
+ if (!listing) return;
722
+ compactTitle = listing.id;
723
+ compactScroll = 0;
724
+ try {
725
+ const summary = goosedumpCompact(listing.id, { scope: 'lineage' }).trim();
726
+ compactLines = summary
727
+ ? summary.split('\n')
728
+ : ['No compact summary available for this session.'];
729
+ } catch (err) {
730
+ const msg = err instanceof Error ? err.message : String(err);
731
+ compactLines = [`Failed to compact session: ${msg}`];
732
+ }
733
+ mode = 'compact';
734
+ tui.requestRender();
735
+ return;
736
+ }
737
+ },
738
+
739
+ invalidate(): void {},
740
+ };
741
+
742
+ function makeRow(content: string, innerW: number, border: string): string {
743
+ const line = truncateToWidth(content, innerW);
744
+ const pad = Math.max(0, innerW - visibleWidth(line));
745
+ return `${border} ${line}${' '.repeat(pad)} ${border}`;
746
+ }
747
+ },
748
+ {
749
+ overlay: true,
750
+ overlayOptions: {
751
+ anchor: 'center',
752
+ width: 72,
753
+ margin: 2,
754
+ },
755
+ },
756
+ );
757
+
758
+ return resumePath;
759
+ }
760
+
591
761
  export function createGoosedumpIntegration(
592
762
  options: GoosedumpIntegrationOptions = {},
593
763
  ): GoosedumpIntegration {
594
764
  const shouldRegisterTool = options.registerTool ?? true;
595
765
  const shouldOverrideDefaultCompaction = options.overrideDefaultCompaction ?? true;
596
766
 
597
- let goosedumpReady = false;
598
- let goosedumpEnabled = false;
767
+ let goosedumpAvailable = false;
599
768
 
600
769
  function checkGoosedump(ctx?: ExtensionContext): boolean {
601
770
  const version = goosedumpVersion();
@@ -686,16 +855,10 @@ export function createGoosedumpIntegration(
686
855
  ),
687
856
  }),
688
857
  async execute(_id, params, _signal, _onUpdate, ctx): Promise<AgentToolResult<void>> {
689
- if (!goosedumpEnabled || !goosedumpReady) {
690
- return {
691
- content: [
692
- {
693
- type: 'text',
694
- text: 'goosedump is not available. Install with: npm install @jarkkojs/goosedump',
695
- },
696
- ],
697
- details: undefined,
698
- };
858
+ if (!goosedumpAvailable) {
859
+ return textResult(
860
+ 'goosedump is not available. Install with: npm install @jarkkojs/goosedump',
861
+ );
699
862
  }
700
863
 
701
864
  try {
@@ -703,10 +866,7 @@ export function createGoosedumpIntegration(
703
866
  case 'list': {
704
867
  const listings = goosedumpList();
705
868
  if (listings.length === 0) {
706
- return {
707
- content: [{ type: 'text', text: 'No sessions found.' }],
708
- details: undefined,
709
- };
869
+ return textResult('No sessions found.');
710
870
  }
711
871
 
712
872
  const lines = ['Available sessions:', ''];
@@ -714,45 +874,22 @@ export function createGoosedumpIntegration(
714
874
  lines.push(` ${listing.id}`);
715
875
  }
716
876
 
717
- return {
718
- content: [{ type: 'text', text: lines.join('\n') }],
719
- details: undefined,
720
- };
877
+ return textResult(lines.join('\n'));
721
878
  }
722
879
 
723
880
  case 'search': {
724
881
  const contextId = params.contextId ?? currentSessionContextId(ctx);
725
- if (!contextId) {
726
- return {
727
- content: [
728
- {
729
- type: 'text',
730
- text: 'contextId is required for search when no current session is available',
731
- },
732
- ],
733
- details: undefined,
734
- };
735
- }
882
+ if (!contextId) return missingContextId('search');
736
883
 
737
- if (!params.query) {
738
- return {
739
- content: [{ type: 'text', text: 'query is required for search' }],
740
- details: undefined,
741
- };
742
- }
884
+ if (!params.query) return textResult('query is required for search');
743
885
 
744
- const result = goosedumpContext(contextId, {
886
+ const result = goosedumpSearch(contextId, params.query, {
745
887
  scope: params.scope ?? 'lineage',
746
- grep: params.query,
747
- rank: true,
748
888
  page: params.page ?? 1,
749
889
  });
750
890
 
751
891
  if (result.messages.length === 0) {
752
- return {
753
- content: [{ type: 'text', text: `No results for "${params.query}"` }],
754
- details: undefined,
755
- };
892
+ return textResult(`No results for "${params.query}"`);
756
893
  }
757
894
 
758
895
  const useCompact = params.compact ?? true;
@@ -761,97 +898,48 @@ export function createGoosedumpIntegration(
761
898
  : formatMessagesFull(result.messages);
762
899
 
763
900
  const header = `Results for "${params.query}" (page ${result.page} of ${result.totalPages}):\n\n`;
764
- return {
765
- content: [{ type: 'text', text: header + text }],
766
- details: undefined,
767
- };
901
+ return textResult(header + text);
768
902
  }
769
903
 
770
904
  case 'expand': {
771
905
  const contextId = params.contextId ?? currentSessionContextId(ctx);
772
- if (!contextId) {
773
- return {
774
- content: [
775
- {
776
- type: 'text',
777
- text: 'contextId is required for expand when no current session is available',
778
- },
779
- ],
780
- details: undefined,
781
- };
782
- }
906
+ if (!contextId) return missingContextId('expand');
783
907
 
784
908
  if (!params.ids || params.ids.length === 0) {
785
- return {
786
- content: [
787
- { type: 'text', text: 'ids is required for expand (array of entry IDs)' },
788
- ],
789
- details: undefined,
790
- };
909
+ return textResult('ids is required for expand (array of entry IDs)');
791
910
  }
792
911
 
793
- const messages = goosedumpExpand(
794
- contextId,
795
- params.ids,
796
- params.scope ?? 'lineage',
797
- );
912
+ const messages = goosedumpShow(contextId, {
913
+ scope: params.scope ?? 'lineage',
914
+ ids: params.ids,
915
+ });
798
916
 
799
917
  if (messages.length === 0) {
800
- return {
801
- content: [{ type: 'text', text: 'No entries found for the given IDs.' }],
802
- details: undefined,
803
- };
918
+ return textResult('No entries found for the given IDs.');
804
919
  }
805
920
 
806
- return {
807
- content: [{ type: 'text', text: formatMessagesFull(messages) }],
808
- details: undefined,
809
- };
921
+ return textResult(formatMessagesFull(messages));
810
922
  }
811
923
 
812
924
  case 'view': {
813
925
  const contextId = params.contextId ?? currentSessionContextId(ctx);
814
- if (!contextId) {
815
- return {
816
- content: [
817
- {
818
- type: 'text',
819
- text: 'contextId is required for view when no current session is available',
820
- },
821
- ],
822
- details: undefined,
823
- };
824
- }
926
+ if (!contextId) return missingContextId('view');
825
927
 
826
- const result = goosedumpContext(contextId, {
827
- scope: params.scope ?? 'lineage',
828
- });
928
+ const messages = goosedumpShow(contextId, { scope: params.scope ?? 'lineage' });
829
929
 
830
- if (result.messages.length === 0) {
831
- return {
832
- content: [{ type: 'text', text: `Session "${contextId}" has no messages.` }],
833
- details: undefined,
834
- };
930
+ if (messages.length === 0) {
931
+ return textResult(`Session "${contextId}" has no messages.`);
835
932
  }
836
933
 
837
- return {
838
- content: [{ type: 'text', text: formatMessagesFull(result.messages) }],
839
- details: undefined,
840
- };
934
+ return textResult(formatMessagesFull(messages));
841
935
  }
842
936
 
843
937
  default:
844
- return {
845
- content: [{ type: 'text', text: `Unknown action: ${params.action}` }],
846
- details: undefined,
847
- };
938
+ return textResult(`Unknown action: ${params.action}`);
848
939
  }
849
940
  } catch (err) {
850
941
  const message = err instanceof Error ? err.message : String(err);
851
- return {
852
- content: [{ type: 'text', text: `goosedump error: ${message}` }],
853
- details: undefined,
854
- };
942
+ return textResult(`goosedump error: ${message}`);
855
943
  }
856
944
  },
857
945
  }),
@@ -859,10 +947,9 @@ export function createGoosedumpIntegration(
859
947
  }
860
948
 
861
949
  pi.on('session_start', async (_event, ctx) => {
862
- goosedumpReady = checkGoosedump(ctx);
863
- goosedumpEnabled = goosedumpReady;
950
+ goosedumpAvailable = checkGoosedump(ctx);
864
951
 
865
- if (goosedumpReady) {
952
+ if (goosedumpAvailable) {
866
953
  const theme = ctx.ui.theme;
867
954
  const dot = theme.fg('success', '●');
868
955
  const label = theme.fg('text', 'Goosedump');
@@ -871,7 +958,7 @@ export function createGoosedumpIntegration(
871
958
  });
872
959
 
873
960
  pi.on('session_before_compact', async (event, ctx) => {
874
- if (!shouldOverrideDefaultCompaction || !goosedumpEnabled || !goosedumpReady) return;
961
+ if (!shouldOverrideDefaultCompaction || !goosedumpAvailable) return;
875
962
  if (event.customInstructions?.trim()) return;
876
963
  if (event.signal.aborted) return;
877
964
 
@@ -901,7 +988,7 @@ export function createGoosedumpIntegration(
901
988
  });
902
989
 
903
990
  pi.on('session_compact', async (event, ctx) => {
904
- if (!goosedumpEnabled || !goosedumpReady || !event.fromExtension) return;
991
+ if (!goosedumpAvailable || !event.fromExtension) return;
905
992
 
906
993
  const compactionEntry = event.compactionEntry;
907
994
  if (!compactionEntry) return;
@@ -925,7 +1012,7 @@ export function createGoosedumpIntegration(
925
1012
  maybePi.registerCommand?.('goosedump', {
926
1013
  description: 'Browse coding agent session history',
927
1014
  handler: async (_args, ctx) => {
928
- if (!goosedumpEnabled || !goosedumpReady) {
1015
+ if (!goosedumpAvailable) {
929
1016
  ctx.ui.notify(
930
1017
  'goosedump is not available. Install with: npm install @jarkkojs/goosedump',
931
1018
  'error',
@@ -941,99 +1028,13 @@ export function createGoosedumpIntegration(
941
1028
  return;
942
1029
  }
943
1030
 
944
- await ctx.ui.custom<void>(
945
- (tui, theme, _kb, done) => {
946
- let selectedIndex = 0;
947
-
948
- return {
949
- render(width: number): string[] {
950
- const innerW = width - 4;
951
- const border = theme.fg('border', '│');
952
- const borderFg = (s: string) => theme.fg('border', s);
953
- const dim = (s: string) => theme.fg('dim', s);
954
- const accent = (s: string) => theme.fg('accent', s);
955
- const text = (s: string) => theme.fg('text', s);
956
- const muted = (s: string) => theme.fg('muted', s);
957
-
958
- const lines: string[] = [];
959
-
960
- // Top border
961
- const title = accent(' Goosedump Sessions ');
962
- const topFill = borderFg(
963
- '─'.repeat(Math.max(0, width - 4 - visibleWidth(title))),
964
- );
965
- lines.push(`${borderFg('╭─')}${title}${topFill}${borderFg('─╮')}`);
966
-
967
- // Session count
968
- const countInfo = `${listings.length} session${listings.length === 1 ? '' : 's'}`;
969
- lines.push(makeRow(`${muted(countInfo)}`, innerW, border));
970
-
971
- lines.push(makeRow('', innerW, border));
972
-
973
- // Session list
974
- const maxItems = Math.min(listings.length, 20);
975
- for (let i = 0; i < maxItems; i++) {
976
- const listing = listings[i];
977
- const isSelected = i === selectedIndex;
978
- const cursor = isSelected ? accent('▶') : ' ';
979
- const idText = isSelected ? accent(listing.id) : text(listing.id);
980
- const line = ` ${cursor} ${idText}`;
981
- lines.push(makeRow(truncateToWidth(line, innerW), innerW, border));
982
- }
983
-
984
- if (listings.length > 20) {
985
- lines.push(
986
- makeRow(dim(` ... and ${listings.length - 20} more`), innerW, border),
987
- );
988
- }
989
-
990
- // Footer
991
- lines.push(makeRow('', innerW, border));
992
- lines.push(makeRow(dim('↑↓ navigate esc close'), innerW, border));
993
-
994
- // Bottom border
995
- lines.push(`${borderFg('╰')}${borderFg('─'.repeat(width - 2))}${borderFg('╯')}`);
996
-
997
- return lines;
998
- },
1031
+ const resumePath = await runSessionBrowser(ctx, listings);
999
1032
 
1000
- handleInput(data: string): void {
1001
- if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl('c'))) {
1002
- done(undefined);
1003
- return;
1004
- }
1005
-
1006
- if (matchesKey(data, Key.up)) {
1007
- selectedIndex = Math.max(0, selectedIndex - 1);
1008
- tui.requestRender();
1009
- return;
1010
- }
1011
-
1012
- if (matchesKey(data, Key.down)) {
1013
- selectedIndex = Math.min(listings.length - 1, selectedIndex + 1);
1014
- tui.requestRender();
1015
- return;
1016
- }
1017
- },
1018
-
1019
- invalidate(): void {},
1020
- };
1021
-
1022
- function makeRow(content: string, innerW: number, border: string): string {
1023
- const line = truncateToWidth(content, innerW);
1024
- const pad = Math.max(0, innerW - visibleWidth(line));
1025
- return `${border} ${line}${' '.repeat(pad)} ${border}`;
1026
- }
1027
- },
1028
- {
1029
- overlay: true,
1030
- overlayOptions: {
1031
- anchor: 'center',
1032
- width: 72,
1033
- margin: 2,
1034
- },
1035
- },
1036
- );
1033
+ if (resumePath) {
1034
+ await ctx.switchSession(resumePath);
1035
+ } else if (resumePath === null) {
1036
+ ctx.ui.notify('Cannot resume: session file path is unavailable.', 'error');
1037
+ }
1037
1038
  } catch (err) {
1038
1039
  const message = err instanceof Error ? err.message : String(err);
1039
1040
  ctx.ui.notify(`goosedump error: ${message}`, 'error');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goosedump",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Coding agent context data browser plugin for pi",
5
5
  "keywords": [
6
6
  "goosedump",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@earendil-works/pi-tui": "^0.78.0",
32
- "@jarkkojs/goosedump": "^0.4.2",
32
+ "@jarkkojs/goosedump": "^0.5.0",
33
33
  "@sinclair/typebox": "^0.34.49"
34
34
  },
35
35
  "devDependencies": {