pi-goosedump 0.5.5 → 0.6.0
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 +29 -1
- package/index.ts +152 -39
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Once installed, pi-goosedump registers a tool and a slash command:
|
|
|
21
21
|
|
|
22
22
|
### Tool: `goosedump`
|
|
23
23
|
|
|
24
|
-
The agent can browse the current session by default or a named session with `contextId`:
|
|
24
|
+
The agent can browse the current Pi session by default or a named session with `contextId`:
|
|
25
25
|
|
|
26
26
|
| Action | Description |
|
|
27
27
|
| -------- | ---------------------------------------- |
|
|
@@ -42,6 +42,34 @@ goosedump({ action: "expand", ids: ["entry-a", "entry-b"] })
|
|
|
42
42
|
goosedump({ action: "view" })
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
#### Other providers
|
|
46
|
+
|
|
47
|
+
By default the tool browses Pi sessions. Set `provider` (one of `claude`,
|
|
48
|
+
`codex`, `crush`, `gemini`, `goose`, `opencode`, `pi`) with a `contextId` to
|
|
49
|
+
browse another tool's sessions; results render the same way as Pi sessions.
|
|
50
|
+
Non-pi providers have no current session, so they require an explicit
|
|
51
|
+
`contextId`.
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
goosedump({ action: "list", provider: "claude" })
|
|
55
|
+
goosedump({ action: "view", provider: "claude", contextId: "abc123" })
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### Raw native output
|
|
59
|
+
|
|
60
|
+
By default every action renders through the normal structured view. Set
|
|
61
|
+
`outputFormat` to emit the raw native session instead — one of `claude`,
|
|
62
|
+
`codex`, `crush`, `gemini`, `goose`, `opencode`, `pi`, or `json`. JSONL providers
|
|
63
|
+
(`claude`, `codex`, `pi`, `gemini`) emit the raw session file; SQLite providers
|
|
64
|
+
(`crush`, `goose`, `opencode`) emit a row-oriented JSON projection of their
|
|
65
|
+
tables; `json` emits goosedump's structured JSON. This is round-trippable and
|
|
66
|
+
applies to `view`, `expand`, `grep`, and `search`.
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
goosedump({ action: "view", outputFormat: "goose" })
|
|
70
|
+
goosedump({ action: "grep", pattern: "*rand*", outputFormat: "claude" })
|
|
71
|
+
```
|
|
72
|
+
|
|
45
73
|
### Command: `/goosedump`
|
|
46
74
|
|
|
47
75
|
Opens an interactive session browser.
|
package/index.ts
CHANGED
|
@@ -29,7 +29,7 @@ import { Type } from '@sinclair/typebox';
|
|
|
29
29
|
|
|
30
30
|
const require = createRequire(import.meta.url);
|
|
31
31
|
|
|
32
|
-
const GOOSEDUMP_VERSION = [0,
|
|
32
|
+
const GOOSEDUMP_VERSION = [0, 6, 2] as const;
|
|
33
33
|
|
|
34
34
|
interface GoosedumpListing {
|
|
35
35
|
id: string;
|
|
@@ -115,10 +115,6 @@ function resolveGoosedumpBinary(): string {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
function binaryPath(): string {
|
|
119
|
-
return resolveGoosedumpBinary();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
118
|
function goosedumpVersion(): string | null {
|
|
123
119
|
try {
|
|
124
120
|
const pkg = require('@jarkkojs/goosedump/package.json') as { version: string };
|
|
@@ -218,9 +214,12 @@ function hitMessage(h: HitJson): GoosedumpMessage {
|
|
|
218
214
|
return { id: h.entryId, role: h.role, content: h.text, score: h.score };
|
|
219
215
|
}
|
|
220
216
|
|
|
217
|
+
const ANSI_SGR = new RegExp(String.fromCharCode(27) + '\\[[0-9;]*m', 'g');
|
|
218
|
+
|
|
221
219
|
function compactLine(content: string): string {
|
|
222
|
-
const
|
|
223
|
-
|
|
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;
|
|
224
223
|
}
|
|
225
224
|
|
|
226
225
|
function formatMessagesCompact(messages: GoosedumpMessage[]): string {
|
|
@@ -235,24 +234,29 @@ function textResult(text: string): AgentToolResult<void> {
|
|
|
235
234
|
return { content: [{ type: 'text', text }], details: undefined };
|
|
236
235
|
}
|
|
237
236
|
|
|
238
|
-
function missingContextId(action: string): AgentToolResult<void> {
|
|
237
|
+
function missingContextId(action: string, provider = 'pi'): AgentToolResult<void> {
|
|
238
|
+
if (provider !== 'pi') {
|
|
239
|
+
return textResult(`contextId is required for ${action} when provider is "${provider}"`);
|
|
240
|
+
}
|
|
239
241
|
return textResult(`contextId is required for ${action} when no current session is available`);
|
|
240
242
|
}
|
|
241
243
|
|
|
242
244
|
function runGoosedump(args: string[]): string {
|
|
243
|
-
return execFileSync('node', [
|
|
245
|
+
return execFileSync('node', [resolveGoosedumpBinary(), ...args], {
|
|
244
246
|
encoding: 'utf-8',
|
|
245
247
|
});
|
|
246
248
|
}
|
|
247
249
|
|
|
248
|
-
function goosedumpList(): GoosedumpListing[] {
|
|
249
|
-
const
|
|
250
|
+
function goosedumpList(provider = 'pi'): GoosedumpListing[] {
|
|
251
|
+
const prefix = `${provider}:`;
|
|
252
|
+
const ids = runGoosedump(['list', `${prefix}*`])
|
|
250
253
|
.split('\n')
|
|
251
254
|
.map((line) => line.trim())
|
|
252
|
-
.filter((line) => line.startsWith(
|
|
253
|
-
.map((line) => line.slice(
|
|
255
|
+
.filter((line) => line.startsWith(prefix))
|
|
256
|
+
.map((line) => line.slice(prefix.length));
|
|
254
257
|
|
|
255
|
-
|
|
258
|
+
// Only pi sessions live in the local sessions tree we can map back to files.
|
|
259
|
+
const paths = provider === 'pi' ? indexPiSessionPaths() : new Map<string, string>();
|
|
256
260
|
return ids.map((id) => ({ id, path: paths.get(id) ?? null }));
|
|
257
261
|
}
|
|
258
262
|
|
|
@@ -290,12 +294,12 @@ function indexPiSessionPaths(): Map<string, string> {
|
|
|
290
294
|
}
|
|
291
295
|
|
|
292
296
|
function goosedumpSearch(
|
|
293
|
-
|
|
297
|
+
target: string,
|
|
294
298
|
query: string,
|
|
295
299
|
options: { scope?: string; page?: number } = {},
|
|
296
300
|
): GoosedumpSearchResult {
|
|
297
301
|
const scope = options.scope ?? 'lineage';
|
|
298
|
-
const args = ['search',
|
|
302
|
+
const args = ['search', target, query];
|
|
299
303
|
if (options.page) args.push('--page', String(options.page));
|
|
300
304
|
args.push('--scope', scope);
|
|
301
305
|
|
|
@@ -305,23 +309,23 @@ function goosedumpSearch(
|
|
|
305
309
|
}
|
|
306
310
|
|
|
307
311
|
function goosedumpGrep(
|
|
308
|
-
|
|
312
|
+
target: string,
|
|
309
313
|
pattern: string,
|
|
310
314
|
options: { scope?: string } = {},
|
|
311
315
|
): GoosedumpMessage[] {
|
|
312
316
|
const scope = options.scope ?? 'lineage';
|
|
313
|
-
const json = JSON.parse(runGoosedump(['grep',
|
|
317
|
+
const json = JSON.parse(runGoosedump(['grep', target, pattern, '--scope', scope])) as {
|
|
314
318
|
hits?: HitJson[];
|
|
315
319
|
};
|
|
316
320
|
return (json.hits ?? []).map(hitMessage);
|
|
317
321
|
}
|
|
318
322
|
|
|
319
323
|
function goosedumpShow(
|
|
320
|
-
|
|
324
|
+
target: string,
|
|
321
325
|
options: { scope?: string; ids?: string[] } = {},
|
|
322
326
|
): GoosedumpMessage[] {
|
|
323
327
|
const scope = options.scope ?? 'lineage';
|
|
324
|
-
const args = ['show',
|
|
328
|
+
const args = ['show', target];
|
|
325
329
|
if (options.ids && options.ids.length > 0) args.push('--ids', options.ids.join(','));
|
|
326
330
|
args.push('--scope', scope);
|
|
327
331
|
|
|
@@ -329,6 +333,35 @@ function goosedumpShow(
|
|
|
329
333
|
return (json.messages ?? []).map(showMessage);
|
|
330
334
|
}
|
|
331
335
|
|
|
336
|
+
// goosedump 0.6.2+ accepts --output-format on show/grep/search to emit a context
|
|
337
|
+
// in a provider's native, round-trippable format. That output is raw native text
|
|
338
|
+
// (not the ShowJson/HitJson shapes), so this bypasses the JSON-parsing wrappers
|
|
339
|
+
// and returns it verbatim. Only used when the caller explicitly sets outputFormat.
|
|
340
|
+
function goosedumpConvert(
|
|
341
|
+
subcommand: 'show' | 'grep' | 'search',
|
|
342
|
+
target: string,
|
|
343
|
+
outputFormat: string,
|
|
344
|
+
options: { positionals?: string[]; scope?: string; ids?: string[]; page?: number } = {},
|
|
345
|
+
): string {
|
|
346
|
+
const args = [subcommand, target, ...(options.positionals ?? [])];
|
|
347
|
+
if (options.ids && options.ids.length > 0) args.push('--ids', options.ids.join(','));
|
|
348
|
+
if (options.page) args.push('--page', String(options.page));
|
|
349
|
+
args.push('--scope', options.scope ?? 'lineage', '--output-format', outputFormat);
|
|
350
|
+
return runGoosedump(args);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Resolve a <provider>:<contextId> target. contextId falls back to the current
|
|
354
|
+
// session only for pi; other providers have no current session, so they require
|
|
355
|
+
// an explicit contextId.
|
|
356
|
+
function resolveTarget(
|
|
357
|
+
ctx: ExtensionContext,
|
|
358
|
+
provider: string,
|
|
359
|
+
contextId: string | undefined,
|
|
360
|
+
): string | null {
|
|
361
|
+
const id = contextId ?? (provider === 'pi' ? currentSessionContextId(ctx) : null);
|
|
362
|
+
return id ? `${provider}:${id}` : null;
|
|
363
|
+
}
|
|
364
|
+
|
|
332
365
|
function capList(items: string[], limit: number): string {
|
|
333
366
|
const shown = items.slice(0, limit).join(', ');
|
|
334
367
|
return items.length > limit ? `${shown} (+${items.length - limit} more)` : shown;
|
|
@@ -659,7 +692,7 @@ async function runSessionBrowser(
|
|
|
659
692
|
const total = compactLines.length;
|
|
660
693
|
const window = compactLines.slice(compactScroll, compactScroll + COMPACT_VIEWPORT);
|
|
661
694
|
for (const row of window) {
|
|
662
|
-
lines.push(makeRow(text(
|
|
695
|
+
lines.push(makeRow(text(row), innerW, border));
|
|
663
696
|
}
|
|
664
697
|
for (let i = window.length; i < COMPACT_VIEWPORT; i++) {
|
|
665
698
|
lines.push(makeRow('', innerW, border));
|
|
@@ -696,7 +729,7 @@ async function runSessionBrowser(
|
|
|
696
729
|
const cursor = isSelected ? accent('▶') : ' ';
|
|
697
730
|
const idText = isSelected ? accent(listing.id) : text(listing.id);
|
|
698
731
|
const line = ` ${cursor} ${idText}`;
|
|
699
|
-
lines.push(makeRow(
|
|
732
|
+
lines.push(makeRow(line, innerW, border));
|
|
700
733
|
}
|
|
701
734
|
|
|
702
735
|
if (total > LIST_VIEWPORT) {
|
|
@@ -860,14 +893,16 @@ export function createGoosedumpIntegration(
|
|
|
860
893
|
name: 'goosedump',
|
|
861
894
|
label: 'goosedump',
|
|
862
895
|
description:
|
|
863
|
-
|
|
896
|
+
"Browse coding agent session history across providers. List sessions, or view/search/grep the current Pi session or a named session. Set provider to browse another tool's sessions (claude, codex, gemini, ...); set outputFormat for raw native output. Supports ranked search, glob filtering, compact overview, and result expansion. Default scope is active lineage.",
|
|
864
897
|
promptSnippet:
|
|
865
|
-
'goosedump({ action: "search", query, contextId?, scope?, page? }) - search session history; omit contextId for current session',
|
|
898
|
+
'goosedump({ action: "search", query, contextId?, provider?, scope?, page? }) - search session history; omit contextId for current Pi session',
|
|
866
899
|
promptGuidelines: [
|
|
867
900
|
'When researching past conversation history, use goosedump to find relevant context.',
|
|
868
901
|
'Start with compact ranked search (compact: true) for quick overview, then expand interesting entries.',
|
|
869
902
|
'Default scope is "lineage" (current branch); use scope: "all" to include all entries in the session.',
|
|
870
903
|
'Omit contextId to search, expand, or view the current Pi session; use list first for other sessions.',
|
|
904
|
+
'Set provider (e.g. "claude", "gemini") with a contextId to browse another tool\'s sessions; results render the same way as Pi sessions.',
|
|
905
|
+
'Set outputFormat (e.g. "claude", "json") only when you explicitly need the raw native session instead of the rendered view.',
|
|
871
906
|
],
|
|
872
907
|
parameters: Type.Object({
|
|
873
908
|
action: Type.Union(
|
|
@@ -886,7 +921,7 @@ export function createGoosedumpIntegration(
|
|
|
886
921
|
contextId: Type.Optional(
|
|
887
922
|
Type.String({
|
|
888
923
|
description:
|
|
889
|
-
'Context/session ID
|
|
924
|
+
'Context/session ID. Defaults to the current Pi session for search, grep, expand, view; required when provider is not pi.',
|
|
890
925
|
}),
|
|
891
926
|
),
|
|
892
927
|
query: Type.Optional(
|
|
@@ -919,6 +954,42 @@ export function createGoosedumpIntegration(
|
|
|
919
954
|
description: 'Page number for ranked results',
|
|
920
955
|
}),
|
|
921
956
|
),
|
|
957
|
+
provider: Type.Optional(
|
|
958
|
+
Type.Union(
|
|
959
|
+
[
|
|
960
|
+
Type.Literal('pi'),
|
|
961
|
+
Type.Literal('claude'),
|
|
962
|
+
Type.Literal('codex'),
|
|
963
|
+
Type.Literal('crush'),
|
|
964
|
+
Type.Literal('gemini'),
|
|
965
|
+
Type.Literal('goose'),
|
|
966
|
+
Type.Literal('opencode'),
|
|
967
|
+
],
|
|
968
|
+
{
|
|
969
|
+
default: 'pi',
|
|
970
|
+
description:
|
|
971
|
+
'Source provider whose sessions to browse (list, view, search, grep, expand). Defaults to pi. Non-pi providers have no current session, so they require an explicit contextId. Results render through the normal structured view regardless of provider.',
|
|
972
|
+
},
|
|
973
|
+
),
|
|
974
|
+
),
|
|
975
|
+
outputFormat: Type.Optional(
|
|
976
|
+
Type.Union(
|
|
977
|
+
[
|
|
978
|
+
Type.Literal('json'),
|
|
979
|
+
Type.Literal('claude'),
|
|
980
|
+
Type.Literal('codex'),
|
|
981
|
+
Type.Literal('crush'),
|
|
982
|
+
Type.Literal('gemini'),
|
|
983
|
+
Type.Literal('goose'),
|
|
984
|
+
Type.Literal('opencode'),
|
|
985
|
+
Type.Literal('pi'),
|
|
986
|
+
],
|
|
987
|
+
{
|
|
988
|
+
description:
|
|
989
|
+
"Explicitly emit raw native output instead of the normal rendered view (view, expand, grep, search). JSONL providers (claude, codex, pi, gemini) emit raw session JSONL; SQLite providers (crush, goose, opencode) emit a JSON table projection; json emits goosedump's structured JSON. Omit to render normally.",
|
|
990
|
+
},
|
|
991
|
+
),
|
|
992
|
+
),
|
|
922
993
|
}),
|
|
923
994
|
async execute(_id, params, _signal, _onUpdate, ctx): Promise<AgentToolResult<void>> {
|
|
924
995
|
if (!goosedumpAvailable) {
|
|
@@ -930,7 +1001,7 @@ export function createGoosedumpIntegration(
|
|
|
930
1001
|
try {
|
|
931
1002
|
switch (params.action) {
|
|
932
1003
|
case 'list': {
|
|
933
|
-
const listings = goosedumpList();
|
|
1004
|
+
const listings = goosedumpList(params.provider ?? 'pi');
|
|
934
1005
|
if (listings.length === 0) {
|
|
935
1006
|
return textResult('No sessions found.');
|
|
936
1007
|
}
|
|
@@ -944,12 +1015,23 @@ export function createGoosedumpIntegration(
|
|
|
944
1015
|
}
|
|
945
1016
|
|
|
946
1017
|
case 'search': {
|
|
947
|
-
const
|
|
948
|
-
|
|
1018
|
+
const provider = params.provider ?? 'pi';
|
|
1019
|
+
const target = resolveTarget(ctx, provider, params.contextId);
|
|
1020
|
+
if (!target) return missingContextId('search', provider);
|
|
949
1021
|
|
|
950
1022
|
if (!params.query) return textResult('query is required for search');
|
|
951
1023
|
|
|
952
|
-
|
|
1024
|
+
if (params.outputFormat) {
|
|
1025
|
+
return textResult(
|
|
1026
|
+
goosedumpConvert('search', target, params.outputFormat, {
|
|
1027
|
+
positionals: [params.query],
|
|
1028
|
+
scope: params.scope,
|
|
1029
|
+
page: params.page ?? 1,
|
|
1030
|
+
}),
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const result = goosedumpSearch(target, params.query, {
|
|
953
1035
|
scope: params.scope ?? 'lineage',
|
|
954
1036
|
page: params.page ?? 1,
|
|
955
1037
|
});
|
|
@@ -968,12 +1050,22 @@ export function createGoosedumpIntegration(
|
|
|
968
1050
|
}
|
|
969
1051
|
|
|
970
1052
|
case 'grep': {
|
|
971
|
-
const
|
|
972
|
-
|
|
1053
|
+
const provider = params.provider ?? 'pi';
|
|
1054
|
+
const target = resolveTarget(ctx, provider, params.contextId);
|
|
1055
|
+
if (!target) return missingContextId('grep', provider);
|
|
973
1056
|
|
|
974
1057
|
if (!params.pattern) return textResult('pattern is required for grep');
|
|
975
1058
|
|
|
976
|
-
|
|
1059
|
+
if (params.outputFormat) {
|
|
1060
|
+
return textResult(
|
|
1061
|
+
goosedumpConvert('grep', target, params.outputFormat, {
|
|
1062
|
+
positionals: [params.pattern],
|
|
1063
|
+
scope: params.scope,
|
|
1064
|
+
}),
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
const messages = goosedumpGrep(target, params.pattern, {
|
|
977
1069
|
scope: params.scope ?? 'lineage',
|
|
978
1070
|
});
|
|
979
1071
|
|
|
@@ -990,14 +1082,24 @@ export function createGoosedumpIntegration(
|
|
|
990
1082
|
}
|
|
991
1083
|
|
|
992
1084
|
case 'expand': {
|
|
993
|
-
const
|
|
994
|
-
|
|
1085
|
+
const provider = params.provider ?? 'pi';
|
|
1086
|
+
const target = resolveTarget(ctx, provider, params.contextId);
|
|
1087
|
+
if (!target) return missingContextId('expand', provider);
|
|
995
1088
|
|
|
996
1089
|
if (!params.ids || params.ids.length === 0) {
|
|
997
1090
|
return textResult('ids is required for expand (array of entry IDs)');
|
|
998
1091
|
}
|
|
999
1092
|
|
|
1000
|
-
|
|
1093
|
+
if (params.outputFormat) {
|
|
1094
|
+
return textResult(
|
|
1095
|
+
goosedumpConvert('show', target, params.outputFormat, {
|
|
1096
|
+
ids: params.ids,
|
|
1097
|
+
scope: params.scope,
|
|
1098
|
+
}),
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
const messages = goosedumpShow(target, {
|
|
1001
1103
|
scope: params.scope ?? 'lineage',
|
|
1002
1104
|
ids: params.ids,
|
|
1003
1105
|
});
|
|
@@ -1010,13 +1112,24 @@ export function createGoosedumpIntegration(
|
|
|
1010
1112
|
}
|
|
1011
1113
|
|
|
1012
1114
|
case 'view': {
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1115
|
+
const provider = params.provider ?? 'pi';
|
|
1116
|
+
const target = resolveTarget(ctx, provider, params.contextId);
|
|
1117
|
+
if (!target) return missingContextId('view', provider);
|
|
1118
|
+
|
|
1119
|
+
if (params.outputFormat) {
|
|
1120
|
+
return textResult(
|
|
1121
|
+
goosedumpConvert('show', target, params.outputFormat, {
|
|
1122
|
+
scope: params.scope,
|
|
1123
|
+
}),
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1015
1126
|
|
|
1016
|
-
const messages = goosedumpShow(
|
|
1127
|
+
const messages = goosedumpShow(target, { scope: params.scope ?? 'lineage' });
|
|
1017
1128
|
|
|
1018
1129
|
if (messages.length === 0) {
|
|
1019
|
-
return textResult(
|
|
1130
|
+
return textResult(
|
|
1131
|
+
`Session "${target.slice(provider.length + 1)}" has no messages.`,
|
|
1132
|
+
);
|
|
1020
1133
|
}
|
|
1021
1134
|
|
|
1022
1135
|
return textResult(formatMessagesFull(messages));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goosedump",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
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.
|
|
32
|
+
"@jarkkojs/goosedump": "^0.6.2",
|
|
33
33
|
"@sinclair/typebox": "^0.34.49"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|