Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://json.schemastore.org/claude-code-marketplace.json",
"name": "allthings",
"description": "allthings evenings in San Francisco, for people who build software, in Claude Code.",
"owner": {
"name": "allthings",
"url": "https://allthings.dev"
},
"plugins": [
{
"name": "allthings",
"source": "./plugins/allthings",
"description": "The next allthings evening above your prompt, /at and /imin, tool rows in the slash grammar, the allthings themes and voice, and the allthings MCP server.",
"category": "community"
}
]
}
34 changes: 34 additions & 0 deletions .github/scripts/claude-code.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Installs Claude Code at the version CLAUDE_CODE_VERSION pins, puts it on the
# job's PATH, and lays the types Claude Code writes beside the allthings mod
# (plugins/allthings/.claude-plugin/types/, gitignored) so its tsconfig and
# the type-aware lint resolve `claude-code`. Laying them loads the plugin
# headless under a throwaway HOME: no sign-in, no model call, no turn.
set -euo pipefail

: "${CLAUDE_CODE_VERSION:?set CLAUDE_CODE_VERSION}"
temp="${RUNNER_TEMP:-$(mktemp -d)}"
prefix="$temp/claude-code"
mkdir -p "$prefix"
(
cd "$prefix"
bun add --exact "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}" >/dev/null
)
bin="$prefix/node_modules/.bin"
if [[ -n "${GITHUB_PATH:-}" ]]; then
echo "$bin" >>"$GITHUB_PATH"
fi
"$bin/claude" --version

plugin="${GITHUB_WORKSPACE:-$(pwd)}/plugins/allthings"
home="$temp/claude-home"
mkdir -p "$home"
# An unknown command is enough: the plugin loads, its types are laid, and the
# run ends without a turn.
HOME="$home" "$bin/claude" -p "/allthings-lay-types" --plugin-dir "$plugin" </dev/null >/dev/null 2>&1 || true
rm -rf "$home"
test -f "$plugin/.claude-plugin/types/claude-code/index.d.ts" || {
echo "::error::Claude Code did not lay the allthings mod's types"
exit 1
}
head -n 1 "$plugin/.claude-plugin/types/claude-code/index.d.ts"
5 changes: 5 additions & 0 deletions .github/workflows/brand.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
paths:
- "brand/**"
- "app/public/brand/**"
# The Claude Code plugin's themes and palette, held to the tokens.
- "plugins/allthings/themes/**"
- "plugins/allthings/hooks/palette.ts"
- "package.json"
- "bun.lock"
- ".github/workflows/brand.yaml"
Expand All @@ -14,6 +17,8 @@ on:
paths:
- "brand/**"
- "app/public/brand/**"
- "plugins/allthings/themes/**"
- "plugins/allthings/hooks/palette.ts"
- "package.json"
- "bun.lock"
- ".github/workflows/brand.yaml"
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ jobs:
working-directory: tools/pr-header
run: bash "$GITHUB_WORKSPACE/.github/scripts/bun-install.sh"

# The allthings mod (plugins/allthings) types against the declarations
# Claude Code lays beside it; its tsconfig extends them.
- name: 🤖 Lay the Claude Code mod's types
run: bash "$GITHUB_WORKSPACE/.github/scripts/claude-code.sh"
env:
CLAUDE_CODE_VERSION: "2.1.293"

# web/src imports the manifest its build writes to web/dist.
- name: 🏗️ Build web
working-directory: web
Expand Down
61 changes: 61 additions & 0 deletions .github/workflows/plugin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Claude plugin

# The allthings plugin for Claude Code (plugins/allthings): its manifests
# validate, its mod type-checks against the types Claude Code writes, and the
# mod's tests pass under Claude Code's own test kit, with no session, sign-in
# or network.
on:
push:
branches: ["main"]
paths:
- "plugins/**"
- ".claude-plugin/**"
- ".github/workflows/plugin.yaml"
- ".github/scripts/claude-code.sh"
pull_request:
paths:
- "plugins/**"
- ".claude-plugin/**"
- ".github/workflows/plugin.yaml"
- ".github/scripts/claude-code.sh"

permissions:
contents: read

concurrency:
group: plugin-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
# Mods need 2.1.287 or later; bump this with the version the README names.
CLAUDE_CODE_VERSION: "2.1.293"
TYPESCRIPT_VERSION: "5.9.3"

jobs:
mod:
name: 🧩 The mod validates, type-checks and passes its tests
runs-on: ubuntu-latest
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false

- name: 🍞 Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.3.14

- name: 🤖 Install Claude Code and lay the mod's types
run: bash "$GITHUB_WORKSPACE/.github/scripts/claude-code.sh"

- name: ✅ Validate the marketplace and the plugin
run: |
claude plugin validate --strict .
claude plugin validate --strict plugins/allthings

- name: 🔍 Type check the mod
run: bunx --package "typescript@${TYPESCRIPT_VERSION}" tsc -p plugins/allthings

- name: 🏄 Run the mod's tests
run: claude plugin test plugins/allthings
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ playwright-report/

# Alchemy local state, wherever a test or a deploy runs from
.alchemy/

# The types Claude Code lays beside a mod each time it loads it
plugins/*/.claude-plugin/types/
76 changes: 76 additions & 0 deletions brand/tests/claude-plugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { describe, expect, test } from "bun:test";
import { join } from "node:path";
import { colors, tokens } from "../src/tokens.ts";

// The Claude Code plugin (plugins/allthings) draws with the brand's colors:
// its themes and its mod's palette are held to the tokens here, so a token
// that changes moves them too.

const plugin = join(import.meta.dir, "../../plugins/allthings");

const hex = Object.fromEntries(
colors(tokens).map((color) => [color.name, color.hex.toUpperCase()]),
);

type Theme = {
name: string;
base: string;
overrides: Record<string, string>;
};

async function theme(slug: string): Promise<Theme> {
return (await Bun.file(
join(plugin, "themes", `${slug}.json`),
).json()) as Theme;
}

describe("the Claude Code plugin's colors", () => {
test("allthings night is Night's palette: Glow for the accent, Lavender for meta", async () => {
const night = await theme("allthings-night");
expect(night.name).toBe("allthings night");
expect(night.base).toBe("dark");
expect(night.overrides).toMatchObject({
claude: hex["glow"],
text: hex["paper"],
inactive: hex["lavender"],
suggestion: hex["lavender"],
promptBorder: hex["ruleNight"],
userMessageBackground: hex["nightRaised"],
selectionBg: hex["ruleNight"],
});
});

test("allthings paper is Paper's palette: Ink text, Bridge for the accent", async () => {
const paper = await theme("allthings-paper");
expect(paper.name).toBe("allthings paper");
expect(paper.base).toBe("light");
expect(paper.overrides).toMatchObject({
claude: hex["bridge"],
text: hex["ink"],
inactive: hex["karlText"],
suggestion: hex["violet"],
promptBorder: hex["rulePaper"],
userMessageBackground: hex["karl"],
selectionBg: hex["rulePaper"],
});
});

test("the mod's palette is the tokens' own", async () => {
const source = await Bun.file(join(plugin, "hooks/palette.ts")).text();
const palette = Object.fromEntries(
[...source.matchAll(/export const (\w+) = "(#[0-9A-F]{6})";/g)].map(
([, name, value]) => [name, value],
),
);
expect(palette).toEqual({
NIGHT: hex["night"],
PAPER: hex["paper"],
INK: hex["ink"],
BRIDGE: hex["bridge"],
GLOW: hex["glow"],
LAVENDER: hex["lavender"],
DUSK: hex["dusk"],
VIOLET: hex["violet"],
});
});
});
22 changes: 22 additions & 0 deletions plugins/allthings/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "allthings",
"version": "0.3.0",
"description": "allthings evenings in San Francisco, for people who build software: the next evening above your prompt, /at and /imin, tool rows in the slash grammar, and the allthings themes and voice.",
"author": {
"name": "allthings",
"url": "https://allthings.dev"
},
"homepage": "https://allthings.dev",
"repository": "https://github.com/allthingsweb-dev/allthingsweb",
"keywords": [
"evenings",
"meetups",
"hackathons",
"san francisco",
"software",
"community",
"mod"
],
"mcpServers": "./mcp.json",
"types": "./types/index.d.ts"
}
69 changes: 69 additions & 0 deletions plugins/allthings/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# allthings for agents

Evenings for people who build software, in the neighborhoods of San Francisco, inside your coding agent.

The plugin gives an agent the allthings MCP server (`list_events`, `get_event`, `list_speakers`, `get_community`) and two skills, `find-evenings` and `evening-briefing`. In Claude Code it also brings a mod that takes over the interface with the allthings/\_ look, two themes and an output style.

| File | For |
| ---------------------------- | ----------------------------------------------------------------------------------------------------- |
| `plugin.json`, `mcp.json` | The [Agent Plugins](https://agent-plugins.org) manifest and MCP config, read by every host |
| `.claude-plugin/plugin.json` | Claude Code's manifest: the same plugin, its MCP server from `mcp.json`, and the mod's state contract |
| `skills/` | The two skills |
| `hooks/`, `types/`, `tests/` | The Claude Code mod and its tests |
| `themes/`, `output-styles/` | The allthings night and allthings paper themes, and the allthings output style |

## Try it in Claude Code

Mods need Claude Code 2.1.287 or later in the terminal (`claude --version`), or 2.1.286 in the Desktop app (`/status`). This plugin is tested with 2.1.293.

For one session, from a clone of this repository:

```bash
claude --plugin-dir plugins/allthings
```

To keep it, add this repository as a marketplace and install the plugin:

```bash
claude plugin marketplace add allthingsweb-dev/allthingsweb
claude plugin install allthings@allthings
```

or, at the Claude Code prompt, `/plugin marketplace add allthingsweb-dev/allthingsweb` and then `/plugin install allthings@allthings`. Run `/plugin` to see `allthings` among the active mods.

## What the mod does

- **Tool rows in the slash grammar.** Each tool call's row reads as a verb, a slash and what it acts on: `read/src/app.ts`, `edit/home.tsx`, `bash/bun test`, `search/"curation"`, `fetch/allthings.dev`, `agent/Find the venue table`, and `allthings/list_events` for an MCP tool. The slash is Bridge on light themes and Glow on dark ones. The status dot, the error color, `Interrupted` and the result under the row are Claude Code's own; whatever else Claude Code's row shows (a line range, a glob, an agent type) follows in dim. Rows inside an expanded group, which carry their output, and the Desktop app's rows, which expand and collapse themselves, stay as Claude Code draws them.
- **The spinner wanders the city.** While Claude works the spinner reads `allthings/_ shipping in SoMa…`, the cursor blinking, then `pairing in the Mission…`, `debugging in Dogpatch…`, `refactoring in the Tenderloin…`, `deploying from FiDi…`, through 36 real neighborhoods. Claude Code's elapsed time and tokens stay after it.
- **The next evening, above the prompt.** A slim Night band: the date, `allthings/<topic>_`, the neighborhood, the host, and an **I'm in →** button that opens the evening's Luma page. On the day it counts down (`tonight · in 2h 14m`); during the evening it says `live now`; once you've pressed the button it says `see you at/<topic>`. No evening ahead, or no network, and there is no band.
- **`/at`** prints the next evening's card: when, where, who's on stage, and where to say you're in. **`/at <slug>`** prints any evening's. **`/imin`** opens the next evening's Luma page. All three answer at once, with no Claude turn, even while Claude is working.

With `reduceMotion` on in `/config`, the cursors stay solid.

### Where the data comes from

The band and the commands call `list_events` (upcoming, limit 1) and `get_event` on the MCP server this plugin configures in `mcp.json`, (allthings.dev/mcp), so a change of URL there, such as a local server while developing, moves them too. The next evening is cached in the plugin's store and asked for again about every 30 minutes, or when it ends. When the site can't be reached the mod stays quiet: a cached evening shows for up to two hours, then the band goes. A headless run (`claude -p`, the SDK) asks the site only when a command does.

`claude plugin validate plugins/allthings` lists everything the mod reaches: the network (`$.http.fetch`, to the MCP URL alone), the plugin's own files (`$.fs.read`, for `mcp.json`), its store, `/config`'s theme and reduce-motion rows, the `COLORFGBG` variable, and `$.process.run` for `open`, `xdg-open` or `start` when you press **I'm in**.

## Themes and the output style

`/theme` lists **allthings night** and **allthings paper**. Claude Code colors its own interface and leaves the terminal's background alone, so set the terminal's background to Night (`#1B1729`) or Paper (`#F4F1EC`) to match. The themes take their colors from the brand tokens (`brand/all-things.tokens.json`), and a test in `brand/` holds them there.

The **allthings** output style keeps Claude Code's coding instructions and changes only the voice: plain and warm, lowercase where natural, neighborhoods by name, evenings rather than events, "I'm in" and never RSVP. It is opt-in: pick it with `/output-style allthings:allthings`, or under **Output style** in `/config`.

## Developing the mod

Claude Code writes the mod's type declarations into `.claude-plugin/types/` (gitignored) each time it loads the plugin from a folder, as with `--plugin-dir`. Then, from the repository root:

```bash
claude plugin validate --strict plugins/allthings
bunx --package typescript@5.9.3 tsc -p plugins/allthings
claude plugin test plugins/allthings
```

The tests run on Claude Code's own test kit, with no session, sign-in or network: every answer from the site is a fixture in `tests/fixtures.ts`. CI (`.github/workflows/plugin.yaml`) runs the three on every change to the plugin.

## Next

Hack mode, `/hack start` and `/hack submit` for hackathon evenings, is planned for v2 and isn't built yet.
Loading
Loading