@webpresso/codex-plugin 3.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "agent-kit",
3
+ "version": "3.1.3",
4
+ "description": "Webpresso agent-kit: blueprints, skills, hooks, and MCP server",
5
+ "author": {
6
+ "name": "Webpresso",
7
+ "url": "https://github.com/webpresso"
8
+ },
9
+ "homepage": "https://github.com/webpresso/agent-kit#readme",
10
+ "repository": "https://github.com/webpresso/agent-kit",
11
+ "license": "Elastic-2.0",
12
+ "keywords": [
13
+ "agent",
14
+ "blueprint",
15
+ "mcp",
16
+ "skills",
17
+ "developer-tools"
18
+ ],
19
+ "skills": "./skills/",
20
+ "mcpServers": "./.mcp.json",
21
+ "hooks": "./hooks/hooks.json",
22
+ "interface": {
23
+ "displayName": "Webpresso agent-kit",
24
+ "shortDescription": "Blueprints, skills, hooks, and MCP tools.",
25
+ "developerName": "Webpresso",
26
+ "category": "Development",
27
+ "capabilities": [
28
+ "Read",
29
+ "Write"
30
+ ]
31
+ }
32
+ }
package/.mcp.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "webpresso": {
3
+ "command": "${PLUGIN_ROOT}/bin/wp",
4
+ "args": ["mcp"]
5
+ }
6
+ }
package/LICENSE ADDED
@@ -0,0 +1,104 @@
1
+ Elastic License 2.0
2
+ ===================
3
+
4
+ Copyright (c) 2026 Webpresso, Inc.
5
+
6
+ Acceptance
7
+ ----------
8
+
9
+ By using the software, you agree to all of the terms and conditions below.
10
+
11
+ Copyright License
12
+ -----------------
13
+
14
+ The licensor grants you a non-exclusive, royalty-free, worldwide,
15
+ non-sublicensable, non-transferable license to use, copy, distribute, make
16
+ available, and prepare derivative works of the software, in each case subject
17
+ to the limitations and conditions below.
18
+
19
+ Limitations
20
+ -----------
21
+
22
+ You may not provide the software to third parties as a hosted or managed
23
+ service, where the service provides users with access to any substantial set of
24
+ the features or functionality of the software.
25
+
26
+ You may not move, change, disable, or circumvent the license key
27
+ functionality in the software, and you may not remove or obscure any
28
+ functionality in the software that is protected by the license key.
29
+
30
+ You may not alter, remove, or obscure any licensing, copyright, or other
31
+ notices of the licensor in the software. Any use of the licensor's trademarks
32
+ is subject to applicable law.
33
+
34
+ Patents
35
+ -------
36
+
37
+ The licensor grants you a license, under any patent claims the licensor can
38
+ license, or becomes able to license, to make, have made, use, sell, offer for
39
+ sale, import and have imported the software, in each case subject to the
40
+ limitations and conditions in this license. This license does not cover any
41
+ patent claims that you cause to be infringed by modifications or additions to
42
+ the software.
43
+
44
+ If you or your company make any written claim that the software infringes or
45
+ contributes to infringement of any patent, your patent license for the software
46
+ granted under these terms ends immediately. If your company makes such a claim,
47
+ your patent license ends immediately for work on behalf of your company.
48
+
49
+ Notices
50
+ -------
51
+
52
+ You must ensure that anyone who gets a copy of any part of the software from
53
+ you also gets a copy of these terms.
54
+
55
+ If you modify the software, you must include in any modified copies of the
56
+ software prominent notices stating that you have modified the software.
57
+
58
+ No Other Rights
59
+ ---------------
60
+
61
+ These terms do not imply any licenses other than those expressly granted in
62
+ these terms.
63
+
64
+ Termination
65
+ -----------
66
+
67
+ If you use the software in violation of these terms, such use is not licensed,
68
+ and your licenses will automatically terminate. If the licensor provides you
69
+ with a notice of your violation, and you cease all violation of this license no
70
+ later than 30 days after you receive that notice, your licenses will be
71
+ reinstated retroactively. However, if you violate these terms after such
72
+ reinstatement, any additional violation of these terms will cause your licenses
73
+ to terminate automatically and permanently.
74
+
75
+ No Liability
76
+ ------------
77
+
78
+ As far as the law allows, the software comes as is, without any warranty or
79
+ condition, and the licensor will not be liable to you for any damages arising
80
+ out of these terms or the use or nature of the software, under any kind of
81
+ legal claim.
82
+
83
+ Definitions
84
+ -----------
85
+
86
+ The licensor is the entity offering these terms, and the software is the
87
+ software the licensor makes available under these terms, including any portion
88
+ of it.
89
+
90
+ you refers to the individual or entity agreeing to these terms.
91
+
92
+ your company is any legal entity, sole proprietorship, or other kind of
93
+ organization that you work for, plus all organizations that have control over,
94
+ are under the control of, or are under common control with that organization.
95
+ control means ownership of substantially all the assets of an entity, or the
96
+ power to direct its management and policies by vote, contract, or otherwise.
97
+ Control can be direct or indirect.
98
+
99
+ your licenses are all the licenses granted to you for the software under these
100
+ terms.
101
+
102
+ use means anything you do with the software requiring one of your licenses.
103
+
104
+ trademark means trademarks, service marks, and similar rights.
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # @webpresso/codex-plugin
2
+
3
+ `@webpresso/codex-plugin` is the Codex CLI host adapter package for Agent Kit.
4
+ It ships the Codex plugin manifest, packaged skills, hook metadata, a
5
+ package-local MCP manifest, and a package-local `bin/wp` shim that forwards to
6
+ an Agent Kit `wp` CLI resolved at runtime.
7
+
8
+ This adapter does not declare `@webpresso/agent-kit` as a package dependency.
9
+ Its package-local `bin/wp` shim resolves the CLI in this order:
10
+
11
+ 1. `WEBPRESSO_WP_BIN`, when set to an executable file.
12
+ 2. The source-checkout root `../../bin/wp`, for local adapter development.
13
+ 3. `@webpresso/agent-kit/bin/wp`, when an integrator installed the root package
14
+ alongside this adapter.
15
+ 4. `wp` on `PATH`.
16
+
17
+ If none of those candidates is available, install or update the root package
18
+ through `wp setup`, `wp update`, or your package manager, or set
19
+ `WEBPRESSO_WP_BIN` to the desired executable. The shim preserves
20
+ `WP_SKIP_UPDATE_CHECK=1` by default for host MCP invocations.
21
+
22
+ The root `@webpresso/agent-kit` package is CLI/core only: it ships `wp`, MCP
23
+ tools, audits, blueprints, session memory, docs, and catalog sources, but it
24
+ does not ship this Codex payload from the root package. Use this adapter when
25
+ you need the Codex-native plugin delivery surface.
package/bin/wp ADDED
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { accessSync, constants, realpathSync, statSync } from "node:fs";
5
+ import { createRequire } from "node:module";
6
+ import { dirname, delimiter, join } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const require = createRequire(import.meta.url);
10
+ const shimPath = fileURLToPath(import.meta.url);
11
+ const shimDir = dirname(shimPath);
12
+ const adapterRoot = dirname(shimDir);
13
+
14
+ function isExecutableFile(candidate) {
15
+ try {
16
+ const stat = statSync(candidate);
17
+ if (!stat.isFile()) return false;
18
+ accessSync(candidate, constants.X_OK);
19
+ return true;
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+
25
+ function sameFile(a, b) {
26
+ try {
27
+ return realpathSync(a) === realpathSync(b);
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+
33
+ function pathWpCandidates() {
34
+ return (process.env.PATH ?? "")
35
+ .split(delimiter)
36
+ .filter(Boolean)
37
+ .map((entry) => join(entry, process.platform === "win32" ? "wp.cmd" : "wp"));
38
+ }
39
+
40
+ function resolveAgentKitPackageWp() {
41
+ try {
42
+ const packageJsonPath = require.resolve("@webpresso/agent-kit/package.json");
43
+ return join(dirname(packageJsonPath), "bin", "wp");
44
+ } catch {
45
+ return undefined;
46
+ }
47
+ }
48
+
49
+ function resolveAgentKitWp() {
50
+ const explicitPath = process.env.WEBPRESSO_WP_BIN;
51
+ if (explicitPath) {
52
+ if (isExecutableFile(explicitPath)) return explicitPath;
53
+ throw new Error(
54
+ `WEBPRESSO_WP_BIN points to ${explicitPath}, but that file is not executable. ` +
55
+ "Set WEBPRESSO_WP_BIN to a valid wp executable, install/update @webpresso/agent-kit, or run wp setup.",
56
+ );
57
+ }
58
+
59
+ const candidates = [
60
+ join(adapterRoot, "..", "..", "bin", "wp"),
61
+ resolveAgentKitPackageWp(),
62
+ ...pathWpCandidates(),
63
+ ].filter(Boolean);
64
+
65
+ for (const candidate of candidates) {
66
+ if (isExecutableFile(candidate) && !sameFile(candidate, shimPath)) {
67
+ return candidate;
68
+ }
69
+ }
70
+
71
+ throw new Error(
72
+ "Unable to locate the Webpresso wp CLI. Install or update @webpresso/agent-kit, run wp setup, " +
73
+ "set WEBPRESSO_WP_BIN to the wp executable, or ensure wp is available on PATH.",
74
+ );
75
+ }
76
+
77
+ const result = spawnSync(resolveAgentKitWp(), process.argv.slice(2), {
78
+ stdio: "inherit",
79
+ env: {
80
+ ...process.env,
81
+ WP_SKIP_UPDATE_CHECK: process.env.WP_SKIP_UPDATE_CHECK ?? "1",
82
+ },
83
+ });
84
+
85
+ if (result.error) {
86
+ throw result.error;
87
+ }
88
+
89
+ process.exit(result.status ?? (result.signal ? 1 : 0));
@@ -0,0 +1,3 @@
1
+ {
2
+ "hooks": {}
3
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@webpresso/codex-plugin",
3
+ "version": "3.1.3",
4
+ "private": false,
5
+ "description": "Codex plugin adapter for Webpresso agent-kit skills, hooks, and MCP runtime.",
6
+ "homepage": "https://github.com/webpresso/agent-kit#readme",
7
+ "bugs": {
8
+ "url": "https://github.com/webpresso/agent-kit/issues"
9
+ },
10
+ "license": "Elastic-2.0",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/webpresso/agent-kit.git",
14
+ "directory": "packages/codex-plugin"
15
+ },
16
+ "files": [
17
+ ".codex-plugin",
18
+ ".mcp.json",
19
+ "skills",
20
+ "hooks",
21
+ "bin/wp",
22
+ "LICENSE",
23
+ "README.md"
24
+ ],
25
+ "type": "module",
26
+ "sideEffects": false,
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "registry": "https://registry.npmjs.org/"
30
+ },
31
+ "engines": {
32
+ "node": ">=24"
33
+ }
34
+ }
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: best-practice-research
3
+ description: "Official/upstream best-practice research wrapper with bounded evidence and dated citations."
4
+ argument-hint: "<technology|decision|practice question>"
5
+ ---
6
+
7
+ # Best-Practice Research
8
+
9
+ Use this skill when a task depends on current external best practices, version-aware guidance, standards, official recommendations, or upstream behavior. This is a workflow wrapper: it routes evidence gathering and synthesis; it is not a new research authority and it does not replace `researcher`.
10
+
11
+ ## Purpose
12
+
13
+ Produce a cited, reusable best-practice answer or handoff that separates current external evidence from repo-local facts and dependency-selection decisions.
14
+
15
+ ## Activate When
16
+
17
+ - The user asks for best practices, recommended approach, current guidance, official recommendations, standards, or version-aware external behavior.
18
+ - `$ralplan`, `$deep-interview`, `$team`, or another workflow needs current external evidence before planning or execution can be correct.
19
+ - The task involves an already chosen technology and needs authoritative usage guidance, migration notes, API behavior, lifecycle rules, or current safety guidance.
20
+
21
+ ## Do Not Activate When
22
+
23
+ - The answer is fully repo-local; use `explore` for codebase facts.
24
+ - The main question is whether to adopt, replace, upgrade, or compare dependencies; use `dependency-expert`.
25
+ - The user only needs implementation against already-grounded requirements; use `executor`, `$ralph`, or `$team` as appropriate.
26
+ - The task can be answered from stable local project conventions without current external lookup.
27
+
28
+ ## Specialist Routing
29
+
30
+ 1. Use `explore` first for brownfield facts: current code usage, local constraints, versions, config, and integration points.
31
+ 2. Use `researcher` for official/upstream docs, release notes, standards, migration guides, source-backed examples, and current best-practice evidence for an already chosen technology.
32
+ 3. Use `dependency-expert` only for adoption/upgrade/replacement/comparison decisions.
33
+ 4. Return to the caller with explicit evidence, uncertainty, and any implementation handoff constraints.
34
+
35
+ ## Source-Quality Rules
36
+
37
+ - Prefer official documentation, upstream source, release notes, changelogs, standards, and maintainer guidance.
38
+ - Include source URLs for material claims.
39
+ - State date/version context for current best-practice claims.
40
+ - Label third-party summaries as supplemental; do not use them before official/upstream sources.
41
+ - Flag stale, conflicting, undocumented, or version-mismatched evidence.
42
+ - Do not over-fetch: gather the smallest evidence set that can support the decision.
43
+
44
+ ## Workflow
45
+
46
+ 1. Classify the question: conceptual best practice, implementation guidance, migration/version guidance, standards/compliance guidance, or mixed local + external guidance.
47
+ 2. Gather repo-local facts with `explore` when local usage or constraints affect the answer.
48
+ 3. Gather external evidence with `researcher` when current or version-aware practice affects correctness.
49
+ 4. Synthesize a concise answer with source quality, version/date context, caveats, and an implementation or planning handoff.
50
+ 5. Stop when the answer is grounded enough for the caller; otherwise report the exact blocker or specialist handoff needed.
51
+
52
+ ## Output Contract
53
+
54
+ ```md
55
+ ## Best-Practice Research: <question>
56
+
57
+ ### Direct Recommendation
58
+
59
+ <actionable guidance or decision support>
60
+
61
+ ### Evidence Used
62
+
63
+ - Official/upstream: <source URL> — <what it establishes>
64
+ - Supplemental, if any: <source URL> — <why it is secondary>
65
+
66
+ ### Version / Date Context
67
+
68
+ <versions, dates, release channels, or unknowns>
69
+
70
+ ### Repo-Local Context
71
+
72
+ <facts from explore, or "not needed">
73
+
74
+ ### Boundaries / Non-goals
75
+
76
+ <what this research does not decide>
77
+
78
+ ### Handoff
79
+
80
+ <planning/execution/test implications>
81
+ ```
82
+
83
+ ## Stop Rules
84
+
85
+ - Stop after a source-backed recommendation is reusable by the caller.
86
+ - Stop and route upward if the task becomes dependency comparison, broad architecture, or implementation.
87
+ - Do not continue researching when remaining work would only polish wording rather than change the recommendation.
88
+
89
+ Task: {{ARGUMENTS}}
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: browse
3
+ description: "Browser/page inspection via Webpresso Playwright: inspect DOM, console, network basics, screenshots, and findings."
4
+ license: MIT
5
+ allowed-tools:
6
+ - Read
7
+ - Glob
8
+ - Grep
9
+ - Bash
10
+ ---
11
+
12
+ # Browse
13
+
14
+ Use `wp browser doctor` first if browser availability is unknown; if it reports a missing browser, run `wp browser ensure chromium`. Prefer repo-local preview or dev-server URLs; if none are discoverable, ask for a URL.
15
+
16
+ ## Read-only inspection
17
+
18
+ The `allowed-tools` frontmatter is an audited disclosure for supported hosts;
19
+ pretool-guard remains the enforced mutation backstop when a host does not prove
20
+ skill-frontmatter enforcement.
21
+
22
+ 1. Identify the URL and whether headed or headless mode is needed.
23
+ 2. Use `wp browser open <url> --json` for a lightweight smoke snapshot, or a project Playwright test for deeper flows.
24
+ 3. Report URL, title, status evidence, console/page errors when available, and any screenshots/artifacts.
25
+ 4. Do not mutate app data unless the user explicitly asks for a mutating browser flow.
@@ -0,0 +1,202 @@
1
+ ---
2
+ name: claude
3
+ description: "Claude CLI outside-voice wrapper for review, adversarial challenge, or consultation from non-Claude hosts."
4
+ license: MIT
5
+ ---
6
+
7
+ # Claude outside voice
8
+
9
+ Use when a non-Claude host needs Claude to review a diff, challenge a plan, or answer a focused repo question. Keep it bounded/read-only unless asked otherwise, and report Claude output as advice, not verified fact.
10
+
11
+ ## Auth check
12
+
13
+ Use local Claude CLI login directly; do not route through Anthropic API-key env vars.
14
+
15
+ ```bash
16
+ AUTH_STATUS_FILE=$(mktemp -t wp-claude-auth.XXXXXX)
17
+ trap 'rm -f "$AUTH_STATUS_FILE"' EXIT
18
+ if ! claude auth status --json >"$AUTH_STATUS_FILE" 2>/dev/null; then
19
+ if ! claude auth status >"$AUTH_STATUS_FILE" 2>/dev/null; then
20
+ echo "CLAUDE_AUTH=missing: run claude auth login with the intended Claude Max account"
21
+ exit 1
22
+ fi
23
+ fi
24
+ if grep -E '"(authenticated|loggedIn|success)"[[:space:]]*:[[:space:]]*true' "$AUTH_STATUS_FILE" >/dev/null; then
25
+ echo "CLAUDE_AUTH=cli-login"
26
+ else
27
+ echo "CLAUDE_AUTH=missing: claude auth status did not report a recognized Claude CLI login"
28
+ exit 1
29
+ fi
30
+ ```
31
+
32
+ ## Portable prompt file
33
+
34
+ Use a suffix-free `mktemp -t` pattern so macOS and Linux both work:
35
+
36
+ ```bash
37
+ PROMPT_FILE=$(mktemp -t wp-claude-review.XXXXXX)
38
+ trap 'rm -f "$PROMPT_FILE"' EXIT
39
+ ```
40
+
41
+ ## Modes
42
+
43
+ ### Review
44
+
45
+ Use single-file / single-question first for any non-trivial diff. Do not send a whole PR unless it already fits within the bounded payload below.
46
+
47
+ **Model policy:** default to `sonnet` via `CLAUDE_REVIEW_MODEL=${CLAUDE_REVIEW_MODEL:-sonnet}`. Override to `opus` only when the review is approval evidence or the user explicitly asks for Opus. Never use `fable` for Claude outside-voice review. Any review used as blueprint approval evidence MUST run with Claude Opus and record `"model": "opus"`.
48
+
49
+ #### Bounded prompt payload
50
+
51
+ Always include:
52
+
53
+ - current branch and base branch
54
+ - `git diff --stat`
55
+ - changed file list
56
+ - one targeted file diff or one narrow snippet/hunk only, capped to a fixed size
57
+
58
+ Prefer ~12 KB or ~200 lines per call. Split large reviews instead of raising the cap.
59
+
60
+ ```bash
61
+ BASE_BRANCH=${BASE_BRANCH:-origin/main}
62
+ CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
63
+ TARGET_FILE=${TARGET_FILE:?set TARGET_FILE to one changed file}
64
+
65
+ {
66
+ printf 'Outside review mode: focused diff review\n'
67
+ printf 'Base branch: %s\nCurrent branch: %s\n\n' "$BASE_BRANCH" "$CURRENT_BRANCH"
68
+ printf 'git diff --stat %s...HEAD\n' "$BASE_BRANCH"
69
+ git diff --stat "$BASE_BRANCH"...HEAD
70
+ printf '\nChanged files:\n'
71
+ git diff --name-only "$BASE_BRANCH"...HEAD
72
+ printf '\nTarget file: %s\n' "$TARGET_FILE"
73
+ printf 'Bounded target diff (max 12000 bytes):\n'
74
+ git diff --unified=3 "$BASE_BRANCH"...HEAD -- "$TARGET_FILE" | \
75
+ head -c 12000
76
+ printf '\n\nQuestion: Identify the highest-signal correctness, security, data-loss, or maintainability risk in %s. Quote only the smallest relevant excerpt. If context is insufficient, answer INSUFFICIENT_CONTEXT.\n' "$TARGET_FILE"
77
+ } >"$PROMPT_FILE"
78
+ ```
79
+
80
+ #### Timed Claude artifact wrapper
81
+
82
+ Run Claude through the bounded Python wrapper. Use `claude --print --model "$CLAUDE_REVIEW_MODEL"`; do not recommend `--bare` for this path. Do not add artificial budget caps unless the user requested one. The wrapper is deterministic:
83
+
84
+ - it defaults to `CLAUDE_REVIEW_MODEL=sonnet` and allows only `opus` or `sonnet`
85
+ - it never passes `--tools ""`, because Claude's variadic `--tools` option can swallow the prompt
86
+ - it captures stdout and stderr for preflight and the full review
87
+ - it writes the artifact on success, non-zero exit, or timeout
88
+ - it runs one full review attempt only; retry only if you change the command shape for a specific diagnosed reason
89
+
90
+ Preflight must be only the tiny `claude-ok` prompt with the selected model. Do not use the real review prompt for preflight.
91
+
92
+ ```bash
93
+ CLAUDE_REVIEW_MODEL=${CLAUDE_REVIEW_MODEL:-sonnet}
94
+ CLAUDE_REVIEW_EFFORT=${CLAUDE_REVIEW_EFFORT:-medium}
95
+ CLAUDE_REVIEW_PREFLIGHT_TIMEOUT_SECONDS=${CLAUDE_REVIEW_PREFLIGHT_TIMEOUT_SECONDS:-30}
96
+ CLAUDE_REVIEW_IDLE_TIMEOUT_SECONDS=${CLAUDE_REVIEW_IDLE_TIMEOUT_SECONDS:-180}
97
+ CLAUDE_REVIEW_ARTIFACT=${CLAUDE_REVIEW_ARTIFACT:-"$(pwd)/.webpresso/claude-review-$(date +%Y%m%dT%H%M%S).json"}
98
+ export CLAUDE_REVIEW_MODEL CLAUDE_REVIEW_EFFORT CLAUDE_REVIEW_PREFLIGHT_TIMEOUT_SECONDS CLAUDE_REVIEW_IDLE_TIMEOUT_SECONDS CLAUDE_REVIEW_ARTIFACT
99
+ mkdir -p "$(dirname "$CLAUDE_REVIEW_ARTIFACT")"
100
+
101
+ python3 - "$PROMPT_FILE" "$CLAUDE_REVIEW_ARTIFACT" <<'PY'
102
+ from __future__ import annotations
103
+
104
+ import json, os, subprocess, sys, time
105
+ from pathlib import Path
106
+
107
+ prompt_path = Path(sys.argv[1])
108
+ artifact_path = Path(sys.argv[2])
109
+ model = os.environ.get("CLAUDE_REVIEW_MODEL", "sonnet")
110
+ effort = os.environ.get("CLAUDE_REVIEW_EFFORT", "medium")
111
+ preflight_timeout = float(os.environ.get("CLAUDE_REVIEW_PREFLIGHT_TIMEOUT_SECONDS", "30"))
112
+ review_idle_timeout = float(os.environ.get("CLAUDE_REVIEW_IDLE_TIMEOUT_SECONDS", "180"))
113
+ if model not in {"opus", "sonnet"}: raise SystemExit("CLAUDE_REVIEW_MODEL must be one of: opus, sonnet")
114
+ if effort not in {"medium", "high"}: raise SystemExit("CLAUDE_REVIEW_EFFORT must be one of: medium, high")
115
+ if preflight_timeout <= 0 or review_idle_timeout <= 0: raise SystemExit("Claude timeout values must be positive numbers")
116
+
117
+
118
+ def utc_now() -> str: return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
119
+
120
+ artifact: dict[str, object] = {"tool": "claude-outside-voice", "model": model, "effort": effort, "startedAt": utc_now(), "artifactPath": str(artifact_path), "runs": []}
121
+
122
+
123
+ def write_artifact(status: str, exit_code: int) -> None:
124
+ artifact.update({"finishedAt": utc_now(), "status": status, "exitCode": exit_code})
125
+ artifact_path.parent.mkdir(parents=True, exist_ok=True)
126
+ artifact_path.write_text(json.dumps(artifact, indent=2) + "\n", encoding="utf-8")
127
+
128
+
129
+ def as_text(value: str | bytes | None) -> str:
130
+ return value.decode(errors="replace") if isinstance(value, bytes) else (value or "")
131
+
132
+
133
+ def run_claude(stage: str, prompt: str, timeout_seconds: float, timeout_kind: str) -> int:
134
+ command = ["claude", "--print", "--model", model, "--effort", effort, prompt]
135
+ started = last_progress = time.monotonic()
136
+ last_progress_at = utc_now()
137
+ out = err = ""
138
+ record: dict[str, object] = {"stage": stage, "command": [*command[:-1], "<prompt>"], "timeoutKind": timeout_kind, "timeoutSeconds": timeout_seconds, "idleTimeoutSeconds": timeout_seconds if timeout_kind == "idle" else None, "startedAt": last_progress_at}
139
+ try:
140
+ process = subprocess.Popen(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
141
+ while True:
142
+ try:
143
+ new_out, new_err = process.communicate(timeout=0.5)
144
+ out, err = as_text(new_out), as_text(new_err)
145
+ code = int(process.returncode)
146
+ timed_out = False
147
+ break
148
+ except subprocess.TimeoutExpired as exc:
149
+ new_out, new_err = as_text(exc.stdout), as_text(exc.stderr)
150
+ if new_out != out or new_err != err:
151
+ out, err, last_progress, last_progress_at = new_out, new_err, time.monotonic(), utc_now()
152
+ reference = last_progress if timeout_kind == "idle" else started
153
+ if time.monotonic() - reference > timeout_seconds:
154
+ process.kill()
155
+ final_out, final_err = process.communicate()
156
+ out += as_text(final_out)[len(out):]
157
+ err += as_text(final_err)[len(err):]
158
+ code, timed_out = 124, True
159
+ break
160
+ except OSError as exc:
161
+ out, err, code, timed_out = "", str(exc), 1, False
162
+ record.update({"exitCode": code, "timedOut": timed_out, "stdout": out, "stderr": err, "finishedAt": utc_now(), "lastProgressAt": last_progress_at, "elapsedSeconds": round(time.monotonic() - started, 3)})
163
+ artifact["runs"].append(record)
164
+ return code
165
+
166
+ preflight_code = run_claude("preflight", "claude-ok", preflight_timeout, "wall-clock")
167
+ if preflight_code != 0:
168
+ write_artifact("preflight-timeout" if preflight_code == 124 else "preflight-failed", preflight_code)
169
+ print(f"CLAUDE_REVIEW_PREFLIGHT_EXIT={preflight_code}", file=sys.stderr)
170
+ print(f"CLAUDE_REVIEW_ARTIFACT={artifact_path}", file=sys.stderr)
171
+ raise SystemExit(preflight_code)
172
+ review_code = run_claude("review", prompt_path.read_text(encoding="utf-8"), review_idle_timeout, "idle")
173
+ write_artifact("idle-timeout" if review_code == 124 else ("success" if review_code == 0 else "failed"), review_code)
174
+ print(f"CLAUDE_REVIEW_EXIT={review_code}", file=sys.stderr)
175
+ print(f"CLAUDE_REVIEW_ARTIFACT={artifact_path}", file=sys.stderr)
176
+ raise SystemExit(review_code)
177
+ PY
178
+ ```
179
+
180
+ After the wrapper exits, verify both the exit code and artifact path before summarizing Claude's raw output:
181
+
182
+ ```bash
183
+ test -f "$CLAUDE_REVIEW_ARTIFACT" || {
184
+ echo "CLAUDE_REVIEW_ARTIFACT=missing: $CLAUDE_REVIEW_ARTIFACT" >&2
185
+ exit 1
186
+ }
187
+ echo "CLAUDE_REVIEW_ARTIFACT=$CLAUDE_REVIEW_ARTIFACT"
188
+ ```
189
+
190
+ #### Retry policy
191
+
192
+ Do not perform blind retries. Run the full review once. Retry only after diagnosing and changing a command-shape problem; record the reason and artifact path. Do not fall back to an unbounded whole-PR prompt.
193
+
194
+ Summarize findings with severity, evidence, model, artifact path, and whether you independently verified them.
195
+
196
+ ### Challenge
197
+
198
+ Ask Claude to argue against the current plan: hidden assumptions, failure modes, missing tests, and simpler alternatives.
199
+
200
+ ### Consult
201
+
202
+ Ask a focused repo question. Include only the necessary file paths and snippets; do not send secrets.
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: codex
3
+ description: "Codex CLI outside-voice wrapper for code review, plan challenge, or consultation from non-Codex hosts."
4
+ license: MIT
5
+ ---
6
+
7
+ # Codex outside voice
8
+
9
+ Use this skill from Claude or another non-Codex host when the user wants Codex to independently review a diff, challenge a plan, or answer a repo question. Keep Codex read-only by default and treat its answer as external advice until independently verified.
10
+
11
+ ## Auth check
12
+
13
+ ```bash
14
+ if ! codex login status >/dev/null 2>&1; then
15
+ echo "CODEX_AUTH=missing: run codex login before using the codex outside-voice skill"
16
+ exit 1
17
+ fi
18
+ echo "CODEX_AUTH=ok"
19
+ ```
20
+
21
+ ## Portable prompt file
22
+
23
+ ```bash
24
+ PROMPT_FILE=$(mktemp -t wp-codex-review.XXXXXX)
25
+ trap 'rm -f "$PROMPT_FILE"' EXIT
26
+ ```
27
+
28
+ ## Modes
29
+
30
+ ### Review
31
+
32
+ 1. Capture the current branch, base branch, and `git diff --stat`.
33
+ 2. Write a concise prompt asking Codex to find correctness, security, data-loss, and maintainability risks.
34
+ 3. Run Codex non-interactively in read-only mode:
35
+
36
+ ```bash
37
+ CODEX_REVIEW_EFFORT=${CODEX_REVIEW_EFFORT:-medium}
38
+ case "$CODEX_REVIEW_EFFORT" in
39
+ medium|high) ;;
40
+ *) echo "CODEX_REVIEW_EFFORT must be one of: medium, high" >&2; exit 2 ;;
41
+ esac
42
+ codex exec -c model_reasoning_effort="\"$CODEX_REVIEW_EFFORT\"" --sandbox read-only --cd "$PWD" - <"$PROMPT_FILE"
43
+ ```
44
+
45
+ 4. Summarize findings with severity, evidence, and whether you independently verified them.
46
+
47
+ ### Challenge
48
+
49
+ Ask Codex to argue against the current plan: hidden assumptions, failure modes, missing tests, and simpler alternatives.
50
+
51
+ ### Consult
52
+
53
+ Ask a focused repo question. Include only the necessary file paths and snippets; do not send secrets.