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
24 changes: 24 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "glance-agent-plugins",
"version": "1.0.0",
"description": "glance.sh plugins for coding agents",
"metadata": {
"description": "glance.sh plugins for coding agents"
},
"owner": {
"name": "Modem"
},
"plugins": [
{
"name": "glance-claude",
"description": "glance.sh screenshot session tools for Claude Code",
"source": {
"source": "npm",
"package": "@modemdev/glance-claude"
},
"category": "productivity",
"tags": ["glance", "screenshots", "mcp"]
}
]
}
84 changes: 84 additions & 0 deletions .github/workflows/release-claude.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Release claude package

on:
push:
tags:
- 'claude-v*'
workflow_dispatch:
inputs:
publish:
description: Publish to npm (unchecked = dry run only)
type: boolean
required: false
default: false

permissions:
contents: read
id-token: write

concurrency:
group: release-claude
cancel-in-progress: false

jobs:
release:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org

- name: Read package metadata
id: pkg
run: |
name=$(node -p "require('./claude/package.json').name")
version=$(node -p "require('./claude/package.json').version")
plugin_version=$(node -p "require('./claude/.claude-plugin/plugin.json').version")
echo "name=$name" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "plugin_version=$plugin_version" >> "$GITHUB_OUTPUT"

- name: Validate plugin manifest version matches package version
run: |
if [ "${{ steps.pkg.outputs.version }}" != "${{ steps.pkg.outputs.plugin_version }}" ]; then
echo "::error title=Version mismatch::claude/package.json has version ${{ steps.pkg.outputs.version }} but claude/.claude-plugin/plugin.json has version ${{ steps.pkg.outputs.plugin_version }}."
exit 1
fi

- name: Validate release tag matches package version
if: github.event_name == 'push'
run: |
expected="claude-v${{ steps.pkg.outputs.version }}"
if [ "${GITHUB_REF_NAME}" != "$expected" ]; then
echo "::error title=Tag/version mismatch::Expected tag $expected for version ${{ steps.pkg.outputs.version }}, got ${GITHUB_REF_NAME}."
exit 1
fi

- name: Ensure npm version is not already published
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish)
run: |
if npm view "${{ steps.pkg.outputs.name }}@${{ steps.pkg.outputs.version }}" version >/dev/null 2>&1; then
echo "::error title=Version already published::${{ steps.pkg.outputs.name }}@${{ steps.pkg.outputs.version }} already exists on npm."
exit 1
fi

- name: Validate package contents
working-directory: claude
run: npm pack --dry-run

- name: Publish to npm
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish)
working-directory: claude
run: npm publish --access public --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Dry-run summary
if: github.event_name == 'workflow_dispatch' && !inputs.publish
run: |
echo "Dry run complete for ${{ steps.pkg.outputs.name }}@${{ steps.pkg.outputs.version }}."
echo "Re-run this workflow with publish=true to publish."
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![Integration](https://github.com/modem-dev/glance-agent-plugins/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/modem-dev/glance-agent-plugins/actions/workflows/test.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
[![Supported Agents](https://img.shields.io/badge/agents-pi%20%7C%20OpenCode-blue)](#available-plugins)
[![Supported Agents](https://img.shields.io/badge/agents-pi%20%7C%20OpenCode%20%7C%20Claude%20Code-blue)](#available-plugins)

Agent integrations for [glance.sh](https://glance.sh) — temporary image sharing for coding agents.

Expand All @@ -14,6 +14,7 @@ Paste a screenshot in your browser, your agent gets the URL instantly.
|---|---|---|---|
| [pi](https://github.com/mariozechner/pi) | [`pi/`](pi/) | `@modemdev/glance-pi` | `pi install npm:@modemdev/glance-pi` |
| [OpenCode](https://github.com/anomalyco/opencode) | [`opencode/`](opencode/) | `@modemdev/glance-opencode` | Add `"@modemdev/glance-opencode"` to `opencode.json` `plugin` list |
| [Claude Code](https://github.com/anthropics/claude-code) | [`claude/`](claude/) | `@modemdev/glance-claude` | `/plugin marketplace add modem-dev/glance-agent-plugins` then `/plugin install glance-claude@glance-agent-plugins` |

## How it works

Expand Down
13 changes: 13 additions & 0 deletions claude/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "glance-claude",
"version": "0.1.0",
"description": "glance.sh MCP tools for Claude Code",
"author": {
"name": "Modem"
},
"homepage": "https://github.com/modem-dev/glance-agent-plugins/tree/main/claude",
"repository": "https://github.com/modem-dev/glance-agent-plugins",
"license": "MIT",
"keywords": ["claude-code", "mcp", "glance", "screenshots", "agent"],
"mcpServers": "./.mcp.json"
}
6 changes: 6 additions & 0 deletions claude/.mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"glance": {
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/servers/glance-mcp.js"]
}
}
97 changes: 97 additions & 0 deletions claude/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# glance.sh plugin for Claude Code

[Claude Code](https://github.com/anthropics/claude-code) plugin that adds glance.sh screenshot tools via MCP.

## What it does

Adds two MCP tools:

- **`glance`** — creates/reuses a live session and returns a URL like `https://glance.sh/s/<id>`
- **`glance_wait`** — waits for the next pasted image and returns `Screenshot: https://glance.sh/<token>.<ext>`

The server keeps a background SSE listener alive, reconnects automatically, and refreshes sessions before they expire.

## Install

Recommended (npm-backed marketplace plugin):

```text
/plugin marketplace add modem-dev/glance-agent-plugins
/plugin install glance-claude@glance-agent-plugins
```

This plugin is distributed as `@modemdev/glance-claude` and installed through Claude Code's plugin marketplace flow.

### Local development (plugin dir)

From this repo root:

```bash
claude --plugin-dir ./agent-plugins/claude
```

Or from inside `agent-plugins/`:

```bash
claude --plugin-dir ./claude
```

## Verify

1. Run `/mcp` and confirm a `glance` server is connected.
2. Ask Claude to call the `glance` tool.
3. Open the returned `https://glance.sh/s/<id>` URL and paste an image.
4. Ask Claude to call `glance_wait`.
5. Confirm Claude receives `Screenshot: <url>`.

## Update / remove

- Update: `/plugin update glance-claude`
- Remove: `/plugin uninstall glance-claude`

If you have multiple plugins with the same name from different marketplaces, use the fully qualified form (`glance-claude@glance-agent-plugins`).

## Publishing (maintainers)

Releases are automated via GitHub Actions.

Prerequisite: configure `NPM_TOKEN` in the `glance-agent-plugins` repository with publish access to `@modemdev/glance-claude`.

1. Bump `version` in both:
- `claude/package.json`
- `claude/.claude-plugin/plugin.json`
2. Commit and push to `main`.
3. Create and push a matching tag:

```bash
git tag claude-v0.1.0
git push origin claude-v0.1.0
```

The `Release claude package` workflow validates tag/version alignment, checks for already-published versions, runs `npm pack --dry-run`, and publishes with npm provenance.

## How it works

```text
Claude calls glance
└─▶ MCP server POST /api/session
└─▶ returns session URL

Claude calls glance_wait
└─▶ waits for SSE image event

User pastes image at /s/<id>
└─▶ glance.sh emits image event
└─▶ tool returns Screenshot: <url>
```

## Requirements

- Claude Code with plugin support
- Node.js runtime available to Claude Code (for stdio MCP server)

## Configuration

Optional environment variable:

- `GLANCE_BASE_URL` (default: `https://glance.sh`)
31 changes: 31 additions & 0 deletions claude/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@modemdev/glance-claude",
"version": "0.1.0",
"description": "glance.sh plugin package for Claude Code",
"license": "MIT",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/modem-dev/glance-agent-plugins.git",
"directory": "claude"
},
"homepage": "https://github.com/modem-dev/glance-agent-plugins/tree/main/claude",
"bugs": {
"url": "https://github.com/modem-dev/glance-agent-plugins/issues"
},
"keywords": [
"claude-code",
"claude-plugin",
"mcp",
"glance",
"screenshot",
"agent"
],
"files": [
".claude-plugin",
".mcp.json",
"servers/glance-mcp.js",
"README.md"
],
"main": "servers/glance-mcp.js"
}
63 changes: 63 additions & 0 deletions claude/servers/glance-mcp.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
export type McpTextContent = { type: string; text: string }

export interface ToolResult {
content: McpTextContent[]
structuredContent?: Record<string, unknown>
isError?: boolean
}

export interface GlanceRuntime {
executeTool(name: string, args?: Record<string, unknown>, signal?: AbortSignal): Promise<ToolResult>
getTools(): Array<{
name: string
description: string
inputSchema: Record<string, unknown>
}>
getState(): {
currentSession: { id: string; url: string } | null
running: boolean
sessionCreatedAt: number
waiterCount: number
}
startBackground(): void
stopBackground(): void
}

export interface McpServer {
handleData(chunk: Buffer | string): void
handleMessage(message: Record<string, unknown>): Promise<void>
start(): void
stop(): void
}

export function createGlanceRuntime(options?: {
baseUrl?: string
fetchImpl?: typeof fetch
log?: (message: string) => void
quietLogs?: boolean
}): GlanceRuntime

export interface McpRuntime {
getTools(): Array<{
name: string
description: string
inputSchema: Record<string, unknown>
}>
executeTool(
name: string,
args?: Record<string, unknown>,
signal?: AbortSignal,
): Promise<ToolResult>
stopBackground(): void
}

export function createMcpServer(options?: {
runtime?: McpRuntime
stdin?: NodeJS.ReadableStream
stdout?: NodeJS.WritableStream
stderr?: NodeJS.WritableStream
sendMessage?: (message: Record<string, unknown>) => void
exit?: (code: number) => void
log?: (message: string) => void
quietLogs?: boolean
}): McpServer
Loading