Skip to content

Commit ec1959f

Browse files
committed
feat: align Codex plugin with Claude OAuth and bundled CLI
1 parent 906dfa9 commit ec1959f

17 files changed

Lines changed: 1387 additions & 1209 deletions

File tree

.github/workflows/cli-check.yml

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
name: CLI Check
2+
3+
4+
on:
5+
push:
6+
branches: [main]
7+
paths:
8+
- 'plugins/greptile/scripts/**'
9+
- 'plugins/greptile/skills/**'
10+
- '.github/workflows/cli-check.yml'
11+
pull_request:
12+
paths:
13+
- 'plugins/greptile/scripts/**'
14+
- 'plugins/greptile/skills/**'
15+
- '.github/workflows/cli-check.yml'
16+
schedule:
17+
- cron: '41 8 * * *'
18+
workflow_dispatch:
19+
20+
permissions:
21+
contents: read
22+
23+
jobs:
24+
check:
25+
runs-on: ubuntu-latest
26+
timeout-minutes: 10
27+
steps:
28+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
29+
30+
- name: Recorded version is well formed
31+
run: |
32+
set -euo pipefail
33+
version=$(tr -d '[:space:]' < plugins/greptile/scripts/greptile.version)
34+
if ! printf '%s' "$version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
35+
echo "::error::plugins/greptile/scripts/greptile.version is not a plain semver string."
36+
exit 1
37+
fi
38+
printf '%s' "$version" > "$RUNNER_TEMP/version.txt"
39+
echo "Recorded version $version"
40+
41+
- name: Bundle runs and reports the recorded version
42+
run: |
43+
set -euo pipefail
44+
version=$(cat "$RUNNER_TEMP/version.txt")
45+
reported=$(node plugins/greptile/scripts/greptile.mjs --version | tr -d '[:space:]')
46+
if [ "$reported" != "$version" ]; then
47+
echo "::error::Vendored bundle reports $reported but greptile.version records $version."
48+
exit 1
49+
fi
50+
echo "Bundle reports $reported."
51+
52+
- name: Login and review run outside the checkout
53+
run: |
54+
set -euo pipefail
55+
mkdir -p "$RUNNER_TEMP/installed plugin/scripts"
56+
cp plugins/greptile/scripts/greptile.mjs "$RUNNER_TEMP/installed plugin/scripts/greptile.mjs"
57+
cd "$RUNNER_TEMP"
58+
GREPTILE_NO_UPDATE_CHECK=1 node "$RUNNER_TEMP/installed plugin/scripts/greptile.mjs" login --help
59+
GREPTILE_NO_AUTO_INSTALL=1 GREPTILE_NO_UPDATE_CHECK=1 node "$RUNNER_TEMP/installed plugin/scripts/greptile.mjs" review --agent --help
60+
61+
- name: Bundle is byte-identical to the published npm release
62+
run: |
63+
set -euo pipefail
64+
version=$(cat "$RUNNER_TEMP/version.txt")
65+
cd "$RUNNER_TEMP"
66+
npm pack "greptile@$version" >/dev/null
67+
tar -xzf "greptile-$version.tgz"
68+
cd "$GITHUB_WORKSPACE"
69+
published=$(shasum -a 256 "$RUNNER_TEMP/package/dist/greptile.js" | cut -d' ' -f1)
70+
vendored=$(shasum -a 256 plugins/greptile/scripts/greptile.mjs | cut -d' ' -f1)
71+
if [ "$published" != "$vendored" ]; then
72+
echo "::error::Vendored bundle does not match npm greptile@$version. published=$published vendored=$vendored. Re-vendor with: npm pack greptile@$version && tar -xzf greptile-$version.tgz && cp package/dist/greptile.js plugins/greptile/scripts/greptile.mjs"
73+
exit 1
74+
fi
75+
echo "Vendored bundle matches npm greptile@$version ($vendored)."
76+
77+
- name: Commands invoke the vendored bundle, not a fetched one
78+
run: |
79+
set -euo pipefail
80+
for f in plugins/greptile/skills/review/SKILL.md plugins/greptile/skills/login/SKILL.md; do
81+
block=$(awk '/^```/{fence = !fence; next} fence' "$f")
82+
if [ -z "$block" ]; then
83+
echo "::error::$f has no fenced command block to validate."
84+
exit 1
85+
fi
86+
if grep -q 'npx' <<<"$block"; then
87+
echo "::error::$f still fetches the CLI with npx. The plugin vendors it; invoke <plugin-root>/scripts/greptile.mjs instead."
88+
exit 1
89+
fi
90+
if ! grep -qF 'node "<plugin-root>/scripts/greptile.mjs"' <<<"$block"; then
91+
echo "::error::$f does not invoke the vendored bundle at <plugin-root>/scripts/greptile.mjs."
92+
exit 1
93+
fi
94+
if ! grep -qF 'GREPTILE_NO_UPDATE_CHECK=1 node "<plugin-root>/scripts/greptile.mjs"' <<<"$block"; then
95+
echo "::error::$f invokes the vendored bundle without GREPTILE_NO_UPDATE_CHECK=1. CLI versions that predate plugin-install detection read this path as a standalone install and name an installer that cannot update the plugin's copy. --agent already suppresses the notice, so this is a second line of defence for invocations that drop it."
96+
exit 1
97+
fi
98+
done
99+
echo "Commands invoke the vendored bundle with the update check disabled."
100+
101+
- name: Review command suppresses the renderer download
102+
run: |
103+
set -euo pipefail
104+
block=$(awk '/^```/{fence = !fence; next} fence' plugins/greptile/skills/review/SKILL.md)
105+
if ! grep -qF 'GREPTILE_NO_AUTO_INSTALL=1 GREPTILE_NO_UPDATE_CHECK=1 node "<plugin-root>/scripts/greptile.mjs"' <<<"$block"; then
106+
echo "::error::plugins/greptile/skills/review/SKILL.md must invoke the bundle with GREPTILE_NO_AUTO_INSTALL=1 ahead of GREPTILE_NO_UPDATE_CHECK=1. README.md tells users the plugin never downloads the mmdr renderer; this is what makes that true."
107+
exit 1
108+
fi
109+
echo "Review command suppresses the renderer download."

.github/workflows/mcp-check.yml

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
name: MCP Check
2+
on:
3+
push:
4+
branches: [main]
5+
paths:
6+
- 'plugins/**'
7+
- '.github/workflows/mcp-check.yml'
8+
pull_request:
9+
paths:
10+
- 'plugins/**'
11+
- '.github/workflows/mcp-check.yml'
12+
schedule:
13+
- cron: '17 8 * * *'
14+
workflow_dispatch:
15+
16+
permissions:
17+
contents: read
18+
19+
jobs:
20+
check:
21+
runs-on: ubuntu-latest
22+
timeout-minutes: 10
23+
steps:
24+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
25+
26+
- name: Resolve the declared server config
27+
run: |
28+
set -euo pipefail
29+
config=plugins/greptile/.mcp.json
30+
jq -e 'if has("mcpServers") then .mcpServers else . end' "$config" > "$RUNNER_TEMP/servers.json"
31+
if ! jq -e '.greptile | type == "object"' "$RUNNER_TEMP/servers.json" >/dev/null; then
32+
echo "::error::$config does not declare a \"greptile\" server object in either the bare or mcpServers shape."
33+
exit 1
34+
fi
35+
if ! jq -e '.greptile.url | type == "string" and startswith("https://")' "$RUNNER_TEMP/servers.json" >/dev/null; then
36+
echo "::error::$config does not declare an https url for the greptile server."
37+
exit 1
38+
fi
39+
jq -r '.greptile.url' "$RUNNER_TEMP/servers.json" > "$RUNNER_TEMP/url.txt"
40+
echo "Resolved $(cat "$RUNNER_TEMP/url.txt")"
41+
42+
- name: Config uses OAuth discovery
43+
run: |
44+
set -euo pipefail
45+
if jq -e '.greptile | has("headers") or has("bearer_token_env_var")' "$RUNNER_TEMP/servers.json" >/dev/null; then
46+
echo "::error::MCP config must use OAuth discovery, without headers or bearer_token_env_var."
47+
exit 1
48+
fi
49+
echo "No headers declared."
50+
51+
- name: Server still advertises OAuth
52+
run: |
53+
set -euo pipefail
54+
url=$(cat "$RUNNER_TEMP/url.txt")
55+
response=$(curl -sS -o /dev/null -D - -X POST "$url" \
56+
-H 'Content-Type: application/json' \
57+
-H 'Accept: application/json, text/event-stream' \
58+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_code_reviews","arguments":{}}}' \
59+
-w 'http_code=%{http_code}\n')
60+
echo "$response"
61+
if ! grep -q 'http_code=401' <<<"$response"; then
62+
echo "::error::An unauthenticated tools/call on $url did not return 401. Codex starts the OAuth flow from that challenge."
63+
exit 1
64+
fi
65+
if ! grep -qi '^www-authenticate:.*resource_metadata=' <<<"$response"; then
66+
echo "::error::$url returned 401 without an RFC 9728 www-authenticate challenge. Codex needs resource_metadata to discover the authorization server."
67+
exit 1
68+
fi
69+
echo "OAuth challenge present."
70+
71+
- name: README documents exactly the tools the server serves
72+
run: |
73+
set -euo pipefail
74+
url=$(cat "$RUNNER_TEMP/url.txt")
75+
curl -sS -X POST "$url" \
76+
-H 'Content-Type: application/json' \
77+
-H 'Accept: application/json, text/event-stream' \
78+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' > "$RUNNER_TEMP/tools.json"
79+
python3 - <<'PY'
80+
import json, os, re, sys
81+
82+
with open(os.path.join(os.environ['RUNNER_TEMP'], 'tools.json')) as fh:
83+
payload = json.load(fh)
84+
if 'result' not in payload:
85+
sys.exit(f"::error::tools/list returned no result: {json.dumps(payload)[:400]}")
86+
served = {t['name'] for t in payload['result']['tools']}
87+
if not served:
88+
sys.exit('::error::tools/list returned no tools.')
89+
90+
readme = open('plugins/greptile/README.md').read()
91+
documented = set(re.findall(r'^- `([a-z_]+)`', readme, re.M))
92+
documented |= set(re.findall(r'/ `([a-z_]+)`', readme))
93+
94+
missing = sorted(served - documented)
95+
extra = sorted(documented - served)
96+
if missing:
97+
print(f"::error::README does not document: {', '.join(missing)}")
98+
if extra:
99+
print(f"::error::README documents tools the server does not serve: {', '.join(extra)}")
100+
if missing or extra:
101+
sys.exit(1)
102+
print(f"README documents all {len(served)} served tools.")
103+
PY

README.md

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,33 @@
1-
# Greptile Codex Plugin
1+
# Greptile for Codex
22

3-
Use Greptile reviews, MCP tools, and agent skills in OpenAI Codex.
3+
The official [Greptile](https://greptile.com) plugin for Codex.
44

5-
## Install
5+
This repository is a Codex plugin marketplace. Add it directly:
66

7-
```bash
7+
```sh
88
codex plugin marketplace add greptileai/greptile-codex-plugin
99
codex plugin add greptile@greptile-codex-plugins
1010
```
1111

12-
Set your Greptile API key in the shell that launches Codex:
12+
The plugin gives Codex two ways to work with Greptile:
1313

14-
```bash
15-
export GREPTILE_API_KEY="your-api-key"
16-
```
17-
18-
Start a new Codex task after installation.
19-
20-
## Included skills
14+
- the **Greptile MCP server**, for reading review results and searching your
15+
knowledge base and coding patterns
16+
- the **Greptile CLI**, for reviewing your working branch before a pull request exists
2117

22-
- `check-pr`: inspect PR readiness and unresolved review feedback.
23-
- `cli-review`: run a Greptile CLI review from a local checkout.
24-
- `greploop`: fix feedback and re-review until the PR is clean.
18+
Both authenticate over OAuth against your Greptile account, with separate
19+
sign-ins for MCP and CLI. There is no API key to create and no separate CLI
20+
installation: the CLI ships with the plugin and requires Node 22+.
2521

26-
The plugin also configures the public Greptile MCP endpoint at `https://api.greptile.com/mcp`.
27-
28-
## Verify
29-
30-
```bash
31-
codex plugin marketplace list
32-
codex plugin list
33-
```
22+
See [`plugins/greptile`](./plugins/greptile) for setup, workflows, and the full tool list.
3423

35-
The marketplace should appear as `greptile-codex-plugins`, with the `greptile` plugin installed.
24+
## Maintenance
3625

37-
## Build provenance
26+
Edit this repository directly. The plugin is no longer generated from another
27+
repository. Keep the marketplace name `greptile-codex-plugins` and plugin name
28+
`greptile` stable for existing installations.
3829

39-
- Skills source: https://github.com/greptileai/skills.git
40-
- Branch: main
41-
- Commit: 646e2dfad81e5157e97daecc802b68d3d2c4d1e4
30+
To update the CLI, copy `dist/greptile.js` from the published `greptile` npm
31+
package to `plugins/greptile/scripts/greptile.mjs`, update `greptile.version`,
32+
and bump the plugin version in `plugins/greptile/.codex-plugin/plugin.json`.
33+
Open a PR and run CLI Check and MCP Check. Do not rebuild the npm artifact locally.

plugins/greptile/.codex-plugin/plugin.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
22
"name": "greptile",
3-
"version": "0.1.0",
4-
"description": "Use Greptile review feedback in Codex to inspect PRs, fix comments, and improve coding standards.",
3+
"version": "0.2.0",
4+
"description": "Review your working branch, read Greptile review results, and search your organization’s knowledge base from Codex.",
55
"author": {
66
"name": "Greptile",
77
"url": "https://www.greptile.com/"
88
},
9-
"homepage": "https://www.greptile.com/docs",
9+
"homepage": "https://www.greptile.com/docs/mcp-v2/overview",
1010
"repository": "https://github.com/greptileai/greptile-codex-plugin",
1111
"license": "MIT",
1212
"keywords": [
@@ -19,8 +19,8 @@
1919
"mcpServers": "./.mcp.json",
2020
"interface": {
2121
"displayName": "Greptile",
22-
"shortDescription": "Fix Greptile review feedback from Codex",
23-
"longDescription": "Use Greptile review signal from Codex to check pull requests, iterate on review comments, and improve repo-specific coding standards.",
22+
"shortDescription": "Review code and explore Greptile feedback",
23+
"longDescription": "Review your working branch, read Greptile review results, and search your organization’s knowledge base from Codex.",
2424
"developerName": "Greptile",
2525
"category": "Developer Tools",
2626
"capabilities": [
@@ -34,9 +34,9 @@
3434
"composerIcon": "./assets/icon.svg",
3535
"logo": "./assets/logo.svg",
3636
"defaultPrompt": [
37-
"Check this PR with Greptile",
38-
"Run a Greptile CLI review",
39-
"Run greploop until this PR is clean"
37+
"Review my current branch with Greptile",
38+
"Show Greptile’s comments on this PR",
39+
"Search our knowledge base for how authentication works"
4040
]
4141
}
4242
}

plugins/greptile/.mcp.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
"mcpServers": {
33
"greptile": {
44
"type": "http",
5-
"url": "https://api.greptile.com/mcp",
6-
"bearer_token_env_var": "GREPTILE_API_KEY",
7-
"note": "Greptile MCP v2. Uses GREPTILE_API_KEY from organization settings. Use for PR comments, review state, feedback search, custom context, and review analytics."
5+
"url": "https://api.greptile.com/mcp"
86
}
97
}
108
}

0 commit comments

Comments
 (0)