pi-goosedump 0.5.4 → 0.5.5
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 +2 -0
- package/index.ts +77 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ The agent can browse the current session by default or a named session with `con
|
|
|
27
27
|
| -------- | ---------------------------------------- |
|
|
28
28
|
| `list` | List all available sessions |
|
|
29
29
|
| `search` | Rank messages by query relevance |
|
|
30
|
+
| `grep` | Filter messages by glob pattern |
|
|
30
31
|
| `expand` | Show full content for specific entry IDs |
|
|
31
32
|
| `view` | View the full session transcript |
|
|
32
33
|
|
|
@@ -36,6 +37,7 @@ Examples:
|
|
|
36
37
|
goosedump({ action: "list" })
|
|
37
38
|
goosedump({ action: "search", query: "bug fix" })
|
|
38
39
|
goosedump({ action: "search", contextId: "abc123", query: "bug fix" })
|
|
40
|
+
goosedump({ action: "grep", pattern: "*rand*" })
|
|
39
41
|
goosedump({ action: "expand", ids: ["entry-a", "entry-b"] })
|
|
40
42
|
goosedump({ action: "view" })
|
|
41
43
|
```
|
package/index.ts
CHANGED
|
@@ -18,7 +18,13 @@ import { createRequire } from 'node:module';
|
|
|
18
18
|
import { homedir } from 'node:os';
|
|
19
19
|
import { basename, join } from 'node:path';
|
|
20
20
|
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
Key,
|
|
23
|
+
matchesKey,
|
|
24
|
+
truncateToWidth,
|
|
25
|
+
visibleWidth,
|
|
26
|
+
wrapTextWithAnsi,
|
|
27
|
+
} from '@earendil-works/pi-tui';
|
|
22
28
|
import { Type } from '@sinclair/typebox';
|
|
23
29
|
|
|
24
30
|
const require = createRequire(import.meta.url);
|
|
@@ -212,12 +218,13 @@ function hitMessage(h: HitJson): GoosedumpMessage {
|
|
|
212
218
|
return { id: h.entryId, role: h.role, content: h.text, score: h.score };
|
|
213
219
|
}
|
|
214
220
|
|
|
221
|
+
function compactLine(content: string): string {
|
|
222
|
+
const collapsed = content.replace(/\s+/g, ' ').trim();
|
|
223
|
+
return truncateToWidth(collapsed, 80).replaceAll(String.fromCharCode(27) + '[0m', '');
|
|
224
|
+
}
|
|
225
|
+
|
|
215
226
|
function formatMessagesCompact(messages: GoosedumpMessage[]): string {
|
|
216
|
-
return messages
|
|
217
|
-
.map(
|
|
218
|
-
(m) => `[${m.id}] ${m.role}: ${m.content.slice(0, 80)}${m.content.length > 80 ? '...' : ''}`,
|
|
219
|
-
)
|
|
220
|
-
.join('\n');
|
|
227
|
+
return messages.map((m) => `[${m.id}] ${m.role}: ${compactLine(m.content)}`).join('\n');
|
|
221
228
|
}
|
|
222
229
|
|
|
223
230
|
function formatMessagesFull(messages: GoosedumpMessage[]): string {
|
|
@@ -297,6 +304,18 @@ function goosedumpSearch(
|
|
|
297
304
|
return { messages, page: json.page ?? options.page ?? 1, totalPages: json.totalPages ?? 1 };
|
|
298
305
|
}
|
|
299
306
|
|
|
307
|
+
function goosedumpGrep(
|
|
308
|
+
contextId: string,
|
|
309
|
+
pattern: string,
|
|
310
|
+
options: { scope?: string } = {},
|
|
311
|
+
): GoosedumpMessage[] {
|
|
312
|
+
const scope = options.scope ?? 'lineage';
|
|
313
|
+
const json = JSON.parse(runGoosedump(['grep', `pi:${contextId}`, pattern, '--scope', scope])) as {
|
|
314
|
+
hits?: HitJson[];
|
|
315
|
+
};
|
|
316
|
+
return (json.hits ?? []).map(hitMessage);
|
|
317
|
+
}
|
|
318
|
+
|
|
300
319
|
function goosedumpShow(
|
|
301
320
|
contextId: string,
|
|
302
321
|
options: { scope?: string; ids?: string[] } = {},
|
|
@@ -605,15 +624,20 @@ async function runSessionBrowser(
|
|
|
605
624
|
): Promise<string | null | undefined> {
|
|
606
625
|
let resumePath: string | null | undefined;
|
|
607
626
|
|
|
627
|
+
const OVERLAY_WIDTH = 72;
|
|
628
|
+
|
|
608
629
|
await ctx.ui.custom<void>(
|
|
609
630
|
(tui, theme, _kb, done) => {
|
|
610
631
|
let selectedIndex = 0;
|
|
632
|
+
let listScroll = 0;
|
|
611
633
|
let mode: 'list' | 'compact' = 'list';
|
|
612
634
|
let compactLines: string[] = [];
|
|
613
635
|
let compactScroll = 0;
|
|
614
636
|
let compactTitle = '';
|
|
615
637
|
|
|
616
638
|
const COMPACT_VIEWPORT = 20;
|
|
639
|
+
const LIST_VIEWPORT = 20;
|
|
640
|
+
const wrapWidth = OVERLAY_WIDTH - 4;
|
|
617
641
|
|
|
618
642
|
return {
|
|
619
643
|
render(width: number): string[] {
|
|
@@ -633,8 +657,6 @@ async function runSessionBrowser(
|
|
|
633
657
|
lines.push(`${borderFg('╭─')}${title}${topFill}${borderFg('─╮')}`);
|
|
634
658
|
|
|
635
659
|
const total = compactLines.length;
|
|
636
|
-
const maxScroll = Math.max(0, total - COMPACT_VIEWPORT);
|
|
637
|
-
if (compactScroll > maxScroll) compactScroll = maxScroll;
|
|
638
660
|
const window = compactLines.slice(compactScroll, compactScroll + COMPACT_VIEWPORT);
|
|
639
661
|
for (const row of window) {
|
|
640
662
|
lines.push(makeRow(text(truncateToWidth(row, innerW)), innerW, border));
|
|
@@ -665,18 +687,21 @@ async function runSessionBrowser(
|
|
|
665
687
|
lines.push(makeRow('', innerW, border));
|
|
666
688
|
|
|
667
689
|
// Session list
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
const
|
|
690
|
+
const total = listings.length;
|
|
691
|
+
const window = listings.slice(listScroll, listScroll + LIST_VIEWPORT);
|
|
692
|
+
for (let i = 0; i < window.length; i++) {
|
|
693
|
+
const absolute = listScroll + i;
|
|
694
|
+
const listing = window[i];
|
|
695
|
+
const isSelected = absolute === selectedIndex;
|
|
672
696
|
const cursor = isSelected ? accent('▶') : ' ';
|
|
673
697
|
const idText = isSelected ? accent(listing.id) : text(listing.id);
|
|
674
698
|
const line = ` ${cursor} ${idText}`;
|
|
675
699
|
lines.push(makeRow(truncateToWidth(line, innerW), innerW, border));
|
|
676
700
|
}
|
|
677
701
|
|
|
678
|
-
if (
|
|
679
|
-
|
|
702
|
+
if (total > LIST_VIEWPORT) {
|
|
703
|
+
const position = `${listScroll + 1}-${Math.min(listScroll + LIST_VIEWPORT, total)} of ${total}`;
|
|
704
|
+
lines.push(makeRow(dim(` (${position})`), innerW, border));
|
|
680
705
|
}
|
|
681
706
|
|
|
682
707
|
// Footer
|
|
@@ -697,6 +722,7 @@ async function runSessionBrowser(
|
|
|
697
722
|
|
|
698
723
|
handleInput(data: string): void {
|
|
699
724
|
if (mode === 'compact') {
|
|
725
|
+
const maxScroll = Math.max(0, compactLines.length - COMPACT_VIEWPORT);
|
|
700
726
|
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl('c'))) {
|
|
701
727
|
mode = 'list';
|
|
702
728
|
tui.requestRender();
|
|
@@ -708,7 +734,7 @@ async function runSessionBrowser(
|
|
|
708
734
|
return;
|
|
709
735
|
}
|
|
710
736
|
if (matchesKey(data, Key.down)) {
|
|
711
|
-
compactScroll
|
|
737
|
+
compactScroll = Math.min(maxScroll, compactScroll + 1);
|
|
712
738
|
tui.requestRender();
|
|
713
739
|
return;
|
|
714
740
|
}
|
|
@@ -718,7 +744,7 @@ async function runSessionBrowser(
|
|
|
718
744
|
return;
|
|
719
745
|
}
|
|
720
746
|
if (matchesKey(data, Key.pageDown)) {
|
|
721
|
-
compactScroll
|
|
747
|
+
compactScroll = Math.min(maxScroll, compactScroll + COMPACT_VIEWPORT);
|
|
722
748
|
tui.requestRender();
|
|
723
749
|
return;
|
|
724
750
|
}
|
|
@@ -732,12 +758,16 @@ async function runSessionBrowser(
|
|
|
732
758
|
|
|
733
759
|
if (matchesKey(data, Key.up)) {
|
|
734
760
|
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
761
|
+
if (selectedIndex < listScroll) listScroll = selectedIndex;
|
|
735
762
|
tui.requestRender();
|
|
736
763
|
return;
|
|
737
764
|
}
|
|
738
765
|
|
|
739
766
|
if (matchesKey(data, Key.down)) {
|
|
740
767
|
selectedIndex = Math.min(listings.length - 1, selectedIndex + 1);
|
|
768
|
+
if (selectedIndex >= listScroll + LIST_VIEWPORT) {
|
|
769
|
+
listScroll = selectedIndex - LIST_VIEWPORT + 1;
|
|
770
|
+
}
|
|
741
771
|
tui.requestRender();
|
|
742
772
|
return;
|
|
743
773
|
}
|
|
@@ -756,11 +786,11 @@ async function runSessionBrowser(
|
|
|
756
786
|
try {
|
|
757
787
|
const summary = goosedumpCompact(listing.id, { scope: 'lineage' }).trim();
|
|
758
788
|
compactLines = summary
|
|
759
|
-
? summary
|
|
789
|
+
? wrapTextWithAnsi(summary, wrapWidth)
|
|
760
790
|
: ['No compact summary available for this session.'];
|
|
761
791
|
} catch (err) {
|
|
762
792
|
const msg = err instanceof Error ? err.message : String(err);
|
|
763
|
-
compactLines =
|
|
793
|
+
compactLines = wrapTextWithAnsi(`Failed to compact session: ${msg}`, wrapWidth);
|
|
764
794
|
}
|
|
765
795
|
mode = 'compact';
|
|
766
796
|
tui.requestRender();
|
|
@@ -781,7 +811,7 @@ async function runSessionBrowser(
|
|
|
781
811
|
overlay: true,
|
|
782
812
|
overlayOptions: {
|
|
783
813
|
anchor: 'center',
|
|
784
|
-
width:
|
|
814
|
+
width: OVERLAY_WIDTH,
|
|
785
815
|
margin: 2,
|
|
786
816
|
},
|
|
787
817
|
},
|
|
@@ -830,7 +860,7 @@ export function createGoosedumpIntegration(
|
|
|
830
860
|
name: 'goosedump',
|
|
831
861
|
label: 'goosedump',
|
|
832
862
|
description:
|
|
833
|
-
'Browse coding agent session history. List all sessions, or view/search the current or a named session. Supports ranked search, compact overview, and result expansion. Default scope is active lineage.',
|
|
863
|
+
'Browse coding agent session history. List all sessions, or view/search/grep the current or a named session. Supports ranked search, glob filtering, compact overview, and result expansion. Default scope is active lineage.',
|
|
834
864
|
promptSnippet:
|
|
835
865
|
'goosedump({ action: "search", query, contextId?, scope?, page? }) - search session history; omit contextId for current session',
|
|
836
866
|
promptGuidelines: [
|
|
@@ -844,12 +874,13 @@ export function createGoosedumpIntegration(
|
|
|
844
874
|
[
|
|
845
875
|
Type.Literal('list'),
|
|
846
876
|
Type.Literal('search'),
|
|
877
|
+
Type.Literal('grep'),
|
|
847
878
|
Type.Literal('expand'),
|
|
848
879
|
Type.Literal('view'),
|
|
849
880
|
],
|
|
850
881
|
{
|
|
851
882
|
description:
|
|
852
|
-
'Operation: list sessions, search within a session, expand entries, or view full session',
|
|
883
|
+
'Operation: list sessions, search within a session, grep by glob pattern, expand entries, or view full session',
|
|
853
884
|
},
|
|
854
885
|
),
|
|
855
886
|
contextId: Type.Optional(
|
|
@@ -861,6 +892,9 @@ export function createGoosedumpIntegration(
|
|
|
861
892
|
query: Type.Optional(
|
|
862
893
|
Type.String({ description: 'Search query to rank messages by relevance' }),
|
|
863
894
|
),
|
|
895
|
+
pattern: Type.Optional(
|
|
896
|
+
Type.String({ description: 'Glob pattern to filter messages (e.g. *rand*)' }),
|
|
897
|
+
),
|
|
864
898
|
scope: Type.Optional(
|
|
865
899
|
Type.Union([Type.Literal('lineage'), Type.Literal('all')], {
|
|
866
900
|
default: 'lineage',
|
|
@@ -933,6 +967,28 @@ export function createGoosedumpIntegration(
|
|
|
933
967
|
return textResult(header + text);
|
|
934
968
|
}
|
|
935
969
|
|
|
970
|
+
case 'grep': {
|
|
971
|
+
const contextId = params.contextId ?? currentSessionContextId(ctx);
|
|
972
|
+
if (!contextId) return missingContextId('grep');
|
|
973
|
+
|
|
974
|
+
if (!params.pattern) return textResult('pattern is required for grep');
|
|
975
|
+
|
|
976
|
+
const messages = goosedumpGrep(contextId, params.pattern, {
|
|
977
|
+
scope: params.scope ?? 'lineage',
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
if (messages.length === 0) {
|
|
981
|
+
return textResult(`No matches for "${params.pattern}"`);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
const useCompact = params.compact ?? true;
|
|
985
|
+
const text = useCompact
|
|
986
|
+
? formatMessagesCompact(messages)
|
|
987
|
+
: formatMessagesFull(messages);
|
|
988
|
+
|
|
989
|
+
return textResult(text);
|
|
990
|
+
}
|
|
991
|
+
|
|
936
992
|
case 'expand': {
|
|
937
993
|
const contextId = params.contextId ?? currentSessionContextId(ctx);
|
|
938
994
|
if (!contextId) return missingContextId('expand');
|