Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 12 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "exeora",
"description": "Remote Exeora tools and guided workflows for Claude.",
"owner": { "name": "Exeora", "email": "hello@exeora.dev" },
"plugins": [
{
"name": "exeora-workspaces",
"source": "./plugins/exeora-claude",
"description": "Exeora's remote MCP connector and guided workflows for Claude."
}
]
}
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ Exeora Workspace is a dashboard build entry at `/dashboard/mcp-panel` and reuses

Edit the maintained files and bump `plugins/exeora/plugin.json`'s version when releasing package changes. Copy updated square mark PNGs from the generated brand assets into `plugins/exeora/assets/` when the branding changes. Archive timestamps and ordering are fixed, so unchanged source produces the same checksum. The builder reads only the plugin resources, the license and the marketplace entry; credentials and application source are not packaged. Publishing to OpenAI's public directory remains a separate verified-publisher submission, MCP connection, review and publication step. See [Package your plugin](https://developers.openai.com/plugins/build/plugins) and [Submit and publish](https://developers.openai.com/plugins/deploy/submission).

### Building the Claude web plugin

`plugins/exeora-claude/` is a separate Claude web package with `.claude-plugin/plugin.json`, root `.mcp.json`, three skills and a listing PNG. `bun run plugin:build:claude` generates identical ZIP bytes as `exeora-claude.plugin` and `exeora-claude-plugin.zip`, filename-specific checksums and metadata. Landing builds generate both platform packages independently. `.claude-plugin/marketplace.json` provides repository installation for Claude without changing the ChatGPT catalog. Bump only the Claude manifest version for Claude package changes. See [Claude web packaging and acceptance](docs/claude-web-plugin.md) for verified schema, OAuth setup, limits and separate manual web acceptance. CLI validation proves syntax only; it does not prove Claude web installation or OAuth.

### Checks

```bash
Expand Down
12 changes: 12 additions & 0 deletions apps/gateway/src/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,18 @@ function withAssetHeaders(response: Response, pathname: string, requestUrl: URL)
options.cacheControl = "public, max-age=31536000, immutable";
}
const result = withSecurityHeaders(response, options);
// Static Assets has no MIME mapping for .plugin; both downloads are ZIP archives.
if (
(response.ok || response.status === 304) &&
(pathname === "/downloads/exeora-claude.plugin" ||
pathname === "/downloads/exeora-claude-plugin.zip")
) {
result.headers.set("Content-Type", "application/zip");
result.headers.set(
"Content-Disposition",
`attachment; filename="${pathname.split("/").at(-1)}"`,
);
}
// These are public static modules/fonts, fetched by the isolated MCP Apps iframe.
if (pathname.startsWith("/dashboard/assets/") || pathname.startsWith("/fonts/")) {
result.headers.set("Access-Control-Allow-Origin", "*");
Expand Down
44 changes: 44 additions & 0 deletions apps/gateway/src/assets.workers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,50 @@ describe("static files", () => {
'href="/downloads/exeora-plugin.zip"',
);
});
it("serves identical Claude ZIP and plugin downloads with matching digests and web instructions", async () => {
const zip = await get("/downloads/exeora-claude-plugin.zip");
const plugin = await get("/downloads/exeora-claude.plugin");
expect(zip.status).toBe(200);
expect(plugin.status).toBe(200);
expect(zip.headers.get("content-type")).toContain("application/zip");
expect(plugin.headers.get("content-type")).toContain("application/zip");
expect(plugin.headers.get("content-disposition")).toBe(
'attachment; filename="exeora-claude.plugin"',
);
expect(zip.headers.get("content-disposition")).toBe(
'attachment; filename="exeora-claude-plugin.zip"',
);
const bytes = new Uint8Array(await zip.arrayBuffer());
expect(new Uint8Array(await plugin.arrayBuffer())).toEqual(bytes);
expect([...bytes.slice(0, 4)]).toEqual([0x50, 0x4b, 0x03, 0x04]);
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
const sha256 = [...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("");
expect(await (await get("/downloads/exeora-claude-plugin.json")).json()).toMatchObject({
name: "exeora-workspaces",
bytes: bytes.length,
sha256,
download: "/downloads/exeora-claude.plugin",
zip: "/downloads/exeora-claude-plugin.zip",
});
for (const filename of ["exeora-claude.plugin", "exeora-claude-plugin.zip"]) {
expect(await (await get(`/downloads/${filename}.sha256`)).text()).toBe(
`${sha256} ${filename}\n`,
);
}
const etag = plugin.headers.get("etag");
expect(etag).toBeTruthy();
const revalidated = await get("/downloads/exeora-claude.plugin", {
"If-None-Match": etag as string,
});
expect(revalidated.status).toBe(304);
expect(revalidated.headers.get("content-type")).toContain("application/zip");
expect((await revalidated.arrayBuffer()).byteLength).toBe(0);
const html = await (await get("/docs/claude-plugin/")).text();
expect(html).toContain('href="/downloads/exeora-claude.plugin"');
expect(html).toContain('href="/downloads/exeora-claude-plugin.zip"');
expect(html).toContain("Upload plugin");
expect(html).toContain('href="/docs/plugin/"');
});
it("allows the MCP sandbox to fetch public modules and fonts without opening HTML to framing", async () => {
const script = await get(await assetPathFromShell());
expect(script.headers.get("access-control-allow-origin")).toBe("*");
Expand Down
3 changes: 2 additions & 1 deletion apps/web/landing/src/components/Footer.astro
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ const links = [
{ href: "/docs/", label: "Docs" },
{ href: "/docs/tools/", label: "Tools" },
{ href: "/docs/extension/", label: "Chrome" },
{ href: "/docs/plugin/", label: "Plugin" },
{ href: "/docs/plugin/", label: "ChatGPT plugin" },
{ href: "/docs/claude-plugin/", label: "Claude plugin" },
{ href: "/#security", label: "Security" },
{ href: "/#faq", label: "FAQ" },
{ href: "/brand/", label: "Brand" },
Expand Down
6 changes: 5 additions & 1 deletion apps/web/landing/src/components/Hero.astro
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,11 @@ const promises = [
</a>
{" · "}
<a href="/docs/plugin/" class="text-foreground underline-offset-2 hover:underline">
Download the Exeora plugin
ChatGPT plugin
</a>
{" · "}
<a href="/docs/claude-plugin/" class="text-foreground underline-offset-2 hover:underline">
Claude web plugin
</a>
</p>
</div>
Expand Down
1 change: 1 addition & 0 deletions apps/web/landing/src/lib/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const DOC_SECTIONS = [
{ href: "/docs/", label: "Getting started" },
{ href: "/docs/clients/", label: "Connecting a client" },
{ href: "/docs/plugin/", label: "Exeora plugin" },
{ href: "/docs/claude-plugin/", label: "Claude web plugin" },
{ href: "/docs/projects/", label: "Projects, locations and workspaces" },
{ href: "/docs/github/", label: "Connecting GitHub" },
],
Expand Down
57 changes: 57 additions & 0 deletions apps/web/landing/src/pages/docs/claude-plugin.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
import manifest from "../../../../../../plugins/exeora-claude/.claude-plugin/plugin.json";
import {
CLAUDE_PLUGIN_DOWNLOAD,
CLAUDE_PLUGIN_METADATA,
CLAUDE_PLUGIN_ZIP,
} from "../../../../../../scripts/claude-plugin-bundle.js";
import Doc from "../../layouts/Doc.astro";
---

<Doc title="Exeora for Claude web" description="Download Exeora's independent Claude plugin with three skills and a remote MCP connector, then upload it in Claude web." canonicalPath="/docs/claude-plugin/">
<p>Work on your projects from a Claude web conversation. This package brings three guided skills and Exeora's remote MCP connector together. It is a separate download from the <a href="/docs/plugin/">ChatGPT and Codex plugin</a>.</p>

<div class="border-border bg-surface my-6 flex flex-col gap-4 rounded-xl border p-5 sm:flex-row sm:items-center sm:justify-between">
<div class="flex items-center gap-3">
<img src="/brand/exeora-mark-256-on-brand.png" alt="" width="48" height="48" class="size-12 rounded-lg" />
<div>
<p class="text-title-lg m-0!">Exeora for Claude</p>
<p class="text-body-md text-foreground-muted m-0!">Version {manifest.version} · Remote MCP + 3 skills</p>
</div>
</div>
<a href={CLAUDE_PLUGIN_DOWNLOAD} download="exeora-claude.plugin" class="bg-accent text-on-accent hover:bg-foreground text-title-md inline-flex justify-center rounded-lg px-5 py-3 no-underline! transition-colors duration-fast">Download Claude plugin</a>
</div>

<p><a href={CLAUDE_PLUGIN_ZIP} download="exeora-claude-plugin.zip">Download as ZIP</a> · <a href={`${CLAUDE_PLUGIN_DOWNLOAD}.sha256`}>SHA-256 checksum</a> · <a href={CLAUDE_PLUGIN_METADATA}>Package details</a>. The <code>.plugin</code> and <code>.zip</code> downloads contain identical archive bytes and no passwords or tokens.</p>

<h2 id="install">Install in Claude web</h2>
<ol class="[&_li]:list-decimal!">
<li>Download either file. You do not need to extract it.</li>
<li>In <a href="https://claude.ai">Claude web</a>, open <strong>Customize > Plugins > Add > Upload plugin</strong> and select the file. Plugins are available on Pro, Max, Team and Enterprise; organization policies may restrict uploads.</li>
<li>Open the installed <strong>Exeora</strong> plugin's <strong>Connectors</strong> tab. Add the Exeora connector if needed, then select <strong>Connect</strong> and complete Exeora's OAuth sign-in. On Team or Enterprise, an Owner may need to add the connector first; each member then connects their own account.</li>
<li>In your conversation, enable Exeora under <strong>+ > Connectors</strong>. Choose <strong>exeora-setup</strong> from the <strong>/</strong> skill menu or ask <strong>“Help me connect Exeora and choose a project.”</strong></li>
</ol>
<p>Installing the plugin loads its skills but does not add the connector or sign you in. A successful project-listing tool call confirms access. Upload through the Plugins page; attaching the file to an ordinary conversation does not install it. See <a href="https://claude.com/docs/plugins/overview">Claude's installation guide</a>.</p>
<p>You can also use <strong>Add > Add marketplace</strong> with <code>https://github.com/leynier/exeora</code> and select <strong>exeora-workspaces</strong>. This repository source may be restricted by your organization. It is separate from a reviewed public directory listing.</p>

<h2 id="oauth">Connect with your own account</h2>
<p>The bundled connector uses <code>https://exeora.dev/mcp</code> over HTTP. Claude discovers the server's OAuth configuration and handles sign-in from the Connectors tab. Choose the projects you intend to authorize on Exeora's consent screen. No fixed client ID, client secret or bearer token is required by this download.</p>
<p>If you configure the connector manually, retain OAuth sign-in and the detected settings. Claude web offers its published client identity or automatic registration; Exeora advertises support for both. Do not choose <strong>No sign in</strong> for this endpoint. The package does not set Claude Code callback ports, OAuth scope pins or discovery overrides: those CLI options are not documented as web plugin controls. See <a href="https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp">Claude's custom connector guide</a>.</p>
<p>For local projects, install the <a href="/docs/">Exeora CLI</a>, keep <code>exeora connect</code> running and add the project on its machine with <code>exeora project add /path/to/project</code>. Or use <a href="/docs/cloud/">Exeora Cloud</a>. Claude's remote connection comes from Anthropic's infrastructure to Exeora's public gateway; it cannot reach your localhost directly.</p>

<h2 id="included">What the skills help you do</h2>
<table>
<thead><tr><th>Skill</th><th>Workflow</th></tr></thead>
<tbody>
<tr><td>exeora-setup</td><td>Verify access and choose an authorized project and workspace.</td></tr>
<tr><td>exeora-project</td><td>Explore, edit and validate code through Exeora's remote tools.</td></tr>
<tr><td>exeora-workspace</td><td>Inspect files and changes; optionally open the Workspace view when Claude offers it.</td></tr>
</tbody>
</table>
<p>The skills use the connector's advertised tools and follow your project's <a href="/docs/policy/">policy</a>. They do not change grants or persistent tool approvals. An offline machine must reconnect; a missing project may be outside the existing grant.</p>

<h2 id="limits">Support and verification limits</h2>
<p>Claude web supports skills and fixed-URL remote MCP connectors. This package does not need local MCP processes, hooks or sub-agents. The listing icon and support/policy links are included using documented fields; their appearance for a custom upload depends on Claude's UI. See <a href="https://claude.com/docs/plugins/platform-support">support by platform</a>.</p>
<p>An interactive MCP App depends on the tools and UI capabilities Claude exposes. The workspace skill uses read tools and the <a href="/dashboard/">Exeora dashboard</a> when embedded UI is unavailable. ChatGPT's Sideapp, composer and file-routing features are not promised in Claude.</p>
<p>Repository checks validate the archive, metadata, skills and downloadable files. CLI parsing does not verify Claude web installation or real OAuth. Confirm those separately by uploading in Claude web, connecting your account and successfully listing projects. The package has not been submitted to Anthropic's public directory or uploaded through the organization Plugins API.</p>
</Doc>
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"build": "bun run build:landing && bun run build:dashboard",
"build:landing": "bun run ../../scripts/build-plugin.ts && astro build --root landing --outDir ../public && pagefind --site public --glob \"docs/**/*.html\"",
"build:landing": "bun run ../../scripts/build-plugin.ts && bun run ../../scripts/build-claude-plugin.ts && astro build --root landing --outDir ../public && pagefind --site public --glob \"docs/**/*.html\"",
"build:dashboard": "vite build --config dashboard/vite.config.ts",
"dev:dashboard": "vite --config dashboard/vite.config.ts"
},
Expand Down
Loading
Loading