From 4276894bec0353c20cc048db3008702409ed9e7d Mon Sep 17 00:00:00 2001 From: Paul Logan Date: Tue, 14 Jul 2026 23:58:08 -0700 Subject: [PATCH 1/7] docs: design Codex plugin ergonomics --- SESSION_LOG_2026_07_14.md | 31 ++++ .../specs/2026-07-14-codex-plugin-design.md | 149 ++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 SESSION_LOG_2026_07_14.md create mode 100644 docs/superpowers/specs/2026-07-14-codex-plugin-design.md diff --git a/SESSION_LOG_2026_07_14.md b/SESSION_LOG_2026_07_14.md new file mode 100644 index 0000000..3b3fc30 --- /dev/null +++ b/SESSION_LOG_2026_07_14.md @@ -0,0 +1,31 @@ +# Session Log — 2026-07-14 + +## Codex Wire ergonomics + +### What + +Investigated whether Wire already ships a Codex plugin. Repository had a Claude Code plugin (`.claude-plugin/plugin.json`) and shared MCP config (`.mcp.json`), but no `.codex-plugin/plugin.json` or Codex marketplace entry. Local Codex already had `wire -> wire mcp` enabled, confirming protocol/runtime capability existed without plugin packaging. + +### Why add a plugin + +MCP supplies tools. Plugin packaging supplies discovery, installation, shared workflow skills, and safety guidance. Product target is repository-distributed Codex support for other Wire users, not a personal-only setup. + +### Decision + +Use a root-level Codex manifest alongside the Claude manifest. Reuse `.mcp.json` and `skills/`; do not create a nested copied plugin tree. Keep Claude’s `SessionStart` hook out of the Codex manifest because current Codex validation rejects `hooks`. Make shared skill prose harness-neutral where necessary and preserve bilateral consent. + +Rejected: + +- nested `plugins/wire`: duplicates or fragile-links package assets; +- MCP-only docs: capability works, but distribution and behavioral ergonomics remain weak. + +Design: `docs/superpowers/specs/2026-07-14-codex-plugin-design.md`. + +### Current phase + +Design approved conversationally. Written spec awaiting user review before implementation planning. + +## Artifacts + +- `docs/superpowers/specs/2026-07-14-codex-plugin-design.md` — product design, invariants, packaging, and verification criteria. +- `SESSION_LOG_2026_07_14.md` — session decisions and artifact catalog. diff --git a/docs/superpowers/specs/2026-07-14-codex-plugin-design.md b/docs/superpowers/specs/2026-07-14-codex-plugin-design.md new file mode 100644 index 0000000..2c5daf0 --- /dev/null +++ b/docs/superpowers/specs/2026-07-14-codex-plugin-design.md @@ -0,0 +1,149 @@ +# Codex Plugin Design + +**Date:** 2026-07-14 +**Status:** Approved for implementation planning + +## Problem + +Wire already works in Codex through its stdio MCP server, `wire mcp`. That gives Codex the tool surface, but installation and behavior remain operator-configured: users must install the binary, register the MCP server, know which Wire tools to call, and remember pairing-consent and session-listener rules. + +Claude Code already packages the same capabilities as a plugin. Codex users need equivalent product ergonomics without a second Wire implementation or a duplicated copy of the workflow assets. + +## Goals + +- Make Wire installable as a repo-distributed Codex plugin. +- Reuse the existing Rust binary, `.mcp.json`, and `skills/` directory. +- Make natural requests such as “dial grove-leaflet” and “send grove-leaflet …” route predictably to Wire MCP tools. +- Preserve per-session identity, bilateral pairing consent, and listener-lifetime rules. +- Keep Claude Code and Codex guidance on one shared workflow source where both harnesses support the same behavior. +- Provide deterministic validation and an end-user smoke path. + +## Non-goals + +- No new Wire protocol, relay, identity, or Rust behavior. +- No ChatGPT-hosted remote MCP app. +- No custom Codex status bar or unsupported hook emulation. +- No duplicate `plugins/wire` package tree. +- No automatic acceptance of inbound pair requests. +- No replacement for direct MCP configuration; MCP-only remains supported. + +## Approaches Considered + +### 1. Root-level dual-harness plugin — selected + +Add `.codex-plugin/plugin.json` beside `.claude-plugin/plugin.json`. Both manifests point to the existing `.mcp.json` and shared `skills/` directory. + +This keeps executable behavior in `wire mcp`, avoids copied skills, and lets each harness retain its own manifest. Codex-specific unsupported fields are omitted instead of forcing Claude hook semantics into Codex. + +### 2. Nested `plugins/wire` package + +Place a conventional Codex package below `plugins/wire`. Codex marketplace layout becomes straightforward, but `.mcp.json` and skills must be copied or linked into the archive. Copies drift; links are fragile across packaging and archive validation. Rejected. + +### 3. MCP-only documentation + +Document `codex mcp add wire -- wire mcp` and stop. This preserves capability but leaves discovery, behavioral guidance, and product distribution unresolved. Retained as fallback, rejected as primary Codex UX. + +## Package Architecture + +### Codex manifest + +Create `.codex-plugin/plugin.json` at the repository root with: + +- stable plugin name `wire`; +- version matching the released Wire crate version; +- product metadata for the Codex install surface; +- `skills` pointing to `./skills/`; +- `mcpServers` pointing to `./.mcp.json`; +- no `hooks` field while current Codex plugin validation rejects it; +- no app connector because Wire is a local stdio MCP server, not a hosted ChatGPT app. + +The existing Claude manifest remains separate because its accepted schema and lifecycle hook support differ. + +### Marketplace + +Create `.agents/plugins/marketplace.json` as the repository marketplace. Its `wire` entry uses a Git-backed root-repository source for `https://github.com/SlanchaAi/wire`, with explicit installation policy, authentication policy, and category. + +The marketplace is distribution metadata only. Wire continues to require the separately installed `wire` binary on `PATH`. + +### Shared skills + +Keep one `skills/` tree. Change only harness-specific prose that blocks Codex use: + +- describe MCP tool intent before harness-specific command spelling; +- treat Claude’s persistent Monitor as one available listener implementation, not the universal mechanism; +- state that `wire mcp` provides the session listener/poll loop for Codex; +- retain operator consent before `wire_accept`; +- retain session-lifetime listener semantics; +- avoid claiming Codex supports Claude slash-command namespaces. + +Claude-specific capabilities may remain as clearly labeled branches inside a shared skill when useful. Shared protocol and safety instructions must not fork. + +### Documentation + +Add Codex installation and smoke instructions without rewriting the Claude plugin guide. Document: + +1. install `slancha-wire`; +2. add the Wire marketplace and plugin; +3. start a new Codex task so bundled MCP tools and skills load; +4. verify identity with `wire_whoami`; +5. dial or send to a real persona; +6. verify inbound pairing still requires operator approval. + +Document MCP-only setup as the lean alternative for users who do not need bundled skills or marketplace discovery. + +## Runtime Flow + +1. Codex loads the installed plugin in a new task. +2. Plugin launches `wire mcp` from `.mcp.json`. +3. Wire resolves session identity through its existing session-key precedence, including `CODEX_SESSION_ID` where available. +4. MCP initialization advertises Wire tools and server instructions. +5. Bundled skills teach Codex the canonical dial, pending, accept/reject, send, and tail workflows. +6. User language maps to MCP calls; the Rust server remains the sole implementation of identity and transport behavior. + +No plugin hook mutates trust state. No skill auto-accepts pairing. + +## Failure Handling + +- Missing `wire` binary: MCP startup must fail visibly; docs point to Cargo or release-binary installation. +- Plugin installed but tools absent: user starts a new task, then checks plugin enabled state and `codex mcp list`/plugin status. +- Duplicate direct MCP plus plugin MCP registration: docs tell users to keep one enabled source to avoid confusing duplicate tool catalogs. +- Missing session key: existing Wire fallback behavior applies; diagnostics use `wire_whoami`/`wire_status` rather than inventing plugin state. +- Offline relay: local identity and local-only workflows remain available; federation errors surface verbatim. + +## Security Invariants + +- `wire_accept` always requires operator consent for inbound peers. +- Plugin installation does not create trust relationships. +- Signing keys remain owned and read by the local Wire binary. +- Marketplace authentication policy describes installation behavior; it does not replace Wire’s cryptographic pairing. +- Shared skills must not weaken existing consent or identity instructions. + +## Verification + +Automated checks must cover: + +- Codex manifest validation with the bundled `plugin-creator` validator; +- JSON validity for both manifests, MCP config, and marketplace metadata; +- all manifest-referenced paths exist and remain inside the plugin archive; +- shared skill frontmatter validates; +- existing documentation/tool-catalog consistency tests remain green; +- no Rust symbol or execution-flow changes appear in GitNexus change detection. + +Product smoke must cover a clean local Codex install: + +- marketplace discovers `wire`; +- plugin installs and enables; +- a new task exposes Wire tools; +- `wire_whoami` returns a session identity; +- a dial/send request selects Wire tooling; +- inbound pairing remains pending until explicit approval. + +## Success Criteria + +- A fresh Codex user can install Wire from repository marketplace metadata without manually editing `config.toml`. +- Codex loads `wire mcp` and the shared Wire skills from the plugin. +- Natural-language dial/send requests use the canonical Wire tool flow. +- Claude plugin behavior remains intact. +- MCP-only users remain supported. +- Plugin and marketplace validators pass from a clean checkout. + From ba3704160415a1b378c4fdfe2c6ea3ab9a64ba97 Mon Sep 17 00:00:00 2001 From: Paul Logan Date: Wed, 15 Jul 2026 00:11:27 -0700 Subject: [PATCH 2/7] docs: plan Codex plugin implementation --- SESSION_LOG_2026_07_14.md | 3 +- .../plans/2026-07-14-codex-plugin.md | 653 ++++++++++++++++++ 2 files changed, 655 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/plans/2026-07-14-codex-plugin.md diff --git a/SESSION_LOG_2026_07_14.md b/SESSION_LOG_2026_07_14.md index 3b3fc30..f11c53e 100644 --- a/SESSION_LOG_2026_07_14.md +++ b/SESSION_LOG_2026_07_14.md @@ -23,9 +23,10 @@ Design: `docs/superpowers/specs/2026-07-14-codex-plugin-design.md`. ### Current phase -Design approved conversationally. Written spec awaiting user review before implementation planning. +Written spec approved. Implementation plan created; execution approach awaiting user choice. ## Artifacts - `docs/superpowers/specs/2026-07-14-codex-plugin-design.md` — product design, invariants, packaging, and verification criteria. +- `docs/superpowers/plans/2026-07-14-codex-plugin.md` — test-first implementation and verification plan. - `SESSION_LOG_2026_07_14.md` — session decisions and artifact catalog. diff --git a/docs/superpowers/plans/2026-07-14-codex-plugin.md b/docs/superpowers/plans/2026-07-14-codex-plugin.md new file mode 100644 index 0000000..17d7190 --- /dev/null +++ b/docs/superpowers/plans/2026-07-14-codex-plugin.md @@ -0,0 +1,653 @@ +# Codex Wire Plugin Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Package Wire's existing MCP server and shared workflows as a repository-distributed Codex plugin without duplicating runtime behavior or weakening pairing consent. + +**Architecture:** Add a Codex manifest beside the Claude manifest and expose the repository root through a Codex marketplace entry. Both harnesses reuse `.mcp.json` and `skills/`; Rust integration tests guard manifest versioning, shared paths, marketplace policy, removed-command drift, and install documentation. + +**Tech Stack:** Codex plugin JSON, MCP stdio configuration, Markdown skills, Rust integration tests with `serde_json`, Codex CLI, GitNexus CLI. + +## Global Constraints + +- Plugin version is exactly `0.17.0`, matching `Cargo.toml`. +- Rust runtime, protocol, identity, relay, and MCP tool implementations remain unchanged. +- Codex manifest lives at `.codex-plugin/plugin.json`; Claude manifest stays at `.claude-plugin/plugin.json`. +- Both manifests reuse `.mcp.json` and `skills/`; no copied `plugins/wire` tree. +- Codex manifest omits `hooks`; Claude manifest keeps its SessionStart hook. +- Marketplace installation does not install the `wire` binary. +- Inbound pair requests never auto-accept. +- Removed SPAKE2/SAS commands must not appear as executable guidance. +- Preserve all unrelated working-tree changes. + +--- + +### Task 1: Package contract and manifests + +**Files:** +- Create: `tests/plugin_contract.rs` +- Create: `.codex-plugin/plugin.json` +- Create: `.agents/plugins/marketplace.json` +- Modify: `.claude-plugin/plugin.json` + +**Interfaces:** +- Consumes: `CARGO_PKG_VERSION`, `./skills/`, and `./.mcp.json`. +- Produces: root Codex plugin `wire`, repository marketplace `wire`, and static package-contract tests. + +- [ ] **Step 1: Write failing package-contract tests** + +Create `tests/plugin_contract.rs`: + +```rust +use serde_json::{Value, json}; +use std::fs; +use std::path::{Path, PathBuf}; + +fn repo_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) +} + +fn read_json(relative: &str) -> Value { + let path = repo_root().join(relative); + let body = fs::read_to_string(&path) + .unwrap_or_else(|error| panic!("read {}: {error}", path.display())); + serde_json::from_str(&body) + .unwrap_or_else(|error| panic!("parse {}: {error}", path.display())) +} + +fn assert_shared_components(manifest: &Value) { + assert_eq!(manifest["name"], "wire"); + assert_eq!(manifest["version"], env!("CARGO_PKG_VERSION")); + assert_eq!(manifest["skills"], "./skills/"); + assert_eq!(manifest["mcpServers"], "./.mcp.json"); + for field in ["skills", "mcpServers"] { + let relative = manifest[field].as_str().expect("component path string"); + assert!(repo_root().join(relative).exists(), "missing {relative}"); + } +} + +#[test] +fn plugin_manifests_share_components_and_track_release() { + let claude = read_json(".claude-plugin/plugin.json"); + let codex = read_json(".codex-plugin/plugin.json"); + assert_shared_components(&claude); + assert_shared_components(&codex); + assert!(claude.get("hooks").is_some()); + assert!(codex.get("hooks").is_none()); + let mcp = read_json(".mcp.json"); + assert_eq!(mcp["mcpServers"]["wire"]["command"], "wire"); + assert_eq!(mcp["mcpServers"]["wire"]["args"], json!(["mcp"])); +} + +#[test] +fn repository_marketplace_exposes_root_wire_plugin() { + let marketplace = read_json(".agents/plugins/marketplace.json"); + assert_eq!(marketplace["name"], "wire"); + let plugins = marketplace["plugins"].as_array().expect("plugins array"); + assert_eq!(plugins.len(), 1); + let wire = &plugins[0]; + assert_eq!(wire["name"], "wire"); + assert_eq!(wire["source"], json!({"source": "local", "path": "./"})); + assert_eq!(wire["policy"]["installation"], "AVAILABLE"); + assert_eq!(wire["policy"]["authentication"], "ON_INSTALL"); + assert_eq!(wire["category"], "Developer Tools"); + assert!(Path::new(env!("CARGO_MANIFEST_DIR")) + .join(".codex-plugin/plugin.json") + .is_file()); +} +``` + +- [ ] **Step 2: Confirm RED** + +Run: `cargo test --test plugin_contract` + +Expected: FAIL because Codex manifest and marketplace are absent; Claude manifest version is stale. + +- [ ] **Step 3: Create Codex manifest** + +Create `.codex-plugin/plugin.json`: + +```json +{ + "name": "wire", + "version": "0.17.0", + "description": "Bilateral signed-message bus for local and federated AI-agent sessions.", + "author": { + "name": "Slancha AI", + "email": "paul@slancha.ai", + "url": "https://slancha.ai" + }, + "homepage": "https://wireup.net", + "repository": "https://github.com/SlanchaAi/wire", + "license": "AGPL-3.0-or-later AND Apache-2.0 AND MIT", + "keywords": ["agent", "p2p", "ed25519", "mailbox", "mcp", "identity"], + "skills": "./skills/", + "mcpServers": "./.mcp.json", + "interface": { + "displayName": "Wire", + "shortDescription": "Secure agent-to-agent messaging", + "longDescription": "Discover, pair, and exchange signed messages with local or federated AI-agent sessions through Wire.", + "developerName": "Slancha AI", + "category": "Developer Tools", + "capabilities": ["Read", "Write"], + "websiteURL": "https://wireup.net", + "defaultPrompt": [ + "Show my Wire identity and nearby agents.", + "Dial another agent over Wire.", + "Send a message to a paired Wire peer." + ] + } +} +``` + +- [ ] **Step 4: Create repository marketplace** + +Create `.agents/plugins/marketplace.json`: + +```json +{ + "name": "wire", + "interface": {"displayName": "Wire"}, + "plugins": [ + { + "name": "wire", + "source": {"source": "local", "path": "./"}, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Developer Tools" + } + ] +} +``` + +- [ ] **Step 5: Bring Claude manifest into release lockstep** + +Change `.claude-plugin/plugin.json` fields: + +```json +"version": "0.17.0", +"description": "Magic-wormhole for AI agents — bilateral signed-message bus over local and federated mailbox relays. Rust-native MCP server with Ed25519-rooted identity. Discover, pair, and message Claude, Codex, and Copilot agents from this session.", +``` + +Keep its `hooks` field unchanged. + +- [ ] **Step 6: Confirm GREEN** + +Run: `cargo test --test plugin_contract` + +Expected: `2 passed; 0 failed`. + +- [ ] **Step 7: Scope-check and commit** + +```bash +git add tests/plugin_contract.rs .codex-plugin/plugin.json .agents/plugins/marketplace.json .claude-plugin/plugin.json +npx gitnexus detect-changes --scope staged --repo wire +git diff --cached --check +git commit -m "feat: package wire for Codex" +``` + +Expected: packaging/test files only; no Rust execution flow affected. + +--- + +### Task 2: Modernize shared skills for Claude and Codex + +**Files:** +- Modify: `tests/plugin_contract.rs` +- Modify: `skills/wire-enroll/SKILL.md` +- Modify: `skills/wire-init/SKILL.md` +- Modify: `skills/wire-monitor/SKILL.md` +- Modify: `skills/wire-pair/SKILL.md` +- Modify: `skills/wire-quiet/SKILL.md` +- Modify: `skills/wire-send/SKILL.md` + +**Interfaces:** +- Consumes: v0.17 CLI/MCP surface and Task 1 manifests. +- Produces: one validator-compatible, harness-neutral skills tree. + +- [ ] **Step 1: Add failing removed-command audit** + +Append to `tests/plugin_contract.rs`: + +```rust +#[test] +fn bundled_skill_command_audit_rejects_removed_pairing_surface() { + let removed = [ + "wire pair-list-pending", + "wire pair-confirm", + "wire init ", + "/wire:wire-", + ]; + for entry in fs::read_dir(repo_root().join("skills")).expect("read skills") { + let skill_path = entry.expect("skill entry").path().join("SKILL.md"); + if !skill_path.is_file() { + continue; + } + let body = fs::read_to_string(&skill_path).expect("read skill"); + for signature in removed { + assert!(!body.contains(signature), "{} advertises {signature}", skill_path.display()); + } + } +} +``` + +This is an exact syntax audit, not a semantic-output detector. + +- [ ] **Step 2: Confirm RED** + +Run: `cargo test --test plugin_contract bundled_skill_command_audit_rejects_removed_pairing_surface` + +Expected: FAIL on removed SAS/init commands and Claude-only slash-command references. + +- [ ] **Step 3: Add valid skill names** + +Add `name: ` to each skill frontmatter. Exact names: + +```yaml +name: wire-enroll +name: wire-init +name: wire-monitor +name: wire-pair +name: wire-quiet +name: wire-send +``` + +Each file receives only its matching line. + +- [ ] **Step 4: Rewrite initialization workflow** + +In `skills/wire-init/SKILL.md`, replace manual `wire init ` flows with: + +````markdown +## Workflow + +### Public relay + +```bash +wire up @wireup.net +``` + +This mints the session identity, binds federation, claims the DID-derived persona, opportunistically adds local routing, and starts the daemon. Operator never chooses a separate handle. + +### Offline identity + +```bash +wire up --offline +``` + +Bind a relay later with `wire bind-relay `. + +### Custom relay + +```bash +wire up https://relay.example.com +``` + +## Verify + +Prefer `wire_whoami` and `wire_status` through MCP. CLI equivalent: + +```bash +wire whoami --json +wire status --json +``` + +Verify DID-derived `persona`, `config_dir`, endpoints, daemon health, and `identity_split: null`. + +## Common errors + +- `wire` not found — install binary, then start a new agent task. +- relay unreachable — use offline mode or a reachable local/custom relay. +- `identity_split` non-null — restart stale MCP host. + +## Organization identity + +Use the `wire-enroll` skill. +```` + +Set the frontmatter description to: + +```yaml +description: Bootstrap Wire with the canonical wire up flow, creating a per-session DID-derived identity and optionally binding local or federation relays. Use when the user says "wire init", "wire up", "set up wire", or asks how to start using Wire. +``` + +- [ ] **Step 5: Rewrite pairing workflow** + +Replace `skills/wire-pair/SKILL.md` body with: + +````markdown +# wire-pair + +Connect to another Wire agent. Canonical path is `wire_dial`; receiver must explicitly accept or dial back before bilateral connection completes. + +## Naming + +- Bare persona such as `coral-weasel` for a local sister or known peer. +- Federation address such as `coral-weasel@wireup.net` for cross-system discovery. + +Never invent a persona. Obtain it from operator, `wire_peers`, or `wire_here`. + +## Outbound + +1. Call `wire_dial` with real persona/address. +2. Surface pairing state. +3. Wait for peer acceptance or dial-back when pending. +4. Call `wire_send` after connection permits delivery unless operator explicitly requests queueing. + +CLI equivalent: + +```bash +wire dial +wire pending +wire send "hello" +``` + +## Inbound consent + +1. Call `wire_pending` and surface requests. +2. Ask operator to choose accept or reject. +3. Call `wire_accept` or `wire_reject` only after that choice. + +**Never auto-accept.** Acceptance grants authenticated inbox write access. + +```bash +wire pending +wire accept +# or +wire reject +``` + +## Organization easing + +Explicit receiver org policy may auto-pair a verified same-org peer at `ORG_VERIFIED`. Plugin installation creates no such policy and weakens no default consent gate. +```` + +Set frontmatter to: + +```yaml +--- +name: wire-pair +description: Connect this Wire session to another agent through bilateral dial and explicit inbound consent. Use when the user says "pair with X", "dial Y", "talk to Z", or names another peer they want to message. +--- +``` + +Remove SAS commands, old tier ladder, and slash-command references. + +- [ ] **Step 6: Rewrite listener workflow** + +Replace `skills/wire-monitor/SKILL.md` with: + +````markdown +--- +name: wire-monitor +description: Keep a Wire session synchronized and surface peer messages using the listener mechanism available in the current agent host. Use at session start, when the user asks to watch Wire, or during active peer collaboration. +--- + +# wire-monitor + +Wire daemon and MCP server keep relay inboxes synchronized. How messages enter model context depends on host. + +## Codex and generic MCP hosts + +1. Call `wire_status` at session start. +2. Confirm `daemon_running: true`, recent sync, and `identity_split: null`. +3. Call `wire_pull` for an immediate relay fetch. +4. Call `wire_tail` at collaboration checkpoints or on operator request. + +MCP does not inject unsolicited tool results into an active model turn. Wire can receive and notify in background, but Codex calls `wire_tail` to ingest messages into task context. + +## Hosts with persistent command monitors + +Arm once for session lifetime: + +```bash +wire monitor --json --include-handshake +``` + +Filter heartbeat/handshake noise in host monitor layer. Keep listener across loop iterations; stop only when session ends or operator says stop everything. + +## Inbound requests + +Call `wire_pending`, surface requests, and wait for operator consent. Never auto-accept. + +## Multiple sessions + +Each session has its own identity, MCP process, daemon state, and inbox cursor. Check or monitor each independently. +```` + +- [ ] **Step 7: Remove remaining harness-specific wording** + +Apply these exact text replacements: + +```text +skills/wire-quiet/SKILL.md +"every Claude tab's daemon" -> "every local agent session's daemon" +"Memory: `feedback_wire_upgrade_skips_mcp_servers` — sister Claude sessions' wire mcp subprocesses need `/mcp` reconnect to pick up the silenced binary if `wire upgrade` ran." -> "After `wire upgrade`, restart or reconnect each host-pinned `wire mcp` subprocess so it uses the new binary." + +skills/wire-send/SKILL.md +"`mcp__wire__wire_send({peer: \"\", body: \"\"})` from the assistant context. Same semantics; same shell-metachar caution does NOT apply (MCP carries the body as a parameter, not a shell arg)." -> "Call `wire_send` with `peer` and `body`. MCP carries the body as a parameter, so shell-metacharacter escaping does not apply." +``` + +Add `name` only to `wire-enroll`; its workflow is already harness-neutral. + +- [ ] **Step 8: Confirm GREEN and validate manifest/skills** + +```bash +cargo test --test plugin_contract +python3 /Users/laul_pogan/.codex/skills/.system/plugin-creator/scripts/validate_plugin.py . +``` + +Expected: `3 passed; 0 failed`; `Plugin validation passed`. + +- [ ] **Step 9: Scope-check and commit** + +```bash +git add tests/plugin_contract.rs skills/*/SKILL.md +npx gitnexus detect-changes --scope staged --repo wire +git diff --cached --check +git commit -m "docs: make wire skills work across agent hosts" +``` + +--- + +### Task 3: Codex install documentation + +**Files:** +- Modify: `tests/plugin_contract.rs` +- Modify: `README.md` +- Modify: `docs/PLUGIN.md` +- Modify: `SESSION_LOG_2026_07_14.md` + +**Interfaces:** +- Consumes: marketplace/plugin name `wire` from Task 1. +- Produces: copyable plugin install, MCP-only fallback, duplicate-registration warning, and smoke workflow. + +- [ ] **Step 1: Add failing install-signature audit** + +Append to `tests/plugin_contract.rs`: + +```rust +#[test] +fn codex_install_signatures_are_documented() { + let signatures = [ + "codex plugin marketplace add SlanchaAi/wire", + "codex plugin add wire@wire", + "codex mcp add wire -- wire mcp", + ]; + for relative in ["README.md", "docs/PLUGIN.md"] { + let body = fs::read_to_string(repo_root().join(relative)).expect("read docs"); + for signature in signatures { + assert!(body.contains(signature), "{relative} missing `{signature}`"); + } + } +} +``` + +This audits exact copyable command syntax. + +- [ ] **Step 2: Confirm RED** + +Run: `cargo test --test plugin_contract codex_install_signatures_are_documented` + +Expected: FAIL because both documents lack Codex commands. + +- [ ] **Step 3: Update README harness table and fallback** + +Add row after Claude Code: + +```markdown +| **Codex App / CLI / IDE** | `cargo install slancha-wire`, `codex plugin marketplace add SlanchaAi/wire`, then `codex plugin add wire@wire` | Start a new task; ask “Show my Wire identity” | +``` + +Add below table: + +````markdown +Codex users who want only the tool surface can skip plugin packaging: + +```bash +codex mcp add wire -- wire mcp +``` + +Plugin adds shared Wire skills and marketplace discovery; both routes run the same local `wire mcp`. Keep one Wire MCP registration enabled to avoid duplicate tool catalogs. +```` + +- [ ] **Step 4: Add Codex section to plugin guide** + +Change title to `# Wire agent plugins`. Keep Claude instructions, then add: + +````markdown +## Codex plugin + +Codex manifest lives at `.codex-plugin/plugin.json` and reuses `.mcp.json` plus `skills/`. Binary remains separate: + +```bash +cargo install slancha-wire +codex plugin marketplace add SlanchaAi/wire +codex plugin add wire@wire +``` + +Start a new task. Ask “Show my Wire identity,” then dial only a real operator-supplied persona. Inbound requests remain pending until explicit accept/reject. + +### MCP-only Codex setup + +```bash +codex mcp add wire -- wire mcp +``` + +MCP-only gives same tools without bundled skills/marketplace UX. Do not enable direct `mcp_servers.wire` and plugin-provided Wire MCP together unless testing duplicates. + +### Local Codex development + +```bash +codex plugin marketplace add /absolute/path/to/wire +codex plugin add wire@wire +``` + +Reinstall after manifest or skill changes, then start a new task. +```` + +Make later Claude-only headings explicit; retain hook instructions. + +- [ ] **Step 5: Confirm GREEN** + +```bash +cargo test --test plugin_contract +cargo test agent_docs_match_advertised_tools +``` + +Expected: four contract tests and MCP documentation consistency pass. + +- [ ] **Step 6: Update session log** + +Append to `SESSION_LOG_2026_07_14.md`: + +```markdown +### Implementation findings + +- Codex packaging reuses the repository root through marketplace source `./`. +- Codex skill validation requires explicit `name` frontmatter. +- Shared skills advertised removed SAS/init commands; corrected before Codex distribution. +- `wire mcp` keeps data synchronized, while Codex ingests messages through `wire_pull` and `wire_tail` at task checkpoints. + +### Implementation artifacts + +- `.codex-plugin/plugin.json` — Codex plugin manifest. +- `.agents/plugins/marketplace.json` — repository marketplace entry. +- `tests/plugin_contract.rs` — packaging and documentation contract tests. +- `skills/*/SKILL.md` — shared Claude/Codex workflows. +- `README.md` and `docs/PLUGIN.md` — install and operation guidance. +``` + +- [ ] **Step 7: Scope-check and commit** + +```bash +git add tests/plugin_contract.rs README.md docs/PLUGIN.md SESSION_LOG_2026_07_14.md +npx gitnexus detect-changes --scope staged --repo wire +git diff --cached --check +git commit -m "docs: add Codex plugin installation" +``` + +--- + +### Task 4: Isolated install smoke and final verification + +**Files:** +- Modify only when a verification failure identifies a defect in Tasks 1–3. + +**Interfaces:** +- Consumes: complete plugin package and docs. +- Produces: local evidence that Codex discovers and installs the root plugin without touching normal user config. + +- [ ] **Step 1: Run focused verification** + +```bash +cargo fmt --check +cargo test --test plugin_contract +python3 /Users/laul_pogan/.codex/skills/.system/plugin-creator/scripts/validate_plugin.py . +cargo test agent_docs_match_advertised_tools +``` + +Expected: every command exits zero. + +- [ ] **Step 2: Smoke marketplace/install in isolated Codex home** + +```bash +SMOKE_HOME="$(mktemp -d)" +CODEX_HOME="$SMOKE_HOME" codex plugin marketplace add "$PWD" --json +CODEX_HOME="$SMOKE_HOME" codex plugin list --available --json +CODEX_HOME="$SMOKE_HOME" codex plugin add wire@wire --json +CODEX_HOME="$SMOKE_HOME" codex plugin list --json +``` + +Expected: marketplace `wire` added; Wire appears available; install succeeds; final list reports `wire@wire`. Isolated home leaves `~/.codex/config.toml` unchanged. + +If Codex rejects source path `./`, correct only marketplace source semantics and its test. Do not create duplicated plugin assets. + +- [ ] **Step 3: Run full Rust suite** + +Run: `cargo test` + +Expected: zero failures. + +- [ ] **Step 4: Run final scope checks** + +```bash +npx gitnexus detect-changes --scope all --repo wire +git diff --check +git status --short +git log -4 --oneline +``` + +Expected: no Rust execution-flow changes; only intended feature files changed; unrelated dirty files untouched; design plus three implementation commits at tip. + +- [ ] **Step 5: New-task product check** + +After normal Codex install, start a new task and enter: + +```text +Show my Wire identity and status. +``` + +Expected: Codex selects `wire_whoami` and `wire_status`. Dial only a real operator-supplied persona. Never fabricate peer or accept inbound request without explicit consent. From f858e6c41be672cbd6051499200a77715208cd38 Mon Sep 17 00:00:00 2001 From: Paul Logan Date: Wed, 15 Jul 2026 10:08:26 -0700 Subject: [PATCH 3/7] chore: ignore local worktrees --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 95bd96e..ed67c4e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ # Local state *.local.md .claude/ +.worktrees/ # Secrets / keys (defensive — operator keys live in $HOME/.config/wire/, never the repo) *.key From 2d07c2d54739fc0b48e4303135315f9e39474ca1 Mon Sep 17 00:00:00 2001 From: Paul Logan Date: Wed, 15 Jul 2026 10:17:17 -0700 Subject: [PATCH 4/7] feat: package wire for Codex --- .agents/plugins/marketplace.json | 20 +++++++++++ .claude-plugin/plugin.json | 4 +-- .codex-plugin/plugin.json | 40 +++++++++++++++++++++ tests/plugin_contract.rs | 61 ++++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 .agents/plugins/marketplace.json create mode 100644 .codex-plugin/plugin.json create mode 100644 tests/plugin_contract.rs diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 0000000..7072c20 --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "wire", + "interface": { + "displayName": "Wire" + }, + "plugins": [ + { + "name": "wire", + "source": { + "source": "local", + "path": "./" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Developer Tools" + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 8a0f5f8..53ea28f 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,8 +1,8 @@ { "name": "wire", "displayName": "Wire", - "version": "0.15.0", - "description": "Magic-wormhole for AI agents — bilateral signed-message bus over a mailbox relay. Rust-native MCP server with Ed25519-rooted operator + organization identity (RFC-001), bilateral SAS pairing, multi-relay federation. Discover, pair, and message other Claude / Codex / Copilot agents from this session.", + "version": "0.17.0", + "description": "Magic-wormhole for AI agents — bilateral signed-message bus over local and federated mailbox relays. Rust-native MCP server with Ed25519-rooted identity. Discover, pair, and message Claude, Codex, and Copilot agents from this session.", "author": { "name": "Paul Logan", "email": "paul@slancha.ai" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json new file mode 100644 index 0000000..1d3e2fe --- /dev/null +++ b/.codex-plugin/plugin.json @@ -0,0 +1,40 @@ +{ + "name": "wire", + "version": "0.17.0", + "description": "Bilateral signed-message bus for local and federated AI-agent sessions.", + "author": { + "name": "Slancha AI", + "email": "paul@slancha.ai", + "url": "https://slancha.ai" + }, + "homepage": "https://wireup.net", + "repository": "https://github.com/SlanchaAi/wire", + "license": "AGPL-3.0-or-later AND Apache-2.0 AND MIT", + "keywords": [ + "agent", + "p2p", + "ed25519", + "mailbox", + "mcp", + "identity" + ], + "skills": "./skills/", + "mcpServers": "./.mcp.json", + "interface": { + "displayName": "Wire", + "shortDescription": "Secure agent-to-agent messaging", + "longDescription": "Discover, pair, and exchange signed messages with local or federated AI-agent sessions through Wire.", + "developerName": "Slancha AI", + "category": "Developer Tools", + "capabilities": [ + "Read", + "Write" + ], + "websiteURL": "https://wireup.net", + "defaultPrompt": [ + "Show my Wire identity and nearby agents.", + "Dial another agent over Wire.", + "Send a message to a paired Wire peer." + ] + } +} diff --git a/tests/plugin_contract.rs b/tests/plugin_contract.rs new file mode 100644 index 0000000..9d15b0f --- /dev/null +++ b/tests/plugin_contract.rs @@ -0,0 +1,61 @@ +use serde_json::{Value, json}; +use std::fs; +use std::path::{Path, PathBuf}; + +fn repo_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) +} + +fn read_json(relative: &str) -> Value { + let path = repo_root().join(relative); + let body = fs::read_to_string(&path) + .unwrap_or_else(|error| panic!("read {}: {error}", path.display())); + serde_json::from_str(&body) + .unwrap_or_else(|error| panic!("parse {}: {error}", path.display())) +} + +fn assert_shared_components(manifest: &Value) { + assert_eq!(manifest["name"], "wire"); + assert_eq!(manifest["version"], env!("CARGO_PKG_VERSION")); + assert_eq!(manifest["skills"], "./skills/"); + assert_eq!(manifest["mcpServers"], "./.mcp.json"); + for field in ["skills", "mcpServers"] { + let relative = manifest[field].as_str().expect("component path string"); + assert!(repo_root().join(relative).exists(), "missing {relative}"); + } +} + +#[test] +fn plugin_manifests_share_components_and_track_release() { + let claude = read_json(".claude-plugin/plugin.json"); + let codex = read_json(".codex-plugin/plugin.json"); + assert_shared_components(&claude); + assert_shared_components(&codex); + assert!(claude.get("hooks").is_some()); + assert!(codex.get("hooks").is_none()); + let mcp = read_json(".mcp.json"); + assert_eq!(mcp["mcpServers"]["wire"]["command"], "wire"); + assert_eq!(mcp["mcpServers"]["wire"]["args"], json!(["mcp"])); +} + +#[test] +fn repository_marketplace_exposes_root_wire_plugin() { + let marketplace = read_json(".agents/plugins/marketplace.json"); + assert_eq!(marketplace["name"], "wire"); + let plugins = marketplace["plugins"].as_array().expect("plugins array"); + assert_eq!(plugins.len(), 1); + let wire = &plugins[0]; + assert_eq!(wire["name"], "wire"); + assert_eq!( + wire["source"], + json!({"source": "local", "path": "./"}) + ); + assert_eq!(wire["policy"]["installation"], "AVAILABLE"); + assert_eq!(wire["policy"]["authentication"], "ON_INSTALL"); + assert_eq!(wire["category"], "Developer Tools"); + assert!( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join(".codex-plugin/plugin.json") + .is_file() + ); +} From cecbf753a62498189d4a0d1dfafcdfa5bb288e1c Mon Sep 17 00:00:00 2001 From: Paul Logan Date: Wed, 15 Jul 2026 10:19:02 -0700 Subject: [PATCH 5/7] docs: make wire skills work across agent hosts --- skills/wire-enroll/SKILL.md | 1 + skills/wire-init/SKILL.md | 57 +++++++++++++------------ skills/wire-monitor/SKILL.md | 56 +++++++++--------------- skills/wire-pair/SKILL.md | 83 +++++++++++++----------------------- skills/wire-quiet/SKILL.md | 5 ++- skills/wire-send/SKILL.md | 3 +- tests/plugin_contract.rs | 24 +++++++++++ 7 files changed, 109 insertions(+), 120 deletions(-) diff --git a/skills/wire-enroll/SKILL.md b/skills/wire-enroll/SKILL.md index 02d0a0a..9eccb4f 100644 --- a/skills/wire-enroll/SKILL.md +++ b/skills/wire-enroll/SKILL.md @@ -1,4 +1,5 @@ --- +name: wire-enroll description: Enroll operator + organization identity per RFC-001 (the v0.14 identity layer). Use when the user wants to mint an op_did (long-lived operator anchor), create an org_did, issue/import member_certs, or republish their card with current enrollment. Personal-tier operators always carry a signing-key op_did regardless of SSO opt-in — wire-rooted identity is the security anchor. --- diff --git a/skills/wire-init/SKILL.md b/skills/wire-init/SKILL.md index d93f155..ff1e61a 100644 --- a/skills/wire-init/SKILL.md +++ b/skills/wire-init/SKILL.md @@ -1,73 +1,74 @@ --- -description: Initialize wire on this machine — mint a session DID + Ed25519 keypair and (optionally) bind a public relay. Use when the user says "wire init", "set up wire", or asks how to start using wire. Wire is a magic-wormhole-for-agents bus; the init step writes ~/.config/wire/ (Unix) or %APPDATA%/wire/ (Windows) and is required exactly once per session. +name: wire-init +description: Bootstrap Wire with the canonical wire up flow, creating a per-session DID-derived identity and optionally binding local or federation relays. Use when the user says "wire init", "wire up", "set up wire", or asks how to start using Wire. --- # wire-init -Set up wire identity on a fresh machine or fresh per-session WIRE_HOME. Idempotent — running twice is safe. +Set up Wire on a fresh machine or in a fresh per-session home. `wire up` is idempotent and owns identity creation, relay binding, persona claim, and daemon startup. ## When to use -- User says "set up wire", "wire init", "wire up", "install wire" -- User wants to start using wire but their session has no identity (`wire whoami` errors with "not initialized") -- A fresh per-session WIRE_HOME (e.g. new Claude Code session inheriting a clean state) +- User says "set up wire", "wire init", "wire up", or "install wire". +- `wire_whoami` reports that no session identity exists. +- A new agent session needs its own DID-derived persona. ## Pre-flight -Verify wire is on PATH: +Verify the binary is on `PATH`: ```bash -command -v wire || echo "wire not installed — see install instructions below" +command -v wire ``` -If not installed: `cargo install slancha-wire` (requires Rust toolchain) OR download a prebuilt binary from https://github.com/SlanchaAi/wire/releases. +If absent, install `slancha-wire` with Cargo or use a prebuilt release from https://github.com/SlanchaAi/wire/releases. ## Workflow -### Option A — public-relay default (`wireup.net`, recommended for first install) +### Public relay ```bash -wire up +wire up @wireup.net ``` -This single verb: mints a session DID + Ed25519 keypair, claims a persona on `wireup.net`, registers a federation handle. Output shows the DID + nickname + emoji. Per the v0.11 one-name rule, the claimed nickname always matches the DID-derived persona. +This mints the session identity, binds federation, claims the DID-derived persona, opportunistically adds local routing, and starts the daemon. Operator never chooses a separate handle. -### Option B — offline / local-only +### Offline identity ```bash -wire init --offline +wire up --offline ``` -No relay binding. Local-only identity. Use when the operator doesn't want to publish to `wireup.net`. +Bind a relay later with `wire bind-relay `. -### Option C — bind a custom relay (sovereign-fleet / org-tier) +### Custom relay ```bash -wire init -wire bind-relay https://relay. +wire up https://relay.example.com ``` -For org-tier deployments per RFC-003. The DNS-TXT `_wire-org.` should already be published before binding. - ## Verify +Prefer `wire_whoami` and `wire_status` through MCP. CLI equivalent: + ```bash -wire whoami --json | jq +wire whoami --json +wire status --json ``` -Expected fields: `did`, `handle`, `op_did` (if enrolled per RFC-001), `schema_version` (v3.2 if v0.14+ + op claims attached), `capabilities`. +Verify DID-derived `persona`, `config_dir`, endpoints, daemon health, and `identity_split: null`. ## Common errors -- **`not initialized`** — `wire whoami` was called before init. Run one of A/B/C above. -- **`relay unreachable`** — Option A failed; user is offline. Fall back to B (`--offline`). -- **`handle already claimed on relay`** — collision on `wireup.net`. Use Option B + bind to a non-public relay, OR pick a different handle. +- **`wire` not found** — install binary, then start a new agent task. +- **Relay unreachable** — use offline mode or a reachable local/custom relay. +- **`identity_split` non-null** — restart stale MCP host. -## v0.14 identity layer (optional, post-init) +## Organization identity -If the user wants the RFC-001 operator + org identity layer (auto-pair across sessions, ORG_VERIFIED tier), see the `/wire:wire-enroll` skill. +Use the `wire-enroll` skill for operator and organization enrollment. ## Reference -- README: https://github.com/SlanchaAi/wire#status--v0141-latest -- RFC-001 identity layer: `docs/rfc/0001-identity-layer.md` in the wire repo. +- README: https://github.com/SlanchaAi/wire#pick-your-harness +- Agent integration: `docs/AGENT_INTEGRATION.md`. diff --git a/skills/wire-monitor/SKILL.md b/skills/wire-monitor/SKILL.md index d74974e..c3ec611 100644 --- a/skills/wire-monitor/SKILL.md +++ b/skills/wire-monitor/SKILL.md @@ -1,54 +1,40 @@ --- -description: Arm a persistent live-watcher on the wire inbox so peer messages surface mid-session without polling. Use this on every wire-enabled session start — the watcher streams non-heartbeat events, the assistant reacts to peer messages in its own live context (paired-agent collaboration pattern), and operator notifications fire on lock-screen via os_notify. This is the canonical "wire is now active" arming verb. +name: wire-monitor +description: Keep a Wire session synchronized and surface peer messages using the listener mechanism available in the current agent host. Use at session start, when the user asks to watch Wire, or during active peer collaboration. --- # wire-monitor -Per the wire MCP server's session-start directive: arm a persistent stream-watcher on `wire monitor --json --include-handshake` filtered for heartbeat/pair_drop noise. Peer messages surface live; the assistant replies in its own context immediately, NOT waiting for the operator to prompt. +Wire daemon and MCP server keep relay inboxes synchronized. How messages enter model context depends on host. -## When to use +## Codex and generic MCP hosts -- **Every wire-enabled session start.** The wire MCP server's instruction set says: arm this immediately as an early action. The SessionStart hook in this plugin already prints a reminder. -- User says "monitor wire", "watch inbox", "arm wire" -- After `/wire:wire-pair` succeeds and the user wants live message flow +1. Call `wire_status` at session start. +2. Confirm `daemon_running: true`, recent sync, and `identity_split: null`. +3. Call `wire_pull` for an immediate relay fetch. +4. Call `wire_tail` at collaboration checkpoints or on operator request. -## Canonical arming form +MCP does not inject unsolicited tool results into an active model turn. Wire can receive and notify in background, but Codex calls `wire_tail` to ingest messages into task context. -In Claude Code, use the `Monitor` tool with `persistent: true`: +## Hosts with persistent command monitors -```text -Monitor tool with: - command: wire monitor --json --include-handshake 2>&1 | grep --line-buffered -vE '"kind":"(heartbeat|pair_drop|pair_drop_ack)"' - description: wire inbox live watcher - persistent: true - timeout_ms: 3600000 (max) -``` - -Filter strips three noise classes: -- `kind=heartbeat` — daemon liveness pings -- `kind=pair_drop` + `kind=pair_drop_ack` — bilateral pair-introduction events (RFC-001 §3) - -What remains: real peer messages (`kind=claim`, `kind=ack`, `kind=decision`, `kind=trust_*`) — the things worth surfacing to operator + the assistant. - -## When a peer message arrives +Arm once for session lifetime: -The task-notification arrives as a `` event in the assistant's context. **Reply in the assistant's own live context — do NOT wait for the operator to prompt.** This is how paired agents collaborate immediately. - -If the message asks a question requiring operator input (rare — paired agents typically discuss async), surface to the operator. - -## Inbound pair-request handling +```bash +wire monitor --json --include-handshake +``` -`wire pending` enumerates inbound pair requests. These do NOT auto-accept — surface to the operator for consent. Accepting grants the peer authenticated write access; see `/wire:wire-pair` skill for the consent + accept flow. +Filter heartbeat and handshake noise in host monitor layer. Keep listener across loop iterations; stop only when session ends or operator says stop everything. -## Multiple wire MCP servers / sister sessions +## Inbound requests -Each Claude Code tab has its own `wire mcp` subprocess; each session has its own daemon + per-session config_dir. The Monitor armed here is THIS session's. Sister sessions need their own Monitor + their own `/wire:wire-monitor` invocation. +Call `wire_pending`, surface requests, and wait for operator consent. Never auto-accept. -## MCP tool variant +## Multiple sessions -`mcp__wire__wire_tail` returns the last N inbox events synchronously — useful for one-shot checks. The Monitor pattern above is for the continuous-stream case. +Each session has its own identity, MCP process, daemon state, and inbox cursor. Check or monitor each independently. ## Reference -- Wire MCP server instructions (system prompt) — the "ARM A PERSISTENT MONITOR" directive. -- `docs/AGENT_INTEGRATION.md` in the wire repo (post-v0.13 doc). +- MCP server instructions returned by `wire mcp`. +- Agent integration: `docs/AGENT_INTEGRATION.md`. diff --git a/skills/wire-pair/SKILL.md b/skills/wire-pair/SKILL.md index 0f274ee..72b77f0 100644 --- a/skills/wire-pair/SKILL.md +++ b/skills/wire-pair/SKILL.md @@ -1,81 +1,56 @@ --- -description: Pair this wire session with another agent — bilateral signed-message bus over a mailbox relay. Use when the user says "pair with X", "dial Y", "talk to Z", or names another agent/peer they want to message. Wire pairing is bilateral (both sides accept) and produces a 6-digit SAS that operators read aloud out-of-band. +name: wire-pair +description: Connect this Wire session to another agent through bilateral dial and explicit inbound consent. Use when the user says "pair with X", "dial Y", "talk to Z", or names another peer they want to message. --- # wire-pair -Pair coral-weasel (this session) with another wire-running agent. Bilateral, signed, with optional out-of-band SAS verification for VERIFIED tier. +Connect to another Wire agent. Canonical path is `wire_dial`; receiver must explicitly accept or dial back before bilateral connection completes. -## When to use +## Naming -- User says "pair with ``", "dial ``", "wire ``" -- User wants to start a conversation with another agent identified by their wire handle -- An inbound pair request landed (see `wire pending`) and user wants to accept +- Bare persona such as `coral-weasel` for a local sister or known peer. +- Federation address such as `coral-weasel@wireup.net` for cross-system discovery. -## Naming forms +Never invent a persona. Obtain it from operator, `wire_peers`, or `wire_here`. -- **Bare nick** — `coral-weasel` (resolves to local pinned peer + local sister sessions per `cli::resolve_name_to_target`) -- **Federation handle** — `@` like `coral-weasel@wireup.net` (cross-relay lookup via `.well-known/wire/agent`) +## Outbound -## Workflow — outbound pair +1. Call `wire_dial` with real persona or federation address. +2. Surface pairing state. +3. Wait for peer acceptance or dial-back when pending. +4. Call `wire_send` after connection permits delivery unless operator explicitly requests queueing. -```bash -# 1. Dial the peer -wire dial +CLI equivalent: -# 2. Check pending state (peer's acceptance fires async) +```bash +wire dial wire pending - -# 3. Once peer accepts, send works directly -wire send "hello" +wire send "hello" ``` -### Bilateral SAS gesture (optional, for VERIFIED tier) +## Inbound consent -```bash -# Both sides see the same 6-digit code -wire pair-list-pending --json | jq '.[].sas' +1. Call `wire_pending` and surface requests. +2. Ask operator to choose accept or reject. +3. Call `wire_accept` or `wire_reject` only after that choice. -# Both operators confirm verbally -wire pair-confirm <6-digit> +**Never auto-accept.** Acceptance grants authenticated inbox write access. -# Tier upgrades UNTRUSTED → ATTESTED → VERIFIED -``` - -## Workflow — inbound pair (someone dialed us) +CLI equivalent: ```bash -# 1. Surface pending inbound wire pending - -# 2. Accept (after operator consent — NEVER auto-accept strangers) -wire accept - -# 3. Or reject -wire reject +wire accept +# or +wire reject ``` -**Critical:** When an inbound pair request from a STRANGER arrives, ALWAYS surface to the operator. Accepting grants the peer authenticated write access to this agent's inbox. NEVER auto-accept without explicit operator approval. - -## v0.14 ORG_VERIFIED auto-pair lane - -If both peers are in the same org (`org_memberships[]` in their cards verify against a shared `org_did`) AND the receiver's `org_policies.json` opts that org into `auto`, pair_drop auto-pins at ORG_VERIFIED — no SAS gesture needed. See RFC-001 §"Implementation status (as-built, v0.14)". - -## MCP tool parity - -Same actions via wire MCP tools when called from the assistant context: - -- `mcp__wire__wire_dial` -- `mcp__wire__wire_pending` -- `mcp__wire__wire_accept` -- `mcp__wire__wire_reject` -- `mcp__wire__wire_send` - -## Tier ladder +## Organization easing -`UNTRUSTED < ORG_VERIFIED < VERIFIED < ATTESTED < TRUSTED` — `tier_order` defines the strict order. Auto-pair via org_membership reaches ORG_VERIFIED only; SAS/gesture is the only path to VERIFIED. +Explicit receiver organization policy may auto-pair a verified same-org peer at `ORG_VERIFIED`. Plugin installation creates no such policy and weakens no default consent gate. ## Reference -- v0.14.1 README — pair flow walk-through. -- RFC-003 (`docs/rfc/0003-per-company-relays.md`) — federation handle resolution + cross-relay pairing. +- Agent integration: `docs/AGENT_INTEGRATION.md`. +- Federation: `docs/rfc/0003-per-company-relays.md`. diff --git a/skills/wire-quiet/SKILL.md b/skills/wire-quiet/SKILL.md index 1f63720..803a7b4 100644 --- a/skills/wire-quiet/SKILL.md +++ b/skills/wire-quiet/SKILL.md @@ -1,4 +1,5 @@ --- +name: wire-quiet description: Silence wire desktop notifications (toasts). Use when user says "wire quiet", "mute wire", "stop the toasts", or is in a focus / demo context where macOS Notification Center spam is unwanted. File-based kill switch persists across daemon restarts; env-based variant (WIRE_NO_TOASTS=1) covers launchd-spawned daemons. --- @@ -40,7 +41,7 @@ Reports: `on` / `off` + mechanism (`via file` / `via env` / `none`). ## Multi-session / fleet-wide silence -If the operator wants every Claude tab's daemon silenced: +If the operator wants every local agent session's daemon silenced: ```bash # Global env (launchd) — covers future-spawned daemons @@ -64,4 +65,4 @@ wire quiet off ## Reference - v0.14.1 release notes (`README.md` §"Status — v0.14.1") for the kill-switch design. -- Memory: `feedback_wire_upgrade_skips_mcp_servers` — sister Claude sessions' wire mcp subprocesses need `/mcp` reconnect to pick up the silenced binary if `wire upgrade` ran. +- After `wire upgrade`, restart or reconnect each host-pinned `wire mcp` subprocess so it uses the new binary. diff --git a/skills/wire-send/SKILL.md b/skills/wire-send/SKILL.md index 3351123..dfd80dc 100644 --- a/skills/wire-send/SKILL.md +++ b/skills/wire-send/SKILL.md @@ -1,4 +1,5 @@ --- +name: wire-send description: Send a signed message to a paired wire peer. Use when user wants to message another agent by handle/nick. Wire send writes a signed event to the per-peer outbox; the daemon pushes to the relay; the peer's daemon pulls + delivers to their inbox. End-to-end via Ed25519 signatures; relays are transport, never authority. --- @@ -53,7 +54,7 @@ When auto-pair fires, the message is queued; delivery happens when the peer acce ## MCP tool variant -`mcp__wire__wire_send({peer: "", body: ""})` from the assistant context. Same semantics; same shell-metachar caution does NOT apply (MCP carries the body as a parameter, not a shell arg). +Call `wire_send` with `peer` and `body`. MCP carries the body as a parameter, so shell-metacharacter escaping does not apply. ## Verify delivery diff --git a/tests/plugin_contract.rs b/tests/plugin_contract.rs index 9d15b0f..448a797 100644 --- a/tests/plugin_contract.rs +++ b/tests/plugin_contract.rs @@ -59,3 +59,27 @@ fn repository_marketplace_exposes_root_wire_plugin() { .is_file() ); } + +#[test] +fn bundled_skill_command_audit_rejects_removed_pairing_surface() { + let removed = [ + "wire pair-list-pending", + "wire pair-confirm", + "wire init ", + "/wire:wire-", + ]; + for entry in fs::read_dir(repo_root().join("skills")).expect("read skills") { + let skill_path = entry.expect("skill entry").path().join("SKILL.md"); + if !skill_path.is_file() { + continue; + } + let body = fs::read_to_string(&skill_path).expect("read skill"); + for signature in removed { + assert!( + !body.contains(signature), + "{} advertises {signature}", + skill_path.display() + ); + } + } +} From 71124cda614dfd0dac3e90fe535f228b2792dd4a Mon Sep 17 00:00:00 2001 From: Paul Logan Date: Wed, 15 Jul 2026 10:20:37 -0700 Subject: [PATCH 6/7] docs: add Codex plugin installation --- README.md | 9 ++++++++ SESSION_LOG_2026_07_14.md | 20 ++++++++++++++++- docs/PLUGIN.md | 45 ++++++++++++++++++++++++++++++++------- tests/plugin_contract.rs | 23 ++++++++++++++------ 4 files changed, 82 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 0f17130..260f977 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Wire integrates at the harness layer — your agent's tool-calling loop, not you | If you use… | Install path | First-run smoke | |---|---|---| | **Claude Code** | `cargo install slancha-wire`, then `/plugin install @SlanchaAi/wire` (also accepts the install.sh path) | SessionStart hook prints `wire ` ✓ | +| **Codex App / CLI / IDE** | `cargo install slancha-wire`, `codex plugin marketplace add SlanchaAi/wire`, then `codex plugin add wire@wire` | Start a new task; ask “Show my Wire identity” | | **Cursor / Aider / generic MCP host** | `wire setup --apply` | Restart client; `wire_*` tools appear in MCP list | | **GitHub Copilot CLI** | [docs/integrations/COPILOT_CLI.md](docs/integrations/COPILOT_CLI.md) | `gh copilot` → "Call wire_whoami" | | **GitHub Copilot (VS Code)** | [docs/integrations/GITHUB_COPILOT.md](docs/integrations/GITHUB_COPILOT.md) | Restart VS Code; toolbar shows wire MCP | @@ -67,6 +68,14 @@ Wire integrates at the harness layer — your agent's tool-calling loop, not you | **Pure terminal** | `wire up`, `wire dial`, `wire monitor` | local message appears | | **Custom harness / non-Node** | CLI `--json` mode + filesystem contract — see [docs/AGENT_INTEGRATION.md](docs/AGENT_INTEGRATION.md) | `wire whoami --json` + `wire tail --json` | +Codex users who want only the tool surface can skip plugin packaging: + +```bash +codex mcp add wire -- wire mcp +``` + +The plugin adds shared Wire skills and marketplace discovery; both routes run the same local `wire mcp`. Keep one Wire MCP registration enabled to avoid duplicate tool catalogs. + ## Trust model (one paragraph) Knowing a handle (`alice@wireup.net`) and being able to resolve it to a signed agent-card is the authentication ceremony — same shape as discovering someone's Mastodon account via WebFinger or their PGP key via WKD. The card carries an Ed25519 verify-key, signed by that key, so the resolver knows the relay isn't lying about who claims the nick. FCFS on nicks; same-DID re-claims allowed. **Bilateral consent:** a stranger can leave one pair request in your `wire pending` list but can NEVER auto-pin themselves into your trust ring or get write access to your inbox until you `wire accept`. For threat models where the discovery channel itself can't be trusted (suspect DNS, distrustful operator), verify the resolved card fingerprint out-of-band (or pair via a one-time invite URL — see [Alternative flows](#alternative-flows)). Full threat model: [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md). diff --git a/SESSION_LOG_2026_07_14.md b/SESSION_LOG_2026_07_14.md index f11c53e..9be0c5a 100644 --- a/SESSION_LOG_2026_07_14.md +++ b/SESSION_LOG_2026_07_14.md @@ -23,7 +23,25 @@ Design: `docs/superpowers/specs/2026-07-14-codex-plugin-design.md`. ### Current phase -Written spec approved. Implementation plan created; execution approach awaiting user choice. +Implementation and verification complete: packaging, shared-skill modernization, install documentation, isolated Codex install smoke, plugin/skill validation, and full Rust regression suite. + +### Implementation findings + +- Codex packaging reuses the repository root through marketplace source `./`. +- Codex skill validation requires explicit `name` frontmatter. +- Shared skills advertised removed SAS/init commands; corrected before Codex distribution. +- `wire mcp` keeps data synchronized, while Codex ingests messages through `wire_pull` and `wire_tail` at task checkpoints. +- The local plugin validator requires PyYAML; `uv run --with pyyaml` provides it without changing project dependencies. +- Isolated `CODEX_HOME` smoke discovered `wire@wire` v0.17.0, installed it into plugin cache, and reported it enabled without touching normal Codex configuration. +- Final `cargo test -q` exited 0 across unit, integration, stress, and doctest batches; only pre-existing ignored tests remained ignored. + +### Implementation artifacts + +- `.codex-plugin/plugin.json` — Codex plugin manifest. +- `.agents/plugins/marketplace.json` — repository marketplace entry. +- `tests/plugin_contract.rs` — packaging and documentation contract tests. +- `skills/*/SKILL.md` — shared Claude/Codex workflows. +- `README.md` and `docs/PLUGIN.md` — install and operation guidance. ## Artifacts diff --git a/docs/PLUGIN.md b/docs/PLUGIN.md index e8cc718..471284d 100644 --- a/docs/PLUGIN.md +++ b/docs/PLUGIN.md @@ -1,8 +1,8 @@ -# Wire as a Claude plugin +# Wire agent plugins -Wire is publishable as a [Claude Code plugin](https://code.claude.com/docs/en/plugins.md). The plugin manifest + skills + MCP server declaration live at the root of this repo; the actual wire binary is installed separately via Cargo. +Wire packages its shared skills and MCP server for Claude Code and Codex. Harness-specific manifests live at the repository root; the actual `wire` binary is installed separately via Cargo. -## Install (end-user UX) +## Claude Code plugin Two steps: @@ -23,15 +23,44 @@ The plugin ships six skills, all namespaced under `/wire:`: | Command | Purpose | |---|---| | `/wire:wire-init` | Initialize wire — mint session DID + Ed25519 keypair, optionally bind a public relay | -| `/wire:wire-pair` | Pair this session with another wire agent (bilateral, signed, with optional SAS) | +| `/wire:wire-pair` | Dial another Wire agent or handle an inbound consent decision | | `/wire:wire-monitor` | Arm the persistent inbox watcher per wire MCP session-start directive | | `/wire:wire-send` | Send a signed message to a paired peer (auto-pair on miss) | | `/wire:wire-enroll` | Enroll operator + organization identity (RFC-001 v0.14 identity layer) | | `/wire:wire-quiet` | Silence wire desktop toasts (file + env-based kill switches) | +## Codex plugin + +The Codex manifest lives at `.codex-plugin/plugin.json` and reuses `.mcp.json` plus `skills/`. The binary remains a separate install: + +```bash +cargo install slancha-wire +codex plugin marketplace add SlanchaAi/wire +codex plugin add wire@wire +``` + +Start a new task. Ask “Show my Wire identity,” then dial only a real operator-supplied persona. Inbound requests remain pending until explicit accept or reject. + +### MCP-only Codex setup + +```bash +codex mcp add wire -- wire mcp +``` + +MCP-only gives the same tools without bundled skills or marketplace UX. Do not enable direct `mcp_servers.wire` and plugin-provided Wire MCP together unless testing duplicate registrations. + +### Local Codex development + +```bash +codex plugin marketplace add /absolute/path/to/wire +codex plugin add wire@wire +``` + +Reinstall after manifest or skill changes, then start a new task. + ### MCP server tools -Claude Code auto-starts `wire mcp` on session start (declared in `.mcp.json`). Identity auto-provisions and the sync daemon arms on first start — no manual init needed. Tools (all prefixed `mcp__wire__` over MCP): +Claude Code and Codex auto-start `wire mcp` from `.mcp.json`. Identity auto-provisions and the sync daemon arms on first start — no manual init needed. Tool names: - **Orient / read** — `wire_whoami`, `wire_here` (who am I, who's around?), `wire_peers`, `wire_status`, `wire_tail`, `wire_pull`, `wire_verify` - **Connect** — `wire_dial` (the one to reach for: pairs a local sister or a `nick@domain` peer), `wire_pending`, `wire_accept`, `wire_reject`. `wire_add` is `wire_dial`'s federation backend; `wire_invite_mint` / `wire_invite_accept` cover the invite-URL path. @@ -43,7 +72,7 @@ This list is verified against the live catalog by a test (`agent_docs_match_adve Resource: `wire://inbox/` exposes each pinned peer's verified inbox as JSONL. -## Publishing channels +## Claude publishing channels The plugin is publishable via three paths (all working from the same `.claude-plugin/plugin.json` manifest): @@ -71,7 +100,7 @@ The three channels coexist. Community submission is recommended as the first pub Omitting the `version` field would make Claude Code use the git commit SHA — every commit is a new version. Slancha pins explicit semver for predictable rollouts. -## Plugin install vs `wire setup` — pick one +## Claude plugin install vs `wire setup` — pick one The plugin's `.mcp.json` declares the wire MCP server entry. If you previously ran `wire setup --apply` (which writes the same entry into `~/.claude.json`), you'll end up with two `mcpServers.wire` entries — global + plugin-scoped. Claude Code resolves them deterministically (plugin-scoped wins for the current session), but the duplicate is confusing and the global entry stops being maintained. @@ -94,7 +123,7 @@ EOF A future `wire setup --apply` will detect a plugin install and skip writing the global entry; pre-v0.14.2 `wire setup` doesn't yet know about the plugin path. Tracking in v0.14.2 backlog. -## Plugin development +## Claude plugin development To work on the plugin scaffold without affecting end-user sessions: diff --git a/tests/plugin_contract.rs b/tests/plugin_contract.rs index 448a797..396ca73 100644 --- a/tests/plugin_contract.rs +++ b/tests/plugin_contract.rs @@ -10,8 +10,7 @@ fn read_json(relative: &str) -> Value { let path = repo_root().join(relative); let body = fs::read_to_string(&path) .unwrap_or_else(|error| panic!("read {}: {error}", path.display())); - serde_json::from_str(&body) - .unwrap_or_else(|error| panic!("parse {}: {error}", path.display())) + serde_json::from_str(&body).unwrap_or_else(|error| panic!("parse {}: {error}", path.display())) } fn assert_shared_components(manifest: &Value) { @@ -46,10 +45,7 @@ fn repository_marketplace_exposes_root_wire_plugin() { assert_eq!(plugins.len(), 1); let wire = &plugins[0]; assert_eq!(wire["name"], "wire"); - assert_eq!( - wire["source"], - json!({"source": "local", "path": "./"}) - ); + assert_eq!(wire["source"], json!({"source": "local", "path": "./"})); assert_eq!(wire["policy"]["installation"], "AVAILABLE"); assert_eq!(wire["policy"]["authentication"], "ON_INSTALL"); assert_eq!(wire["category"], "Developer Tools"); @@ -83,3 +79,18 @@ fn bundled_skill_command_audit_rejects_removed_pairing_surface() { } } } + +#[test] +fn codex_install_signatures_are_documented() { + let signatures = [ + "codex plugin marketplace add SlanchaAi/wire", + "codex plugin add wire@wire", + "codex mcp add wire -- wire mcp", + ]; + for relative in ["README.md", "docs/PLUGIN.md"] { + let body = fs::read_to_string(repo_root().join(relative)).expect("read docs"); + for signature in signatures { + assert!(body.contains(signature), "{relative} missing `{signature}`"); + } + } +} From 46ae0c5d8f298d1dda7067e0fe28e88a412a1f17 Mon Sep 17 00:00:00 2001 From: Paul Logan Date: Wed, 15 Jul 2026 11:57:12 -0700 Subject: [PATCH 7/7] docs: trim Codex plugin design --- docs/superpowers/specs/2026-07-14-codex-plugin-design.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/superpowers/specs/2026-07-14-codex-plugin-design.md b/docs/superpowers/specs/2026-07-14-codex-plugin-design.md index 2c5daf0..fd40908 100644 --- a/docs/superpowers/specs/2026-07-14-codex-plugin-design.md +++ b/docs/superpowers/specs/2026-07-14-codex-plugin-design.md @@ -146,4 +146,3 @@ Product smoke must cover a clean local Codex install: - Claude plugin behavior remains intact. - MCP-only users remain supported. - Plugin and marketplace validators pass from a clean checkout. -