A Claude Code mod that opens a pane of every git-agent skill and command; pressing one stages /git-agent:<name> in the prompt for you to run, so a misclick…

A clickable list of git-agent's skills and commands, so you never have to remember thirteen /git-agent:<name> spellings.
/git-agent-launcher opens a pane listing every git-agent entry with its description. Pressing one stages /git-agent:<name> in the prompt box. It does not run it. You add arguments if you want, and Enter is what runs it. This matters because commit-agent and pr-agent push without asking, so a misclick has to stay harmless.
This is a mod: a Claude Code plugin whose hooks/hooks.json names a hooks module. It has no skills, agents, or settings hooks.
git-agent installed. Without it the pane shows the install command and nothing else./plugin marketplace add shawn-sandy/agentics
/plugin install git-agent-launcher@agentics-kit
/git-agent-launcher. The pane opens and takes the keyboard.1–9 for the first nine entries, or click any entry. Entries 10–13 are reached by click, or with Tab and Enter./git-agent:<name> , followed by whatever you had typed. The keyboard is back in the prompt box, so add arguments or press Enter.| Prompt before the press | Entry pressed | Prompt after |
|---|---|---|
| (empty) | commit-agent | /git-agent:commit-agent |
fix typo in readme | commit-bg | /git-agent:commit-bg fix typo in readme |
/git-agent:commit-agent fix typo | commit-bg | /git-agent:commit-bg fix typo |
A command staged by an earlier press is replaced, not stacked, and what followed it is kept. The pane stays open for another press. Escape, or the pane's close mark, closes it. Ctrl+X Tab returns the keyboard to an open pane.
| Surface | What you get |
|---|---|
| Terminal | The pane: docked beside the transcript in the fullscreen layout, inline above the prompt otherwise |
| Desktop Code tab | The pane |
VS Code chat panel, claude -p, cloud sessions | No pane; the command answers with the list as text, one /git-agent:<name> — <description> per line |
If a surface's prompt box does not accept the staged text, a toast names the command to type instead: Type /git-agent:<name> in the prompt.
Mods are unsandboxed and run with your permissions. This one reads the command list and the prompt box. It writes only the prompt box, its own pane, and a toast. claude plugin validate kit/plugins/git-agent-launcher should report exactly the following, and anything else is a defect:
./register.tsx hooks: session.start, session.attach, session.detach, command.run{command=git-agent-launcher}, ui.render{component=Pane, requestId=git-agent-launcher}
./register.tsx answers its own command: command.run{command=git-agent-launcher}
./register.tsx calls: $.command.list (via gitAgentEntries), $.command.register, $.prompt.fill (via stage), $.prompt.read (via stage), $.ui.open, $.ui.resolve, $.ui.toast (via stage)
$.fs, $.process, $.http, $.model, $.session, or $.env.session.attach and session.detach: these hooks only observe. They track which drawing surfaces are attached, so a claude -p run, or a session whose last client has left, gets the text list instead of a pane nobody can see.version: No version specified warning is expected. This repo keeps versions in marketplace.json only.git-agent-launcher/
├── .claude-plugin/
│ └── plugin.json # manifest, no version key
├── hooks/
│ ├── hooks.json # { "modules": ["./register.tsx"] }
│ ├── register.tsx # the hooks module
│ └── register.test.ts # claude plugin test, terminal and desktop surfaces
├── tsconfig.json # extends the API types the engine lays in .claude-plugin/types/ (gitignored)
├── CHANGELOG.md
└── README.md
| Component | What it does |
|---|---|
/git-agent-launcher | Registered on session.start; opens the pane (focus, closeOnEscape), or answers with the text list where no pane draws |
Pane render | Lists $.command.list() entries whose plugin is git-agent, in typeahead order: a plain Button per entry and its description, dimmed |
| Press | $.prompt.read() and then $.prompt.fill(..., mode: 'replace'); a toast when the fill is refused |
claude --plugin-dir ./kit/plugins/git-agent-launcher # load it for one session
claude plugin validate kit/plugins/git-agent-launcher # the trust report above
claude plugin test kit/plugins/git-agent-launcher # the regression tests
bash tests/plugins/test-mod-plugins.sh # both, for every mod; the merge gate runs thishooks/register.tsx 82 lines1import type { CommandInfo, EngineInterface, Register } from 'claude-code'
2
3const PANE = 'git-agent-launcher'
4const PREFIX = 'git-agent:'
5const INSTALL = 'git-agent is not installed. Run /plugin install git-agent@agentics-kit'
6
7// A `claude -p` run has no surface, yet `ui.open` still answers isPlaced there,
8// so the text listing keys on whether a drawing surface is present: the one the
9// session started on, or a remote client still attached.
10let startedOnSurface = false
11const attached = new Set<string>()
12
13/** git-agent's skills and commands, in the order the typeahead lists them. */
14async function gitAgentEntries($: EngineInterface): Promise<CommandInfo[]> {
15 return (await $.command.list()).filter(c => c.plugin === 'git-agent')
16}
17
18/** The text answer for surfaces that draw no pane: one `/name — description` per line. */
19function listing(entries: CommandInfo[]): string {
20 return entries.length === 0 ? INSTALL : entries.map(c => `/${c.name} — ${c.description}`).join('\n')
21}
22
23/**
24 * Stages `/<name> ` in the prompt, never runs it: commit-agent and pr-agent
25 * push, so a misclick must stay harmless and Enter is what runs it. A command
26 * a previous press staged is replaced rather than stacked; what follows it is
27 * kept as arguments.
28 */
29async function stage($: EngineInterface, name: string): Promise<void> {
30 const { text: draft } = await $.prompt.read()
31 const command = `/${name}`
32 const args = draft.replace(/^\/git-agent:\S*\s*/, '')
33 const { isFilled } = await $.prompt.fill({ text: `${command} ${args}`, mode: 'replace' })
34 if (!isFilled) $.ui.toast(`Type ${command} in the prompt`)
35}
36
37export const register: Register = on => {
38 on('session.start', async ($, e, next) => {
39 startedOnSurface ||= e.surface !== null
40 await $.command.register({ name: 'git-agent-launcher', description: 'Open a pane of git-agent skills and commands' })
41 return next(e)
42 })
43
44 on('session.attach', ($, e, next) => {
45 attached.add(e.clientId)
46 return next(e)
47 })
48
49 on('session.detach', ($, e, next) => {
50 attached.delete(e.clientId)
51 return next(e)
52 })
53
54 on('command.run', { command: 'git-agent-launcher' }, async $ => {
55 const opened = await $.ui.open({ id: PANE, title: 'git-agent', focus: true, closeOnEscape: true })
56 if (opened.isPlaced && (startedOnSurface || attached.size > 0)) return {}
57 return { text: listing(await gitAgentEntries($)) }
58 })
59
60 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
61 const { Box, Text, Button } = $.ui.resolve(e)
62 const entries = await gitAgentEntries($)
63 if (entries.length === 0) return <Text>{INSTALL}</Text>
64
65 return (
66 <Box flexDirection="column" width={e.props.bodyColumns}>
67 <Text dimColor>Press an entry to stage it in the prompt · Esc closes</Text>
68 {entries.map((c, i) => {
69 const label = c.name.slice(PREFIX.length)
70 const hotkey = i < 9 ? { hotkey: String(i + 1) } : {}
71 return (
72 <Box flexDirection="column">
73 <Button key={label} label={label} plain {...hotkey} onPress={() => stage($, c.name)} />
74 <Text dimColor>{c.description}</Text>
75 </Box>
76 )
77 })}
78 </Box>
79 )
80 })
81}
82