Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions .github/workflows/cli-check.yml
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."
103 changes: 103 additions & 0 deletions .github/workflows/mcp-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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 = {t['name'] for t in payload['result']['tools']}
Comment thread
ravern marked this conversation as resolved.
Outdated
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
48 changes: 20 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,33 @@
# 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
```sh
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:
The plugin gives Codex two ways to work with Greptile:

```bash
export GREPTILE_API_KEY="your-api-key"
```

Start a new Codex task after installation.

## Included skills
- the **Greptile MCP server**, for reading review results and searching your
knowledge base and coding patterns
- the **Greptile CLI**, for reviewing your working branch before a pull request exists

- `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.
Both authenticate over OAuth against your Greptile account, with separate
sign-ins for MCP and CLI. There is no API key to create and no separate CLI
installation: the CLI ships with the plugin and requires Node 22+.

The plugin also configures the public Greptile MCP endpoint at `https://api.greptile.com/mcp`.

## Verify

```bash
codex plugin marketplace list
codex plugin list
```
See [`plugins/greptile`](./plugins/greptile) for setup, workflows, and the full tool list.

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

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

- Skills source: https://github.com/greptileai/skills.git
- Branch: main
- Commit: 646e2dfad81e5157e97daecc802b68d3d2c4d1e4
To update the CLI, copy `dist/greptile.js` from the published `greptile` npm
package to `plugins/greptile/scripts/greptile.mjs`, update `greptile.version`,
and bump the plugin version in `plugins/greptile/.codex-plugin/plugin.json`.
Open a PR and run CLI Check and MCP Check. Do not rebuild the npm artifact locally.
16 changes: 8 additions & 8 deletions plugins/greptile/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "greptile",
"version": "0.1.0",
"description": "Use Greptile review feedback in Codex to inspect PRs, fix comments, and improve coding standards.",
"version": "0.2.0",
"description": "Review your working branch, read Greptile review results, and search your organization’s knowledge base from Codex.",
"author": {
"name": "Greptile",
"url": "https://www.greptile.com/"
},
"homepage": "https://www.greptile.com/docs",
"homepage": "https://www.greptile.com/docs/mcp-v2/overview",
"repository": "https://github.com/greptileai/greptile-codex-plugin",
"license": "MIT",
"keywords": [
Expand All @@ -19,8 +19,8 @@
"mcpServers": "./.mcp.json",
"interface": {
"displayName": "Greptile",
"shortDescription": "Fix Greptile review feedback from Codex",
"longDescription": "Use Greptile review signal from Codex to check pull requests, iterate on review comments, and improve repo-specific coding standards.",
"shortDescription": "Review code and explore Greptile feedback",
"longDescription": "Review your working branch, read Greptile review results, and search your organization’s knowledge base from Codex.",
"developerName": "Greptile",
"category": "Developer Tools",
"capabilities": [
Expand All @@ -34,9 +34,9 @@
"composerIcon": "./assets/icon.svg",
"logo": "./assets/logo.svg",
"defaultPrompt": [
"Check this PR with Greptile",
"Run a Greptile CLI review",
"Run greploop until this PR is clean"
"Review my current branch with Greptile",
"Show Greptile’s comments on this PR",
"Search our knowledge base for how authentication works"
]
}
}
4 changes: 1 addition & 3 deletions plugins/greptile/.mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
"mcpServers": {
"greptile": {
"type": "http",
"url": "https://api.greptile.com/mcp",
"bearer_token_env_var": "GREPTILE_API_KEY",
"note": "Greptile MCP v2. Uses GREPTILE_API_KEY from organization settings. Use for PR comments, review state, feedback search, custom context, and review analytics."
"url": "https://api.greptile.com/mcp"
}
}
}
Loading
Loading