pi-goosedump 0.5.4 → 0.5.6
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 +83 -28
- 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);
|
|
@@ -109,10 +115,6 @@ function resolveGoosedumpBinary(): string {
|
|
|
109
115
|
}
|
|
110
116
|
}
|
|
111
117
|
|
|
112
|
-
function binaryPath(): string {
|
|
113
|
-
return resolveGoosedumpBinary();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
118
|
function goosedumpVersion(): string | null {
|
|
117
119
|
try {
|
|
118
120
|
const pkg = require('@jarkkojs/goosedump/package.json') as { version: string };
|
|
@@ -212,12 +214,16 @@ function hitMessage(h: HitJson): GoosedumpMessage {
|
|
|
212
214
|
return { id: h.entryId, role: h.role, content: h.text, score: h.score };
|
|
213
215
|
}
|
|
214
216
|
|
|
217
|
+
const ANSI_SGR = new RegExp(String.fromCharCode(27) + '\\[[0-9;]*m', 'g');
|
|
218
|
+
|
|
219
|
+
function compactLine(content: string): string {
|
|
220
|
+
const plain = content.replace(ANSI_SGR, '');
|
|
221
|
+
const collapsed = plain.replace(/\s+/g, ' ').trim();
|
|
222
|
+
return collapsed.length > 80 ? `${collapsed.slice(0, 80)}...` : collapsed;
|
|
223
|
+
}
|
|
224
|
+
|
|
215
225
|
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');
|
|
226
|
+
return messages.map((m) => `[${m.id}] ${m.role}: ${compactLine(m.content)}`).join('\n');
|
|
221
227
|
}
|
|
222
228
|
|
|
223
229
|
function formatMessagesFull(messages: GoosedumpMessage[]): string {
|
|
@@ -233,7 +239,7 @@ function missingContextId(action: string): AgentToolResult<void> {
|
|
|
233
239
|
}
|
|
234
240
|
|
|
235
241
|
function runGoosedump(args: string[]): string {
|
|
236
|
-
return execFileSync('node', [
|
|
242
|
+
return execFileSync('node', [resolveGoosedumpBinary(), ...args], {
|
|
237
243
|
encoding: 'utf-8',
|
|
238
244
|
});
|
|
239
245
|
}
|
|
@@ -297,6 +303,18 @@ function goosedumpSearch(
|
|
|
297
303
|
return { messages, page: json.page ?? options.page ?? 1, totalPages: json.totalPages ?? 1 };
|
|
298
304
|
}
|
|
299
305
|
|
|
306
|
+
function goosedumpGrep(
|
|
307
|
+
contextId: string,
|
|
308
|
+
pattern: string,
|
|
309
|
+
options: { scope?: string } = {},
|
|
310
|
+
): GoosedumpMessage[] {
|
|
311
|
+
const scope = options.scope ?? 'lineage';
|
|
312
|
+
const json = JSON.parse(runGoosedump(['grep', `pi:${contextId}`, pattern, '--scope', scope])) as {
|
|
313
|
+
hits?: HitJson[];
|
|
314
|
+
};
|
|
315
|
+
return (json.hits ?? []).map(hitMessage);
|
|
316
|
+
}
|
|
317
|
+
|
|
300
318
|
function goosedumpShow(
|
|
301
319
|
contextId: string,
|
|
302
320
|
options: { scope?: string; ids?: string[] } = {},
|
|
@@ -605,15 +623,20 @@ async function runSessionBrowser(
|
|
|
605
623
|
): Promise<string | null | undefined> {
|
|
606
624
|
let resumePath: string | null | undefined;
|
|
607
625
|
|
|
626
|
+
const OVERLAY_WIDTH = 72;
|
|
627
|
+
|
|
608
628
|
await ctx.ui.custom<void>(
|
|
609
629
|
(tui, theme, _kb, done) => {
|
|
610
630
|
let selectedIndex = 0;
|
|
631
|
+
let listScroll = 0;
|
|
611
632
|
let mode: 'list' | 'compact' = 'list';
|
|
612
633
|
let compactLines: string[] = [];
|
|
613
634
|
let compactScroll = 0;
|
|
614
635
|
let compactTitle = '';
|
|
615
636
|
|
|
616
637
|
const COMPACT_VIEWPORT = 20;
|
|
638
|
+
const LIST_VIEWPORT = 20;
|
|
639
|
+
const wrapWidth = OVERLAY_WIDTH - 4;
|
|
617
640
|
|
|
618
641
|
return {
|
|
619
642
|
render(width: number): string[] {
|
|
@@ -633,11 +656,9 @@ async function runSessionBrowser(
|
|
|
633
656
|
lines.push(`${borderFg('╭─')}${title}${topFill}${borderFg('─╮')}`);
|
|
634
657
|
|
|
635
658
|
const total = compactLines.length;
|
|
636
|
-
const maxScroll = Math.max(0, total - COMPACT_VIEWPORT);
|
|
637
|
-
if (compactScroll > maxScroll) compactScroll = maxScroll;
|
|
638
659
|
const window = compactLines.slice(compactScroll, compactScroll + COMPACT_VIEWPORT);
|
|
639
660
|
for (const row of window) {
|
|
640
|
-
lines.push(makeRow(text(
|
|
661
|
+
lines.push(makeRow(text(row), innerW, border));
|
|
641
662
|
}
|
|
642
663
|
for (let i = window.length; i < COMPACT_VIEWPORT; i++) {
|
|
643
664
|
lines.push(makeRow('', innerW, border));
|
|
@@ -665,18 +686,21 @@ async function runSessionBrowser(
|
|
|
665
686
|
lines.push(makeRow('', innerW, border));
|
|
666
687
|
|
|
667
688
|
// Session list
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
const
|
|
689
|
+
const total = listings.length;
|
|
690
|
+
const window = listings.slice(listScroll, listScroll + LIST_VIEWPORT);
|
|
691
|
+
for (let i = 0; i < window.length; i++) {
|
|
692
|
+
const absolute = listScroll + i;
|
|
693
|
+
const listing = window[i];
|
|
694
|
+
const isSelected = absolute === selectedIndex;
|
|
672
695
|
const cursor = isSelected ? accent('▶') : ' ';
|
|
673
696
|
const idText = isSelected ? accent(listing.id) : text(listing.id);
|
|
674
697
|
const line = ` ${cursor} ${idText}`;
|
|
675
|
-
lines.push(makeRow(
|
|
698
|
+
lines.push(makeRow(line, innerW, border));
|
|
676
699
|
}
|
|
677
700
|
|
|
678
|
-
if (
|
|
679
|
-
|
|
701
|
+
if (total > LIST_VIEWPORT) {
|
|
702
|
+
const position = `${listScroll + 1}-${Math.min(listScroll + LIST_VIEWPORT, total)} of ${total}`;
|
|
703
|
+
lines.push(makeRow(dim(` (${position})`), innerW, border));
|
|
680
704
|
}
|
|
681
705
|
|
|
682
706
|
// Footer
|
|
@@ -697,6 +721,7 @@ async function runSessionBrowser(
|
|
|
697
721
|
|
|
698
722
|
handleInput(data: string): void {
|
|
699
723
|
if (mode === 'compact') {
|
|
724
|
+
const maxScroll = Math.max(0, compactLines.length - COMPACT_VIEWPORT);
|
|
700
725
|
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl('c'))) {
|
|
701
726
|
mode = 'list';
|
|
702
727
|
tui.requestRender();
|
|
@@ -708,7 +733,7 @@ async function runSessionBrowser(
|
|
|
708
733
|
return;
|
|
709
734
|
}
|
|
710
735
|
if (matchesKey(data, Key.down)) {
|
|
711
|
-
compactScroll
|
|
736
|
+
compactScroll = Math.min(maxScroll, compactScroll + 1);
|
|
712
737
|
tui.requestRender();
|
|
713
738
|
return;
|
|
714
739
|
}
|
|
@@ -718,7 +743,7 @@ async function runSessionBrowser(
|
|
|
718
743
|
return;
|
|
719
744
|
}
|
|
720
745
|
if (matchesKey(data, Key.pageDown)) {
|
|
721
|
-
compactScroll
|
|
746
|
+
compactScroll = Math.min(maxScroll, compactScroll + COMPACT_VIEWPORT);
|
|
722
747
|
tui.requestRender();
|
|
723
748
|
return;
|
|
724
749
|
}
|
|
@@ -732,12 +757,16 @@ async function runSessionBrowser(
|
|
|
732
757
|
|
|
733
758
|
if (matchesKey(data, Key.up)) {
|
|
734
759
|
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
760
|
+
if (selectedIndex < listScroll) listScroll = selectedIndex;
|
|
735
761
|
tui.requestRender();
|
|
736
762
|
return;
|
|
737
763
|
}
|
|
738
764
|
|
|
739
765
|
if (matchesKey(data, Key.down)) {
|
|
740
766
|
selectedIndex = Math.min(listings.length - 1, selectedIndex + 1);
|
|
767
|
+
if (selectedIndex >= listScroll + LIST_VIEWPORT) {
|
|
768
|
+
listScroll = selectedIndex - LIST_VIEWPORT + 1;
|
|
769
|
+
}
|
|
741
770
|
tui.requestRender();
|
|
742
771
|
return;
|
|
743
772
|
}
|
|
@@ -756,11 +785,11 @@ async function runSessionBrowser(
|
|
|
756
785
|
try {
|
|
757
786
|
const summary = goosedumpCompact(listing.id, { scope: 'lineage' }).trim();
|
|
758
787
|
compactLines = summary
|
|
759
|
-
? summary
|
|
788
|
+
? wrapTextWithAnsi(summary, wrapWidth)
|
|
760
789
|
: ['No compact summary available for this session.'];
|
|
761
790
|
} catch (err) {
|
|
762
791
|
const msg = err instanceof Error ? err.message : String(err);
|
|
763
|
-
compactLines =
|
|
792
|
+
compactLines = wrapTextWithAnsi(`Failed to compact session: ${msg}`, wrapWidth);
|
|
764
793
|
}
|
|
765
794
|
mode = 'compact';
|
|
766
795
|
tui.requestRender();
|
|
@@ -781,7 +810,7 @@ async function runSessionBrowser(
|
|
|
781
810
|
overlay: true,
|
|
782
811
|
overlayOptions: {
|
|
783
812
|
anchor: 'center',
|
|
784
|
-
width:
|
|
813
|
+
width: OVERLAY_WIDTH,
|
|
785
814
|
margin: 2,
|
|
786
815
|
},
|
|
787
816
|
},
|
|
@@ -830,7 +859,7 @@ export function createGoosedumpIntegration(
|
|
|
830
859
|
name: 'goosedump',
|
|
831
860
|
label: 'goosedump',
|
|
832
861
|
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.',
|
|
862
|
+
'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
863
|
promptSnippet:
|
|
835
864
|
'goosedump({ action: "search", query, contextId?, scope?, page? }) - search session history; omit contextId for current session',
|
|
836
865
|
promptGuidelines: [
|
|
@@ -844,12 +873,13 @@ export function createGoosedumpIntegration(
|
|
|
844
873
|
[
|
|
845
874
|
Type.Literal('list'),
|
|
846
875
|
Type.Literal('search'),
|
|
876
|
+
Type.Literal('grep'),
|
|
847
877
|
Type.Literal('expand'),
|
|
848
878
|
Type.Literal('view'),
|
|
849
879
|
],
|
|
850
880
|
{
|
|
851
881
|
description:
|
|
852
|
-
'Operation: list sessions, search within a session, expand entries, or view full session',
|
|
882
|
+
'Operation: list sessions, search within a session, grep by glob pattern, expand entries, or view full session',
|
|
853
883
|
},
|
|
854
884
|
),
|
|
855
885
|
contextId: Type.Optional(
|
|
@@ -861,6 +891,9 @@ export function createGoosedumpIntegration(
|
|
|
861
891
|
query: Type.Optional(
|
|
862
892
|
Type.String({ description: 'Search query to rank messages by relevance' }),
|
|
863
893
|
),
|
|
894
|
+
pattern: Type.Optional(
|
|
895
|
+
Type.String({ description: 'Glob pattern to filter messages (e.g. *rand*)' }),
|
|
896
|
+
),
|
|
864
897
|
scope: Type.Optional(
|
|
865
898
|
Type.Union([Type.Literal('lineage'), Type.Literal('all')], {
|
|
866
899
|
default: 'lineage',
|
|
@@ -933,6 +966,28 @@ export function createGoosedumpIntegration(
|
|
|
933
966
|
return textResult(header + text);
|
|
934
967
|
}
|
|
935
968
|
|
|
969
|
+
case 'grep': {
|
|
970
|
+
const contextId = params.contextId ?? currentSessionContextId(ctx);
|
|
971
|
+
if (!contextId) return missingContextId('grep');
|
|
972
|
+
|
|
973
|
+
if (!params.pattern) return textResult('pattern is required for grep');
|
|
974
|
+
|
|
975
|
+
const messages = goosedumpGrep(contextId, params.pattern, {
|
|
976
|
+
scope: params.scope ?? 'lineage',
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
if (messages.length === 0) {
|
|
980
|
+
return textResult(`No matches for "${params.pattern}"`);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const useCompact = params.compact ?? true;
|
|
984
|
+
const text = useCompact
|
|
985
|
+
? formatMessagesCompact(messages)
|
|
986
|
+
: formatMessagesFull(messages);
|
|
987
|
+
|
|
988
|
+
return textResult(text);
|
|
989
|
+
}
|
|
990
|
+
|
|
936
991
|
case 'expand': {
|
|
937
992
|
const contextId = params.contextId ?? currentSessionContextId(ctx);
|
|
938
993
|
if (!contextId) return missingContextId('expand');
|