diff --git a/.devkit.yml b/.devkit.yml
index 0f5a5e2..1d37a4e 100644
--- a/.devkit.yml
+++ b/.devkit.yml
@@ -1,4 +1,4 @@
-plugin_dir: usr/plugins/share_chat
+plugin_dir: .
display_name: Share Chat
# If the plugin has agent-driven behaviour needing an e2e seam, enable it here:
# e2e_pod_env:
diff --git a/.github/workflows/plugin-e2e.yml b/.github/workflows/plugin-e2e.yml
index 587c7c9..27851ab 100644
--- a/.github/workflows/plugin-e2e.yml
+++ b/.github/workflows/plugin-e2e.yml
@@ -26,6 +26,10 @@ name: plugin-e2e
on:
pull_request:
branches: ["main", "master"]
+ # push on main: proves the shipped code, and gives the README badge a status
+ # (PR-only runs leave branch=main with zero runs → "no status").
+ push:
+ branches: ["main", "master"]
workflow_dispatch:
inputs:
capture_all_traces:
@@ -35,8 +39,8 @@ on:
permissions:
contents: read
- pull-requests: write # merge-guard drafts a PR whose plugin-e2e is red
- actions: write # DEC-074 count-based artifact retention: delete superseded e2e artifacts
+ pull-requests: write # merge-guard drafts a PR whose plugin-e2e is red
+ actions: write # DEC-074 count-based artifact retention: delete superseded e2e artifacts
jobs:
e2e:
diff --git a/Makefile b/Makefile
index c954001..cd9dedb 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,3 @@
-PLUGIN_DIR := usr/plugins/share_chat
+PLUGIN_DIR := .
PLUGIN_DISPLAY_NAME := Share Chat
-include tests/_testkit/e2e/Makefile.devkit
diff --git a/README.md b/README.md
index 24bdc25..bba5ceb 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Share Chat — Agent Zero Plugin
+# Share Chat — Agent Zero plugin
@@ -6,46 +6,25 @@
-[](https://github.com/agent-zero-plugins/agent-zero-plugin-share-chat/actions/workflows/plugin-e2e.yml)
-License: [Apache-2.0](LICENSE)
+> One click to share the conversation you're looking at. **Share Chat** adds a small circular share button to the top bar of the Agent Zero chat area. Clicking it copies a deep link to the currently open conversation to your clipboard, paste it into a message, a ticket, or another browser tab, and Agent Zero opens straight to that chat.
-One click to share the conversation you're looking at. **Share Chat** adds a small circular
-share button to the top bar of the Agent Zero chat area. Clicking it copies a **deep link**
-to the currently open conversation to your clipboard — paste it into a message, a ticket, or
-another browser tab, and Agent Zero opens straight to that chat.
+
+
+
-
+## ✨ Features
-## Why
+- **One-click deep link** — copies `/?ctxid=` to the clipboard, ready to paste into a message, a ticket, or another browser tab.
+- **Instant confirmation** — the button flips to a green ✓ _"Link copied!"_ and reverts after ~2 seconds, so you know it worked without dismissing anything.
+- **Client-side only** — the link is composed entirely in your browser. No backend, no state, no tokens, no network calls.
+- **Native feel** — a `chat-top-end` extension styled like the rest of A0's toolbar controls.
+- **Safe by default** — no chat selected → the button is a no-op, and the link grants no access by itself (see the access model below).
-- **Zero-friction hand-off** — point a teammate (or your future self on another device) at the
- exact conversation, not "the chat somewhere in my sidebar".
-- **No backend, no state, no tokens** — the link is composed entirely client-side as
- `/?ctxid=`. Nothing is stored, published, or proxied.
-- **Native feel** — the button matches A0's toolbar styling, confirms with a green check
- ("Link copied!") and auto-reverts after ~2 seconds.
+## ⚙️ How it works
-
-
-## How it works
-
-```mermaid
-flowchart LR
- subgraph Browser ["Your browser (client-side only)"]
- BTN["Share button
(chat-top-end extension)"] -->|click| CTX["getContext()
current chat id"]
- CTX --> LINK["Compose deep link
origin + ?ctxid=<id>"]
- LINK --> CLIP["navigator.clipboard
(textarea fallback)"]
- CLIP --> CONFIRM["✓ Link copied!
auto-reset 2s"]
- end
- subgraph Recipient ["Recipient's browser"]
- URL["Opens link with ?ctxid"] --> STORE["A0 chats-store.init()
reads & strips ctxid"]
- STORE --> OPEN["Selects that chat"]
- end
- LINK -.paste anywhere.-> URL
-```
+
-The plugin only **produces** the link. The **consume** half — opening `?ctxid=` links — is a
-pre-existing Agent Zero capability the plugin piggybacks on.
+The plugin only **produces** the link. The **consume** half — opening `?ctxid=` links — is a pre-existing Agent Zero capability it piggybacks on.
### Access model (honest edition)
@@ -53,108 +32,60 @@ The deep link is **not** a public share page and grants **no access by itself**:
- The recipient must be able to reach your Agent Zero instance **and** authenticate to it.
An unauthenticated visitor hits the A0 login screen, not your conversation.
-- The `ctxid` identifies a chat *within* your instance; it is not a bearer token.
+- The `ctxid` identifies a chat _within_ your instance; it is not a bearer token.
- If no chat is selected, the button is a safe no-op.
See [SECURITY.md](SECURITY.md) for the full threat model.
-## Usage
-
-Every behaviour below is covered by a BDD scenario in [`tests/e2e/features/`](tests/e2e/features/),
-and [`docs/BEHAVIOUR.md`](docs/BEHAVIOUR.md) shows each one as a screenshot captured from a passing
-run — so what you read here is what CI proves on every push.
-
-### Share the chat you are looking at
+## 💬 Usage
-Click the share control in the chat toolbar. The link to the current chat goes straight to your
-clipboard, and the button confirms with a green *"Link copied!"* that reverts after ~2 seconds — so
-you know it worked without a dialog to dismiss.
-
-Paste it to a teammate, or to yourself on another device, and they land on **that** conversation
-rather than hunting the sidebar for "the one about the deploy".
-
-### What the link actually is
+Click the share control in the chat toolbar. That's the whole feature — and that's on purpose:
```
/?ctxid=
```
-Composed entirely client-side. **Nothing is stored, published, proxied, or sent anywhere** — there is
-no backend, no state and no token. The link is only as reachable as your A0 instance already is: if
-the recipient cannot reach your origin, or is not authenticated to it, the link does nothing for them.
-
-That is the deliberate trade-off. This plugin is a fast hand-off *within* an environment people
-already share, not a publishing tool. If you need a chat readable by someone with no access to your
-instance, this is the wrong plugin — export it instead.
-
-### Scope, stated honestly
-
-The suite asserts three scenarios: the control is present, sharing copies a link to *this* chat, and
-the copy is confirmed. That is the whole feature — it is small on purpose, and the coverage matches
-the surface rather than being padded to look thorough.
-
----
+
-## Install
+Every behaviour has an asserted [BDD scenario](tests/e2e/features/), and [`docs/BEHAVIOUR.md`](docs/BEHAVIOUR.md) shows each as a screenshot from a passing run.
-### From the Plugin Hub
+## 📦 Installation
-1. Open **Settings → Plugins** in Agent Zero.
-2. Find **Share Chat** and click install, then enable it.
-3. Open any chat — the share button appears at the right end of the top bar.
+**Plugin Hub** — once listed in the [Plugin Index](https://github.com/agent0ai/a0-plugins): **Settings → Plugins → Share Chat → Install**.
-### Manual install
+**Manual** (Zip) — build it yourself, or grab the zip from the [latest release](https://github.com/agent-zero-plugins/agent-zero-plugin-share-chat/releases/latest):
```bash
-cd /a0/usr/plugins
-git clone https://github.com/agent-zero-plugins/agent-zero-plugin-share-chat.git _tmp_share_chat
-cp -r _tmp_share_chat/usr/plugins/share_chat ./share_chat
-rm -rf _tmp_share_chat
+make package # → dist/share_chat.zip
```
-Then enable **Share Chat** in **Settings → Plugins**.
-
-## Configuration
+Then **Settings → Plugins → Install from file** → pick the zip. Or copy the plugin into your instance (`usr/plugins/share_chat/`) and reload plugins.
-There is none — by design. `default_config.yaml` declares no options, there is no config
-screen, no per-project and no per-agent settings. The plugin is a single declarative HTML
-extension (`extensions/webui/chat-top-end/share-chat.html`).
+## 🔧 Configuration
-| Manifest field | Value |
-|---|---|
-| `name` | `share_chat` |
-| `version` | `0.1.0` |
-| `license` | `Apache-2.0` |
-| `always_enabled` | `false` (opt-in) |
-| `per_project_config` / `per_agent_config` | `false` / `false` |
+None — by design. `default_config.yaml` declares no options, there is no config screen, no per-project and no per-agent settings. The plugin is a single declarative HTML extension (`extensions/webui/chat-top-end/share-chat.html`).
-## Development & testing
-
-The repo follows the [agent-zero-plugins devkit v2](https://github.com/agent-zero-plugins/agent-zero-plugin-development-testkit)
-layout: plugin source under `usr/plugins/share_chat/`, devkit as the `tests/_testkit` submodule.
+## 🛠️ Development
```bash
-git clone --recursive https://github.com/agent-zero-plugins/agent-zero-plugin-share-chat.git
+git clone --recurse-submodules https://github.com/agent-zero-plugins/agent-zero-plugin-share-chat
cd agent-zero-plugin-share-chat
-# L1 shape suite (fast, no A0 boot)
-pip install pytest pyyaml
-pytest tests/component -v
-
-# Tier-1 static BDD gates (feature purity, honesty, traceability)
-make verify
-
-# Full local BDD e2e loop (boots a disposable nested A0)
-make bdd-e2e
+make verify # BDD static gates
+python -m pytest tests -q # L1 shape suite (needs the tests/_testkit submodule)
+make e2e # behaviour BDD on a nested disposable A0 (podman)
```
-Behaviour contract lives in [`docs/spec/behaviour-spec.md`](docs/spec/behaviour-spec.md)
-(BEH-1…BEH-11) and is mirrored by the executable Gherkin in
-[`tests/e2e/features/`](tests/e2e/features/). CI (`plugin-e2e`) runs lint → seam-off
-red-proof → BDD e2e on every PR.
+## 🧪 Test layout
+
+| Layer | Where | What |
+| ------------- | -------------------------------------- | ---------------------------------------------------------- |
+| L1 shape | `tests/component/test_plugin_shape.py` | static validator, deps/A0-API audits, dead hooks |
+| Static gates | `make verify` | feature purity, honesty, traceability |
+| Behaviour BDD | `tests/e2e/features/` + `steps/` | control present, link identifies this chat, copy confirmed |
-See [CONTRIBUTING.md](CONTRIBUTING.md) for the workflow.
+Specs live in [`docs/spec/`](docs/spec/) (BEH-1..11). CI runs the pyramid on every PR, including a seam-off red-proof — a scenario that passes without the plugin installed fails the build.
-## License
+## ⚖️ License
-[Apache-2.0](LICENSE) © Interu
\ No newline at end of file
+Apache-2.0 — see [LICENSE](LICENSE), © Interu. Links are composed in your browser and copied to your clipboard; nothing is stored, published, or sent anywhere. Full threat model in [SECURITY.md](SECURITY.md).
diff --git a/usr/plugins/share_chat/__init__.py b/__init__.py
similarity index 100%
rename from usr/plugins/share_chat/__init__.py
rename to __init__.py
diff --git a/usr/plugins/share_chat/default_config.yaml b/default_config.yaml
similarity index 100%
rename from usr/plugins/share_chat/default_config.yaml
rename to default_config.yaml
diff --git a/docs/how-it-works.png b/docs/how-it-works.png
new file mode 100644
index 0000000..97ffc8e
Binary files /dev/null and b/docs/how-it-works.png differ
diff --git a/docs/share-chat.gif b/docs/share-chat.gif
new file mode 100644
index 0000000..585c8dd
Binary files /dev/null and b/docs/share-chat.gif differ
diff --git a/usr/plugins/share_chat/extensions/webui/chat-top-end/share-chat.html b/extensions/webui/chat-top-end/share-chat.html
similarity index 100%
rename from usr/plugins/share_chat/extensions/webui/chat-top-end/share-chat.html
rename to extensions/webui/chat-top-end/share-chat.html
diff --git a/usr/plugins/share_chat/meta.yaml b/meta.yaml
similarity index 100%
rename from usr/plugins/share_chat/meta.yaml
rename to meta.yaml
diff --git a/usr/plugins/share_chat/plugin.yaml b/plugin.yaml
similarity index 100%
rename from usr/plugins/share_chat/plugin.yaml
rename to plugin.yaml
diff --git a/tests/_testkit b/tests/_testkit
index c77ed9b..2f3e8ca 160000
--- a/tests/_testkit
+++ b/tests/_testkit
@@ -1 +1 @@
-Subproject commit c77ed9bd7ccb675b656caffabcf5c1e7aad77c91
+Subproject commit 2f3e8ca6f5f2a69e4a247beb27c1a6912980b9ef
diff --git a/tests/component/test_plugin_shape.py b/tests/component/test_plugin_shape.py
index e11b19e..1cce0e3 100644
--- a/tests/component/test_plugin_shape.py
+++ b/tests/component/test_plugin_shape.py
@@ -72,7 +72,8 @@ def test_license_declared_apache2(plugin_dir: Path) -> None:
plugin = yaml.safe_load((plugin_dir / "plugin.yaml").read_text())
assert plugin.get("license") == "Apache-2.0"
- license_file = plugin_dir.parent.parent.parent / "LICENSE"
+ # Root layout: the repo root IS the plugin dir, so LICENSE sits beside plugin.yaml.
+ license_file = plugin_dir / "LICENSE"
text = license_file.read_text()
assert "Apache License" in text and "Version 2.0" in text
assert len(text) > 10_000, f"LICENSE is a stub ({len(text)} bytes)"
diff --git a/tests/conftest.py b/tests/conftest.py
index bfbb6f6..32b09da 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,11 +1,29 @@
-"""Shared fixtures for the share_chat test suite."""
-from __future__ import annotations
+"""pytest conftest for an agent-zero-plugins plugin source repo.
+Provides the `plugin_dir` fixture every a0_plugin_testkit assertion needs.
+Point it at this plugin's source directory.
+"""
+
+import sys
from pathlib import Path
import pytest
+# Tests exec plugin modules straight from the plugin tree. Without this, the
+# loader writes __pycache__ INTO the plugin dir (= repo root, root layout),
+# which the static validator rightly flags as a packaging hazard.
+sys.dont_write_bytecode = True
+
@pytest.fixture(scope="session")
def plugin_dir() -> Path:
- return Path(__file__).resolve().parent.parent / "usr" / "plugins" / "share_chat"
+ """Path to this plugin's source directory.
+
+ The testkit's assertions take this as their first argument:
+ assert_extension_at_surface(plugin_dir, ...)
+ assert_no_dead_plugin_hooks(plugin_dir)
+ etc.
+
+ Root layout: the repo root IS the plugin dir (.devkit.yml plugin_dir: .)
+ """
+ return Path(__file__).resolve().parent.parent
diff --git a/usr/plugins/share_chat/webui/.gitkeep b/webui/.gitkeep
similarity index 100%
rename from usr/plugins/share_chat/webui/.gitkeep
rename to webui/.gitkeep
diff --git a/usr/plugins/share_chat/webui/thumbnail.png b/webui/thumbnail.png
similarity index 100%
rename from usr/plugins/share_chat/webui/thumbnail.png
rename to webui/thumbnail.png