@g3nomaly/g3n-mods 0.1.7 → 0.1.8

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 (35) hide show
  1. package/.claude-plugin/marketplace.json +7 -1
  2. package/README.md +12 -5
  3. package/mods/agent-adventure/.claude-plugin/plugin.json +23 -0
  4. package/mods/agent-adventure/hooks/content.ts +263 -0
  5. package/mods/agent-adventure/hooks/engine.ts +1125 -0
  6. package/mods/agent-adventure/hooks/hooks.json +1 -0
  7. package/mods/agent-adventure/hooks/register.tsx +465 -0
  8. package/mods/agent-adventure/hooks/sprite.ts +424 -0
  9. package/mods/agent-adventure/types/index.d.ts +248 -0
  10. package/mods/agent-adventure/view/panes.json +8 -0
  11. package/mods/agent-adventure/view/view.css +230 -0
  12. package/mods/agent-adventure/view/view.ts +308 -0
  13. package/mods/agents-panel/.claude-plugin/plugin.json +9 -1
  14. package/mods/agents-panel/hooks/register.tsx +18 -4
  15. package/mods/agents-panel/view/panes.json +8 -0
  16. package/mods/context-bar/.claude-plugin/plugin.json +9 -1
  17. package/mods/context-bar/hooks/register.tsx +4 -3
  18. package/mods/context-bar/view/panes.json +8 -0
  19. package/mods/custom-crab-agents/hooks/register.tsx +15 -1
  20. package/mods/delete-guard/.claude-plugin/plugin.json +9 -1
  21. package/mods/delete-guard/hooks/register.tsx +15 -5
  22. package/mods/delete-guard/view/panes.json +8 -0
  23. package/mods/focus-mode/.claude-plugin/plugin.json +6 -0
  24. package/mods/focus-mode/hooks/register.tsx +7 -4
  25. package/mods/focus-mode/view/panes.json +8 -0
  26. package/mods/mini-game/.claude-plugin/plugin.json +9 -1
  27. package/mods/mini-game/hooks/register.tsx +21 -7
  28. package/mods/mini-game/view/panes.json +8 -0
  29. package/mods/tool-timeline/.claude-plugin/plugin.json +9 -1
  30. package/mods/tool-timeline/hooks/register.tsx +18 -4
  31. package/mods/tool-timeline/view/panes.json +8 -0
  32. package/mods/you-should-know/.claude-plugin/plugin.json +3 -3
  33. package/mods/you-should-know/hooks/register.tsx +173 -45
  34. package/mods/you-should-know/types/index.d.ts +13 -4
  35. package/package.json +2 -1
@@ -12,6 +12,20 @@ const PANEL = 'agents-panel'
12
12
  /** Key prefix of the agents panel's own elements, and the one this section carries so the panel shows it. */
13
13
  const PANEL_KEY = 'agents:'
14
14
  const ADDON_KEY = 'addon:'
15
+
16
+ /** Prop the mods viewer adds to every pane it draws; the Claude Code extension draws on the `vscode` surface too, without it. */
17
+ const VIEWER_PROP = 'g3nViewer'
18
+
19
+ /**
20
+ * Tells whether the mods viewer asked for this drawing, rather than the Claude Code extension on the same surface.
21
+ *
22
+ * @param props - The `Pane` props of the `ui.render` argument.
23
+ * @returns `true` when the viewer's prop is set.
24
+ */
25
+ function isViewer(props: object): boolean {
26
+ return (props as Readonly<Record<string, unknown>>)[VIEWER_PROP] === true
27
+ }
28
+
15
29
  const crabs = atom({ plugin: 'custom-crab-agents', key: 'crabs' } as const, [])
16
30
  const planned = atom({ plugin: 'custom-crab-agents', key: 'planned' } as const, [])
17
31
  const contextWindow = atom({ plugin: 'custom-crab-agents', key: 'window' } as const, 1_000_000)
@@ -592,7 +606,7 @@ export const register: Register = on => {
592
606
  const now = await $.clock.now()
593
607
 
594
608
  // The viewer draws the crabs with the mod's own view: hand it the cards as data, inside the key the panel looks for.
595
- if (e.surface === 'vscode') {
609
+ if (e.surface === 'vscode' && isViewer(e.props)) {
596
610
  const { Input } = $.ui.resolve(e)
597
611
  const data: CrabsViewData = {
598
612
  crabs: list.map(crab => {
@@ -5,5 +5,13 @@
5
5
  "author": {
6
6
  "name": "g3ntrovert"
7
7
  },
8
- "types": "./types/index.d.ts"
8
+ "types": "./types/index.d.ts",
9
+ "userConfig": {
10
+ "showInClaudeCode": {
11
+ "type": "boolean",
12
+ "title": "Show in Claude Code",
13
+ "default": false,
14
+ "description": "Open this mod's pane in the Claude Code chat when a session starts. The mods viewer shows it either way."
15
+ }
16
+ }
9
17
  }
@@ -424,6 +424,9 @@ const held: Held[] = []
424
424
  /** The card lists every file instead of the first `SHOWN_FILES`. */
425
425
  let isExpanded = false
426
426
 
427
+ /** The switch stays in the Claude Code chat; else only a waiting delete's card shows there. Set from the plugin options. */
428
+ let isShownInChat = false
429
+
427
430
  /**
428
431
  * Turns the guard on or off, for this session and every later one.
429
432
  *
@@ -436,12 +439,16 @@ async function setGuard($: EngineInterface, isGuarding: boolean): Promise<void>
436
439
  }
437
440
 
438
441
  /**
439
- * Shows the switch again once no delete waits: the pane's title goes back from the card's.
442
+ * Shows the switch again once no delete waits: the pane's title goes back from the card's, or the pane leaves the
443
+ * Claude Code chat when the switch is not shown there.
440
444
  *
441
445
  * @param $ - Engine handle the hook received.
442
446
  */
443
447
  async function showSwitch($: EngineInterface): Promise<void> {
444
- if (held.length === 0) await $.ui.open({ id: PANE, title: TITLE }).catch(() => undefined)
448
+ if (held.length === 0) {
449
+ if (isShownInChat) await $.ui.open({ id: PANE, title: TITLE }).catch(() => undefined)
450
+ else await $.ui.close({ id: PANE }).catch(() => undefined)
451
+ }
445
452
  $.ui.invalidate('ui.render')
446
453
  }
447
454
 
@@ -483,7 +490,8 @@ async function hold($: EngineInterface, card: Omit<Held, 'settle'>, signal: Abor
483
490
  const onAbort = (): void => drop('cancel')
484
491
  signal?.addEventListener('abort', onAbort, { once: true })
485
492
 
486
- // The switch's pane is usually open already: retitle and focus it, then redraw it as the card.
493
+ // A delete waits on an answer, so its card opens in the Claude Code chat whatever the option says: retitle and focus
494
+ // the switch's pane when it is open already, then redraw it as the card.
487
495
  if (held.length === 1) await $.ui.open({ id: PANE, title: HOLD_TITLE, focus: true })
488
496
  $.ui.invalidate('ui.render')
489
497
 
@@ -508,8 +516,10 @@ async function hold($: EngineInterface, card: Omit<Held, 'settle'>, signal: Abor
508
516
  * Registers this mod's `delete-guard` command, its session hook, its Bash tool hook, and its pane: the switch, or the card while a delete waits.
509
517
  *
510
518
  * @param on - Hook registration function the engine passes in.
519
+ * @param options - Plugin options; `showInClaudeCode` keeps the switch in the Claude Code chat.
511
520
  */
512
- export const register: Register = on => {
521
+ export const register: Register = (on, options) => {
522
+ isShownInChat = options.showInClaudeCode === true
513
523
  let trash: string | undefined
514
524
  let home = ''
515
525
 
@@ -524,7 +534,7 @@ export const register: Register = on => {
524
534
  description: 'Turn the permanent-delete guard on or off; it stays that way in later sessions',
525
535
  argumentHint: 'on | off',
526
536
  })
527
- void $.ui.open({ id: PANE, title: TITLE })
537
+ if (isShownInChat) void $.ui.open({ id: PANE, title: TITLE })
528
538
  return next(e)
529
539
  })
530
540
 
@@ -0,0 +1,8 @@
1
+ {
2
+ "panes": [
3
+ {
4
+ "id": "delete-guard",
5
+ "title": "Delete guard"
6
+ }
7
+ ]
8
+ }
@@ -12,6 +12,12 @@
12
12
  "title": "Minutes",
13
13
  "default": 25,
14
14
  "description": "Length of a focus block in minutes"
15
+ },
16
+ "showInClaudeCode": {
17
+ "type": "boolean",
18
+ "title": "Show in Claude Code",
19
+ "default": false,
20
+ "description": "Open this mod's pane in the Claude Code chat when a session starts. The mods viewer shows it either way."
15
21
  }
16
22
  }
17
23
  }
@@ -81,7 +81,7 @@ async function setLength($: EngineInterface, minutes: number): Promise<void> {
81
81
  }
82
82
 
83
83
  /**
84
- * Starts a block of `minutes`, replacing one already running, and shows the pane.
84
+ * Starts a block of `minutes`, replacing one already running.
85
85
  *
86
86
  * @param $ - Engine handle the hook received, see `EngineInterface`.
87
87
  * @param minutes - Length of the block in minutes.
@@ -89,7 +89,7 @@ async function setLength($: EngineInterface, minutes: number): Promise<void> {
89
89
  async function startFocus($: EngineInterface, minutes: number): Promise<void> {
90
90
  const startedAt = await $.clock.now()
91
91
  await update($, block, () => ({ startedAt, endsAt: startedAt + minutes * 60_000, minutes }))
92
- await $.ui.open({ id: PANE, title: TITLE })
92
+ $.ui.invalidate('ui.render')
93
93
  }
94
94
 
95
95
  /**
@@ -144,7 +144,8 @@ async function awayBlock($: EngineInterface): Promise<FocusBlock | null> {
144
144
  * from asking anything during a block: questions are refused, and so are permission prompts.
145
145
  *
146
146
  * @param on - Hook registration function the engine passes in.
147
- * @param options - Plugin options; `minutes` sets the default block length.
147
+ * @param options - Plugin options; `minutes` sets the default block length, `showInClaudeCode` opens the pane in the
148
+ * Claude Code chat at session start.
148
149
  */
149
150
  export const register: Register = (on, options) => {
150
151
  const configured = Number(options.minutes ?? DEFAULT_MINUTES)
@@ -158,7 +159,7 @@ export const register: Register = (on, options) => {
158
159
  description: 'Start a focus block in which Claude asks you nothing (the length you set, or the minutes given), or end the running one',
159
160
  argumentHint: '[minutes] | end',
160
161
  })
161
- void $.ui.open({ id: PANE, title: TITLE })
162
+ if (options.showInClaudeCode === true) void $.ui.open({ id: PANE, title: TITLE })
162
163
  $.clock.every(1000, () => void tick($))
163
164
  return next(e)
164
165
  })
@@ -177,6 +178,8 @@ export const register: Register = (on, options) => {
177
178
  // A length typed with the command becomes the one the pane starts with next time.
178
179
  if (arg !== '') await setLength($, minutes)
179
180
  await startFocus($, minutes)
181
+ // Typed in the chat, so the countdown shows there too.
182
+ await $.ui.open({ id: PANE, title: TITLE })
180
183
  return { text: `Focus block started: ${minutes} min.` }
181
184
  })
182
185
 
@@ -0,0 +1,8 @@
1
+ {
2
+ "panes": [
3
+ {
4
+ "id": "focus-mode",
5
+ "title": "Focus"
6
+ }
7
+ ]
8
+ }
@@ -5,5 +5,13 @@
5
5
  "author": {
6
6
  "name": "g3ntrovert"
7
7
  },
8
- "types": "./types/index.d.ts"
8
+ "types": "./types/index.d.ts",
9
+ "userConfig": {
10
+ "showInClaudeCode": {
11
+ "type": "boolean",
12
+ "title": "Show in Claude Code",
13
+ "default": false,
14
+ "description": "Open this mod's pane in the Claude Code chat when a session starts. The mods viewer shows it either way."
15
+ }
16
+ }
9
17
  }
@@ -27,7 +27,7 @@ const CELL_WIDTH = 2
27
27
  const NUMBER_COLORS = [undefined, '#6b8fd6', '#7cc47f', '#e06c6c', '#9b87f5', '#e0775a', '#5fb3c4', '#d0d0d0', '#8a8f98']
28
28
 
29
29
  /**
30
- * Shows the pane, titled after the game it holds.
30
+ * Opens the pane in the Claude Code chat, titled after the game it holds.
31
31
  *
32
32
  * @param $ - Engine handle the hook received, see `EngineInterface`.
33
33
  */
@@ -36,14 +36,25 @@ async function openPane($: EngineInterface): Promise<void> {
36
36
  }
37
37
 
38
38
  /**
39
- * Starts a new Minesweeper board and shows the pane.
39
+ * Titles the pane after the game it holds where the Claude Code chat shows it, and redraws it. A press never opens
40
+ * it there: a pane the mods viewer alone draws stays out of the chat.
41
+ *
42
+ * @param $ - Engine handle the hook received, see `EngineInterface`.
43
+ */
44
+ async function showGame($: EngineInterface): Promise<void> {
45
+ if ((await $.ui.panes()).some(pane => pane.id === PANE)) await openPane($)
46
+ else $.ui.invalidate('ui.render')
47
+ }
48
+
49
+ /**
50
+ * Starts a new Minesweeper board and shows it.
40
51
  *
41
52
  * @param $ - Engine handle the hook received, see `EngineInterface`.
42
53
  */
43
54
  async function startGame($: EngineInterface): Promise<void> {
44
55
  const now = await $.clock.now()
45
56
  await update($, game, () => newGame(now))
46
- await openPane($)
57
+ await showGame($)
47
58
  }
48
59
 
49
60
  /**
@@ -57,13 +68,13 @@ async function showView($: EngineInterface, next: View): Promise<void> {
57
68
  await update($, view, () => next)
58
69
  if (next === 'mole') {
59
70
  await update($, whack, current => current ?? newWhack())
60
- await openPane($)
71
+ await showGame($)
61
72
  return
62
73
  }
63
74
  await changeWhack($, quit)
64
75
  const current = await read($, game)
65
76
  if (current === null || current.status === 'won' || current.status === 'lost') await startGame($)
66
- else await openPane($)
77
+ else await showGame($)
67
78
  }
68
79
 
69
80
  /**
@@ -161,15 +172,16 @@ async function hint($: EngineInterface, turnId: string): Promise<void> {
161
172
  * Registers this mod's `mini-game` command, its session, turn and pane hooks.
162
173
  *
163
174
  * @param on - Hook registration function the engine passes in.
175
+ * @param options - Plugin options; `showInClaudeCode` opens the pane in the Claude Code chat at session start.
164
176
  */
165
- export const register: Register = on => {
177
+ export const register: Register = (on, options) => {
166
178
  on('session.start', async ($, e, next) => {
167
179
  await $.command.register({ name: 'mini-game', description: 'Play Minesweeper or whack-a-mole while Claude works' })
168
180
  const storedBest = await $.store.get(BEST_KEY)
169
181
  if (typeof storedBest === 'number') await update($, best, () => storedBest)
170
182
  const storedWhackBest = await $.store.get(WHACK_BEST_KEY)
171
183
  if (typeof storedWhackBest === 'number') await update($, whackBest, () => storedWhackBest)
172
- void openPane($)
184
+ if (options.showInClaudeCode === true) void openPane($)
173
185
  $.clock.every(1000, () => void tick($))
174
186
  return next(e)
175
187
  })
@@ -179,6 +191,8 @@ export const register: Register = on => {
179
191
  const next = arg === '' ? await read($, view) : VIEW_ARGS[arg]
180
192
  if (next === undefined) return { text: `Unknown game "${arg}". Use /mini-game mines or /mini-game mole.`, exitCode: 1 }
181
193
  await showView($, next)
194
+ // Typed in the chat, so the game shows there too.
195
+ await openPane($)
182
196
  return {
183
197
  text: next === 'mines'
184
198
  ? 'Minesweeper is open. Clear the board without hitting a mine.'
@@ -0,0 +1,8 @@
1
+ {
2
+ "panes": [
3
+ {
4
+ "id": "mini-game",
5
+ "title": "Mini game"
6
+ }
7
+ ]
8
+ }
@@ -5,5 +5,13 @@
5
5
  "author": {
6
6
  "name": "g3ntrovert"
7
7
  },
8
- "types": "./types/index.d.ts"
8
+ "types": "./types/index.d.ts",
9
+ "userConfig": {
10
+ "showInClaudeCode": {
11
+ "type": "boolean",
12
+ "title": "Show in Claude Code",
13
+ "default": false,
14
+ "description": "Open this mod's pane in the Claude Code chat when a session starts. The mods viewer shows it either way."
15
+ }
16
+ }
9
17
  }
@@ -12,6 +12,19 @@ const selected = atom({ plugin: 'tool-timeline', key: 'selected' } as const, '')
12
12
  /** Calls kept in state; older ones fall off. */
13
13
  const KEEP = 100
14
14
 
15
+ /** Prop the mods viewer adds to every pane it draws; the Claude Code extension draws on the `vscode` surface too, without it. */
16
+ const VIEWER_PROP = 'g3nViewer'
17
+
18
+ /**
19
+ * Tells whether the mods viewer asked for this drawing, rather than the Claude Code extension on the same surface.
20
+ *
21
+ * @param props - The `Pane` props of the `ui.render` argument.
22
+ * @returns `true` when the viewer's prop is set.
23
+ */
24
+ function isViewer(props: object): boolean {
25
+ return (props as Readonly<Record<string, unknown>>)[VIEWER_PROP] === true
26
+ }
27
+
15
28
  /** Approximate pixel width of one cell of the editor's code font. */
16
29
  const CELL_PX = 7
17
30
 
@@ -250,11 +263,12 @@ async function toggleSelected($: EngineInterface, id: string): Promise<void> {
250
263
  * @brief Subscribes the timeline's handlers to the engine's events, its command and its pane.
251
264
  *
252
265
  * @param on - Subscribes a handler to one engine event, optionally filtered.
266
+ * @param options - Plugin options; `showInClaudeCode` opens the pane in the Claude Code chat at session start.
253
267
  */
254
- export const register: Register = on => {
268
+ export const register: Register = (on, options) => {
255
269
  on('session.start', async ($, e, next) => {
256
- await $.command.register({ name: 'tool-timeline', description: 'Show or hide the tool call timeline' })
257
- void $.ui.open({ id: PANE, title: TITLE })
270
+ await $.command.register({ name: 'tool-timeline', description: 'Show or hide the tool call timeline in the Claude Code chat' })
271
+ if (options.showInClaudeCode === true) void $.ui.open({ id: PANE, title: TITLE })
258
272
  $.clock.every(1000, () => void tick($))
259
273
  return next(e)
260
274
  })
@@ -329,7 +343,7 @@ export const register: Register = on => {
329
343
  const axis = placeCalls(shown, now)
330
344
 
331
345
  // The viewer draws the timeline with the mod's own view: hand it the calls as data.
332
- if (e.surface === 'vscode') {
346
+ if (e.surface === 'vscode' && isViewer(e.props)) {
333
347
  const { Input } = $.ui.resolve(e)
334
348
  const spans = screenSpans(list, now)
335
349
  const data: TimelineViewData = {
@@ -0,0 +1,8 @@
1
+ {
2
+ "panes": [
3
+ {
4
+ "id": "tool-timeline",
5
+ "title": "Tool timeline"
6
+ }
7
+ ]
8
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "you-should-know",
3
3
  "version": "0.1.0",
4
- "description": "A card under long replies that flags their key point: a headline and one supporting line.",
4
+ "description": "A compact card under a reply: the questions it asks you word for word, and for long replies what changed and what it concluded, each line tagged.",
5
5
  "author": {
6
6
  "name": "g3ntrovert"
7
7
  },
@@ -10,8 +10,8 @@
10
10
  "minChars": {
11
11
  "type": "number",
12
12
  "title": "Minimum reply length",
13
- "default": 1500,
14
- "description": "Replies shorter than this many characters get no card"
13
+ "default": 800,
14
+ "description": "Replies shorter than this many characters get no changes, conclusions or facts; their questions still show"
15
15
  },
16
16
  "model": {
17
17
  "type": "string",