SLOPSHOPPER

Gigabuddy

Real-time awareness, coordination, and collaboration for Claude Code agents via Gigabuddy places. Agents see peers join and leave, what files they're focused…

newtoasttimer
v0.1.26no licenseupdated 2026-10-09gigabuddy/agent-plugins
A shopper browsing a rack in a slop shop
README

Gigabuddy — Claude Code plugin

Real-time awareness, coordination, and collaboration for Claude Code agents via Gigabuddy places. Agents see peers join and leave, what files they're focused on, conflicts, and messages — injected straight into context.

Install

# In Claude Code:
/plugin marketplace add gigabuddy/agent-plugins
/plugin install gigabuddy

Restart Claude Code — plugin MCP servers only attach at session start.

The plugin is a thin shell: it launches the @gigabuddy/agent MCP server from npm, which connects your session to the Gigabuddy door. Sign in when prompted on first connect.

What's in the box

  • MCP server wiring — @gigabuddy/agent, the Gigabuddy agent client.
  • Hooks — awareness injected into your prompts, an activity nudge after edits, a clean disconnect on session end. A Gigabuddy connector added on claude.ai also shows up in Claude Code; its tools are refused in favour of the plugin's, which act as this session's own agent.
  • Statusline — your session identity and place at a glance.
  • Skills — /decide, /handover, /pickup, /idea, /issue. Skill guidance is served live by the Gigabuddy door, so it improves without plugin updates.

License / source

This repository contains the plugin shell only. The Gigabuddy platform is proprietary; see https://gigabuddy.com.

Source 1 files
mod/register.tsx 108 lines
1/**
2 * The Gigabuddy mod — Gigabuddy driving this Claude Code session from its live
3 * record (page:ihuoK970I4i8, decision:FWFiBJHe8lPw).
4 *
5 * The plugin's own MCP server (`agent`) already holds the live connection to
6 * Gigabuddy; this module reaches it with `$.mcp.call` and does one thing: a
7 * loop collects what the person sent from the session view
8 * (`session_commands_wait`, bounded, re-armed from a timer so no MCP call
9 * outlives its timeout and nothing polls a server). A message is submitted as
10 * the person's own words (`$.prompt.submit` with `asUser`, a turn of its own
11 * once the session is idle); a Stop ends the running turn.
12 *
13 * It does NOT decide tool prompts. A mod can only replace Claude Code's own
14 * permission dialog with a prompt of its own, which loses the dialog's choices
15 * (don't ask again, this session, the suggested rules, auto mode) — Daniel,
16 * 2026-10-09: not until a mod's prompt is at least as good. Answering the real
17 * dialog from Gigabuddy is the channel relay's job (libs/agent channel.ts),
18 * for a session launched with the channel: `claude
19 * --dangerously-load-development-channels plugin:gigabuddy@gigabuddy`. The
20 * server-side tools for a mod-driven approval (`permission_check` and
21 * friends) stay, unused, for when that prompt is built.
22 *
23 * A hook's budget counts its own time only, never a `$` call in flight, so the
24 * waits cost nothing. Nothing here runs under `-p` (no one to ask).
25 */
26import type { EngineInterface, Register } from 'claude-code';
27
28/** The server's key in this plugin's .mcp.json. */
29const SERVER_KEY = 'agent';
30/** One bounded wait on the server; the tools cap it there too. */
31const WAIT_MS = 25_000;
32/** Between two inbound waits — a breath, not a poll: the wait itself blocks until something arrives. */
33const REARM_MS = 50;
34
35/** The first text block of an MCP result, parsed as JSON; `{}` when it is not. */
36function parse(result: { content: Array<{ type: string; text?: string }> }): Record<string, unknown> {
37  const text = result.content.find((c) => c.type === 'text')?.text;
38  if (!text) return {};
39  try {
40    const parsed = JSON.parse(text) as unknown;
41    return parsed && typeof parsed === 'object' ? (parsed as Record<string, unknown>) : {};
42  } catch {
43    return {};
44  }
45}
46
47/** What the inbound loop needs of the module: the server's name, and the running turn for Stop. */
48type Link = { server: string | null; turnId: string | null };
49
50/** One wait on the server, then whatever came in, then the next wait. Re-armed from a timer: work that outlives a dispatch. */
51async function collect($: EngineInterface, link: Link): Promise<void> {
52  if (!link.server) return;
53  try {
54    const { commands } = parse(await $.mcp.call(link.server, 'session_commands_wait', { timeout_ms: WAIT_MS }));
55    for (const command of Array.isArray(commands) ? commands : [])
56      await handle($, link, command as Record<string, unknown>);
57  } catch (err) {
58    $.ui.log(`gigabuddy: session commands wait failed: ${String(err)}`, { to: 'debug' });
59  }
60  $.clock.after(REARM_MS, () => void collect($, link));
61}
62
63async function handle($: EngineInterface, link: Link, command: Record<string, unknown>): Promise<void> {
64  if (command['kind'] === 'message' && typeof command['text'] === 'string' && command['text']) {
65    // The person's own words, read bare; queued for a turn of its own once the session is idle.
66    await $.prompt.submit({ text: command['text'], asUser: true });
67    return;
68  }
69  if (command['kind'] === 'abort') {
70    if (!link.turnId) return;
71    try {
72      await $.turn.abort({ turnId: link.turnId });
73      $.ui.toast('Stopped from Gigabuddy');
74    } catch (err) {
75      $.ui.log(`gigabuddy: stop failed: ${String(err)}`, { to: 'debug' });
76    }
77  }
78}
79
80export const register: Register = (on) => {
81  /** The server's name as `$.mcp.call` takes it (null until connected), and the running turn. */
82  const link: Link = { server: null, turnId: null };
83
84  // ── Inbound: what the person sent from the session view ─────────────────────
85
86  on('session.start', async ($, e, next) => {
87    if (!e.isInteractive) return next(e);
88    const connected = await $.mcp.connect(SERVER_KEY);
89    if (connected.isConnected) {
90      link.server = connected.server;
91      $.clock.after(REARM_MS, () => void collect($, link));
92    } else {
93      $.ui.log(`gigabuddy: not driving this session from Gigabuddy (${connected.message})`, { to: 'debug' });
94    }
95    return next(e);
96  });
97
98  on('turn.start', ($, e, next) => {
99    link.turnId = e.turnId;
100    return next(e);
101  });
102
103  on('turn.complete', ($, e, next) => {
104    link.turnId = null;
105    return next(e);
106  });
107};
108