pi-goosedump 0.5.6 → 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.
Files changed (3) hide show
  1. package/README.md +29 -1
  2. package/index.ts +144 -30
  3. 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, 5, 1] as const;
32
+ const GOOSEDUMP_VERSION = [0, 6, 2] as const;
33
33
 
34
34
  interface GoosedumpListing {
35
35
  id: string;
@@ -234,7 +234,10 @@ function textResult(text: string): AgentToolResult<void> {
234
234
  return { content: [{ type: 'text', text }], details: undefined };
235
235
  }
236
236
 
237
- 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
+ }
238
241
  return textResult(`contextId is required for ${action} when no current session is available`);
239
242
  }
240
243
 
@@ -244,14 +247,16 @@ function runGoosedump(args: string[]): string {
244
247
  });
245
248
  }
246
249
 
247
- function goosedumpList(): GoosedumpListing[] {
248
- const ids = runGoosedump(['list', 'pi:*'])
250
+ function goosedumpList(provider = 'pi'): GoosedumpListing[] {
251
+ const prefix = `${provider}:`;
252
+ const ids = runGoosedump(['list', `${prefix}*`])
249
253
  .split('\n')
250
254
  .map((line) => line.trim())
251
- .filter((line) => line.startsWith('pi:'))
252
- .map((line) => line.slice('pi:'.length));
255
+ .filter((line) => line.startsWith(prefix))
256
+ .map((line) => line.slice(prefix.length));
253
257
 
254
- const paths = indexPiSessionPaths();
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>();
255
260
  return ids.map((id) => ({ id, path: paths.get(id) ?? null }));
256
261
  }
257
262
 
@@ -289,12 +294,12 @@ function indexPiSessionPaths(): Map<string, string> {
289
294
  }
290
295
 
291
296
  function goosedumpSearch(
292
- contextId: string,
297
+ target: string,
293
298
  query: string,
294
299
  options: { scope?: string; page?: number } = {},
295
300
  ): GoosedumpSearchResult {
296
301
  const scope = options.scope ?? 'lineage';
297
- const args = ['search', `pi:${contextId}`, query];
302
+ const args = ['search', target, query];
298
303
  if (options.page) args.push('--page', String(options.page));
299
304
  args.push('--scope', scope);
300
305
 
@@ -304,23 +309,23 @@ function goosedumpSearch(
304
309
  }
305
310
 
306
311
  function goosedumpGrep(
307
- contextId: string,
312
+ target: string,
308
313
  pattern: string,
309
314
  options: { scope?: string } = {},
310
315
  ): GoosedumpMessage[] {
311
316
  const scope = options.scope ?? 'lineage';
312
- const json = JSON.parse(runGoosedump(['grep', `pi:${contextId}`, pattern, '--scope', scope])) as {
317
+ const json = JSON.parse(runGoosedump(['grep', target, pattern, '--scope', scope])) as {
313
318
  hits?: HitJson[];
314
319
  };
315
320
  return (json.hits ?? []).map(hitMessage);
316
321
  }
317
322
 
318
323
  function goosedumpShow(
319
- contextId: string,
324
+ target: string,
320
325
  options: { scope?: string; ids?: string[] } = {},
321
326
  ): GoosedumpMessage[] {
322
327
  const scope = options.scope ?? 'lineage';
323
- const args = ['show', `pi:${contextId}`];
328
+ const args = ['show', target];
324
329
  if (options.ids && options.ids.length > 0) args.push('--ids', options.ids.join(','));
325
330
  args.push('--scope', scope);
326
331
 
@@ -328,6 +333,35 @@ function goosedumpShow(
328
333
  return (json.messages ?? []).map(showMessage);
329
334
  }
330
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
+
331
365
  function capList(items: string[], limit: number): string {
332
366
  const shown = items.slice(0, limit).join(', ');
333
367
  return items.length > limit ? `${shown} (+${items.length - limit} more)` : shown;
@@ -859,14 +893,16 @@ export function createGoosedumpIntegration(
859
893
  name: 'goosedump',
860
894
  label: 'goosedump',
861
895
  description:
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.',
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.",
863
897
  promptSnippet:
864
- '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',
865
899
  promptGuidelines: [
866
900
  'When researching past conversation history, use goosedump to find relevant context.',
867
901
  'Start with compact ranked search (compact: true) for quick overview, then expand interesting entries.',
868
902
  'Default scope is "lineage" (current branch); use scope: "all" to include all entries in the session.',
869
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.',
870
906
  ],
871
907
  parameters: Type.Object({
872
908
  action: Type.Union(
@@ -885,7 +921,7 @@ export function createGoosedumpIntegration(
885
921
  contextId: Type.Optional(
886
922
  Type.String({
887
923
  description:
888
- 'Context/session ID (defaults to current session for search, expand, view)',
924
+ 'Context/session ID. Defaults to the current Pi session for search, grep, expand, view; required when provider is not pi.',
889
925
  }),
890
926
  ),
891
927
  query: Type.Optional(
@@ -918,6 +954,42 @@ export function createGoosedumpIntegration(
918
954
  description: 'Page number for ranked results',
919
955
  }),
920
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
+ ),
921
993
  }),
922
994
  async execute(_id, params, _signal, _onUpdate, ctx): Promise<AgentToolResult<void>> {
923
995
  if (!goosedumpAvailable) {
@@ -929,7 +1001,7 @@ export function createGoosedumpIntegration(
929
1001
  try {
930
1002
  switch (params.action) {
931
1003
  case 'list': {
932
- const listings = goosedumpList();
1004
+ const listings = goosedumpList(params.provider ?? 'pi');
933
1005
  if (listings.length === 0) {
934
1006
  return textResult('No sessions found.');
935
1007
  }
@@ -943,12 +1015,23 @@ export function createGoosedumpIntegration(
943
1015
  }
944
1016
 
945
1017
  case 'search': {
946
- const contextId = params.contextId ?? currentSessionContextId(ctx);
947
- if (!contextId) return missingContextId('search');
1018
+ const provider = params.provider ?? 'pi';
1019
+ const target = resolveTarget(ctx, provider, params.contextId);
1020
+ if (!target) return missingContextId('search', provider);
948
1021
 
949
1022
  if (!params.query) return textResult('query is required for search');
950
1023
 
951
- const result = goosedumpSearch(contextId, params.query, {
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, {
952
1035
  scope: params.scope ?? 'lineage',
953
1036
  page: params.page ?? 1,
954
1037
  });
@@ -967,12 +1050,22 @@ export function createGoosedumpIntegration(
967
1050
  }
968
1051
 
969
1052
  case 'grep': {
970
- const contextId = params.contextId ?? currentSessionContextId(ctx);
971
- if (!contextId) return missingContextId('grep');
1053
+ const provider = params.provider ?? 'pi';
1054
+ const target = resolveTarget(ctx, provider, params.contextId);
1055
+ if (!target) return missingContextId('grep', provider);
972
1056
 
973
1057
  if (!params.pattern) return textResult('pattern is required for grep');
974
1058
 
975
- const messages = goosedumpGrep(contextId, params.pattern, {
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, {
976
1069
  scope: params.scope ?? 'lineage',
977
1070
  });
978
1071
 
@@ -989,14 +1082,24 @@ export function createGoosedumpIntegration(
989
1082
  }
990
1083
 
991
1084
  case 'expand': {
992
- const contextId = params.contextId ?? currentSessionContextId(ctx);
993
- if (!contextId) return missingContextId('expand');
1085
+ const provider = params.provider ?? 'pi';
1086
+ const target = resolveTarget(ctx, provider, params.contextId);
1087
+ if (!target) return missingContextId('expand', provider);
994
1088
 
995
1089
  if (!params.ids || params.ids.length === 0) {
996
1090
  return textResult('ids is required for expand (array of entry IDs)');
997
1091
  }
998
1092
 
999
- const messages = goosedumpShow(contextId, {
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, {
1000
1103
  scope: params.scope ?? 'lineage',
1001
1104
  ids: params.ids,
1002
1105
  });
@@ -1009,13 +1112,24 @@ export function createGoosedumpIntegration(
1009
1112
  }
1010
1113
 
1011
1114
  case 'view': {
1012
- const contextId = params.contextId ?? currentSessionContextId(ctx);
1013
- if (!contextId) return missingContextId('view');
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
+ }
1014
1126
 
1015
- const messages = goosedumpShow(contextId, { scope: params.scope ?? 'lineage' });
1127
+ const messages = goosedumpShow(target, { scope: params.scope ?? 'lineage' });
1016
1128
 
1017
1129
  if (messages.length === 0) {
1018
- return textResult(`Session "${contextId}" has no messages.`);
1130
+ return textResult(
1131
+ `Session "${target.slice(provider.length + 1)}" has no messages.`,
1132
+ );
1019
1133
  }
1020
1134
 
1021
1135
  return textResult(formatMessagesFull(messages));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goosedump",
3
- "version": "0.5.6",
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.5.1",
32
+ "@jarkkojs/goosedump": "^0.6.2",
33
33
  "@sinclair/typebox": "^0.34.49"
34
34
  },
35
35
  "devDependencies": {