-
Notifications
You must be signed in to change notification settings - Fork 0
feat: align Codex plugin with Claude OAuth and bundled CLI #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
ec1959f
feat: align Codex plugin with Claude OAuth and bundled CLI
ravern 796d873
style: use explicit loop in MCP tool check
ravern afdffb3
chore: update URLs after repository rename
ravern dfff4ce
docs: match Claude plugin public documentation
ravern 2cdb93e
chore: rename marketplace to greptile-plugin
ravern File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| name: CLI Check | ||
|
|
||
|
|
||
| on: | ||
| push: | ||
| branches: [main] | ||
| paths: | ||
| - 'plugins/greptile/scripts/**' | ||
| - 'plugins/greptile/skills/**' | ||
| - '.github/workflows/cli-check.yml' | ||
| pull_request: | ||
| paths: | ||
| - 'plugins/greptile/scripts/**' | ||
| - 'plugins/greptile/skills/**' | ||
| - '.github/workflows/cli-check.yml' | ||
| schedule: | ||
| - cron: '41 8 * * *' | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| check: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 10 | ||
| steps: | ||
| - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 | ||
|
|
||
| - name: Recorded version is well formed | ||
| run: | | ||
| set -euo pipefail | ||
| version=$(tr -d '[:space:]' < plugins/greptile/scripts/greptile.version) | ||
| if ! printf '%s' "$version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then | ||
| echo "::error::plugins/greptile/scripts/greptile.version is not a plain semver string." | ||
| exit 1 | ||
| fi | ||
| printf '%s' "$version" > "$RUNNER_TEMP/version.txt" | ||
| echo "Recorded version $version" | ||
|
|
||
| - name: Bundle runs and reports the recorded version | ||
| run: | | ||
| set -euo pipefail | ||
| version=$(cat "$RUNNER_TEMP/version.txt") | ||
| reported=$(node plugins/greptile/scripts/greptile.mjs --version | tr -d '[:space:]') | ||
| if [ "$reported" != "$version" ]; then | ||
| echo "::error::Vendored bundle reports $reported but greptile.version records $version." | ||
| exit 1 | ||
| fi | ||
| echo "Bundle reports $reported." | ||
|
|
||
| - name: Login and review run outside the checkout | ||
| run: | | ||
| set -euo pipefail | ||
| mkdir -p "$RUNNER_TEMP/installed plugin/scripts" | ||
| cp plugins/greptile/scripts/greptile.mjs "$RUNNER_TEMP/installed plugin/scripts/greptile.mjs" | ||
| cd "$RUNNER_TEMP" | ||
| GREPTILE_NO_UPDATE_CHECK=1 node "$RUNNER_TEMP/installed plugin/scripts/greptile.mjs" login --help | ||
| GREPTILE_NO_AUTO_INSTALL=1 GREPTILE_NO_UPDATE_CHECK=1 node "$RUNNER_TEMP/installed plugin/scripts/greptile.mjs" review --agent --help | ||
|
|
||
| - name: Bundle is byte-identical to the published npm release | ||
| run: | | ||
| set -euo pipefail | ||
| version=$(cat "$RUNNER_TEMP/version.txt") | ||
| cd "$RUNNER_TEMP" | ||
| npm pack "greptile@$version" >/dev/null | ||
| tar -xzf "greptile-$version.tgz" | ||
| cd "$GITHUB_WORKSPACE" | ||
| published=$(shasum -a 256 "$RUNNER_TEMP/package/dist/greptile.js" | cut -d' ' -f1) | ||
| vendored=$(shasum -a 256 plugins/greptile/scripts/greptile.mjs | cut -d' ' -f1) | ||
| if [ "$published" != "$vendored" ]; then | ||
| 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" | ||
| exit 1 | ||
| fi | ||
| echo "Vendored bundle matches npm greptile@$version ($vendored)." | ||
|
|
||
| - name: Commands invoke the vendored bundle, not a fetched one | ||
| run: | | ||
| set -euo pipefail | ||
| for f in plugins/greptile/skills/review/SKILL.md plugins/greptile/skills/login/SKILL.md; do | ||
| block=$(awk '/^```/{fence = !fence; next} fence' "$f") | ||
| if [ -z "$block" ]; then | ||
| echo "::error::$f has no fenced command block to validate." | ||
| exit 1 | ||
| fi | ||
| if grep -q 'npx' <<<"$block"; then | ||
| echo "::error::$f still fetches the CLI with npx. The plugin vendors it; invoke <plugin-root>/scripts/greptile.mjs instead." | ||
| exit 1 | ||
| fi | ||
| if ! grep -qF 'node "<plugin-root>/scripts/greptile.mjs"' <<<"$block"; then | ||
| echo "::error::$f does not invoke the vendored bundle at <plugin-root>/scripts/greptile.mjs." | ||
| exit 1 | ||
| fi | ||
| if ! grep -qF 'GREPTILE_NO_UPDATE_CHECK=1 node "<plugin-root>/scripts/greptile.mjs"' <<<"$block"; then | ||
| 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." | ||
| exit 1 | ||
| fi | ||
| done | ||
| echo "Commands invoke the vendored bundle with the update check disabled." | ||
|
|
||
| - name: Review command suppresses the renderer download | ||
| run: | | ||
| set -euo pipefail | ||
| block=$(awk '/^```/{fence = !fence; next} fence' plugins/greptile/skills/review/SKILL.md) | ||
| if ! grep -qF 'GREPTILE_NO_AUTO_INSTALL=1 GREPTILE_NO_UPDATE_CHECK=1 node "<plugin-root>/scripts/greptile.mjs"' <<<"$block"; then | ||
| 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." | ||
| exit 1 | ||
| fi | ||
| echo "Review command suppresses the renderer download." |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| name: MCP Check | ||
| on: | ||
| push: | ||
| branches: [main] | ||
| paths: | ||
| - 'plugins/**' | ||
| - '.github/workflows/mcp-check.yml' | ||
| pull_request: | ||
| paths: | ||
| - 'plugins/**' | ||
| - '.github/workflows/mcp-check.yml' | ||
| schedule: | ||
| - cron: '17 8 * * *' | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| check: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 10 | ||
| steps: | ||
| - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 | ||
|
|
||
| - name: Resolve the declared server config | ||
| run: | | ||
| set -euo pipefail | ||
| config=plugins/greptile/.mcp.json | ||
| jq -e 'if has("mcpServers") then .mcpServers else . end' "$config" > "$RUNNER_TEMP/servers.json" | ||
| if ! jq -e '.greptile | type == "object"' "$RUNNER_TEMP/servers.json" >/dev/null; then | ||
| echo "::error::$config does not declare a \"greptile\" server object in either the bare or mcpServers shape." | ||
| exit 1 | ||
| fi | ||
| if ! jq -e '.greptile.url | type == "string" and startswith("https://")' "$RUNNER_TEMP/servers.json" >/dev/null; then | ||
| echo "::error::$config does not declare an https url for the greptile server." | ||
| exit 1 | ||
| fi | ||
| jq -r '.greptile.url' "$RUNNER_TEMP/servers.json" > "$RUNNER_TEMP/url.txt" | ||
| echo "Resolved $(cat "$RUNNER_TEMP/url.txt")" | ||
|
|
||
| - name: Config uses OAuth discovery | ||
| run: | | ||
| set -euo pipefail | ||
| if jq -e '.greptile | has("headers") or has("bearer_token_env_var")' "$RUNNER_TEMP/servers.json" >/dev/null; then | ||
| echo "::error::MCP config must use OAuth discovery, without headers or bearer_token_env_var." | ||
| exit 1 | ||
| fi | ||
| echo "No headers declared." | ||
|
|
||
| - name: Server still advertises OAuth | ||
| run: | | ||
| set -euo pipefail | ||
| url=$(cat "$RUNNER_TEMP/url.txt") | ||
| response=$(curl -sS -o /dev/null -D - -X POST "$url" \ | ||
| -H 'Content-Type: application/json' \ | ||
| -H 'Accept: application/json, text/event-stream' \ | ||
| -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_code_reviews","arguments":{}}}' \ | ||
| -w 'http_code=%{http_code}\n') | ||
| echo "$response" | ||
| if ! grep -q 'http_code=401' <<<"$response"; then | ||
| echo "::error::An unauthenticated tools/call on $url did not return 401. Codex starts the OAuth flow from that challenge." | ||
| exit 1 | ||
| fi | ||
| if ! grep -qi '^www-authenticate:.*resource_metadata=' <<<"$response"; then | ||
| echo "::error::$url returned 401 without an RFC 9728 www-authenticate challenge. Codex needs resource_metadata to discover the authorization server." | ||
| exit 1 | ||
| fi | ||
| echo "OAuth challenge present." | ||
|
|
||
| - name: README documents exactly the tools the server serves | ||
| run: | | ||
| set -euo pipefail | ||
| url=$(cat "$RUNNER_TEMP/url.txt") | ||
| curl -sS -X POST "$url" \ | ||
| -H 'Content-Type: application/json' \ | ||
| -H 'Accept: application/json, text/event-stream' \ | ||
| -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' > "$RUNNER_TEMP/tools.json" | ||
| python3 - <<'PY' | ||
| import json, os, re, sys | ||
|
|
||
| with open(os.path.join(os.environ['RUNNER_TEMP'], 'tools.json')) as fh: | ||
| payload = json.load(fh) | ||
| if 'result' not in payload: | ||
| sys.exit(f"::error::tools/list returned no result: {json.dumps(payload)[:400]}") | ||
| served = set() | ||
| for tool in payload['result']['tools']: | ||
| served.add(tool['name']) | ||
| if not served: | ||
| sys.exit('::error::tools/list returned no tools.') | ||
|
|
||
| readme = open('plugins/greptile/README.md').read() | ||
| documented = set(re.findall(r'^- `([a-z_]+)`', readme, re.M)) | ||
| documented |= set(re.findall(r'/ `([a-z_]+)`', readme)) | ||
|
|
||
| missing = sorted(served - documented) | ||
| extra = sorted(documented - served) | ||
| if missing: | ||
| print(f"::error::README does not document: {', '.join(missing)}") | ||
| if extra: | ||
| print(f"::error::README documents tools the server does not serve: {', '.join(extra)}") | ||
| if missing or extra: | ||
| sys.exit(1) | ||
| print(f"README documents all {len(served)} served tools.") | ||
| PY |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,41 +1,19 @@ | ||
| # Greptile Codex Plugin | ||
| # Greptile for Codex | ||
|
|
||
| Use Greptile reviews, MCP tools, and agent skills in OpenAI Codex. | ||
| The official [Greptile](https://greptile.com) plugin for Codex. | ||
|
|
||
| ## Install | ||
| This repository is a Codex plugin marketplace. Add it directly: | ||
|
|
||
| ```bash | ||
| codex plugin marketplace add greptileai/greptile-codex-plugin | ||
| codex plugin add greptile@greptile-codex-plugins | ||
| ``` | ||
|
|
||
| Set your Greptile API key in the shell that launches Codex: | ||
|
|
||
| ```bash | ||
| export GREPTILE_API_KEY="your-api-key" | ||
| codex plugin marketplace add greptileai/codex-plugin | ||
| codex plugin add greptile@greptile-plugin | ||
| ``` | ||
|
|
||
| Start a new Codex task after installation. | ||
|
|
||
| ## Included skills | ||
|
|
||
| - `check-pr`: inspect PR readiness and unresolved review feedback. | ||
| - `cli-review`: run a Greptile CLI review from a local checkout. | ||
| - `greploop`: fix feedback and re-review until the PR is clean. | ||
|
|
||
| The plugin also configures the public Greptile MCP endpoint at `https://api.greptile.com/mcp`. | ||
|
|
||
| ## Verify | ||
|
|
||
| ```bash | ||
| codex plugin marketplace list | ||
| codex plugin list | ||
| ``` | ||
| The plugin gives Codex two ways to work with Greptile: | ||
|
|
||
| The marketplace should appear as `greptile-codex-plugins`, with the `greptile` plugin installed. | ||
| - the **Greptile MCP server**, for reading and resolving review results and for searching your knowledge base and coding patterns | ||
| - the **Greptile CLI**, for reviewing your working branch before a pull request exists | ||
|
|
||
| ## Build provenance | ||
| Both authenticate over OAuth against your Greptile account. There is no API key to create and nothing to install — the CLI ships with the plugin, so it needs no npm or Homebrew install, only Node 22+ on your machine. | ||
|
|
||
| - Skills source: https://github.com/greptileai/skills.git | ||
| - Branch: main | ||
| - Commit: 646e2dfad81e5157e97daecc802b68d3d2c4d1e4 | ||
| See [`plugins/greptile`](./plugins/greptile) for setup, workflows, and the full tool list. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.