-
Notifications
You must be signed in to change notification settings - Fork 5
236 lines (201 loc) · 9.04 KB
/
Copy pathcompetitive-update.yml
File metadata and controls
236 lines (201 loc) · 9.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
name: Competitive Update
# ---------------------------------------------------------------
# daily competitive intelligence pipeline.
# merges competitor release checking (from collect_competitive.py)
# with pricing page comparison into a single workflow.
#
# sources:
# - competitor releases: openai/codex, cursor changelog, gemini-cli
# - pricing pages: cursor, antigravity, copilot
#
# daily competitor pricing and feature comparison
#
# cost: ~$0.02-0.05 per run (haiku, stripped text input)
# tested with: claude code v2.1.122
# ---------------------------------------------------------------
on:
schedule:
- cron: '0 12 * * *' # daily at 12:00 UTC
workflow_dispatch:
permissions:
contents: write
pull-requests: write
concurrency:
group: "competitive-update"
cancel-in-progress: false
env:
CLAUDE_MODEL: claude-haiku-4-5-20251001
jobs:
competitive-check:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Check API key
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
if [ -z "$ANTHROPIC_API_KEY" ]; then
echo "::warning::ANTHROPIC_API_KEY not available -- skipping"
exit 1
fi
- uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install dependencies
run: |
pip install anthropic==0.49.0 requests==2.32.3
npm install -g @anthropic-ai/claude-code
# ---- phase 1: check competitor releases ----
- name: Check competitor releases
id: releases
run: python .github/scripts/collect_competitive.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
# ---- phase 2: fetch competitor pricing pages ----
- name: Fetch competitor pricing pages
id: pricing
run: |
mkdir -p /tmp/competitor-data
PRICING_CHANGED=false
fetch_with_retry() {
local URL="$1"
local NAME="$2"
local OUTPUT="/tmp/competitor-data/${NAME}-pricing.txt"
for attempt in 1 2; do
CONTENT=$(curl -sL --connect-timeout 10 --max-time 30 "$URL" 2>/dev/null \
| python3 -c "import sys,re; t=sys.stdin.read(); t=re.sub(r'<script[^>]*>.*?</script>','',t,flags=re.S); t=re.sub(r'<style[^>]*>.*?</style>','',t,flags=re.S); t=re.sub(r'<[^>]+>',' ',t); t=re.sub(r'\s+',' ',t).strip(); print(t[:8000])" \
|| true)
# validate: must be >500 chars and contain price-related keywords
CHAR_COUNT=$(echo "$CONTENT" | wc -c)
if [ "$CHAR_COUNT" -gt 500 ] && echo "$CONTENT" | grep -qiE '(\$[0-9]|price|pricing|per month|/mo|free|plan|tier|pro|enterprise)'; then
echo "$CONTENT" > "$OUTPUT"
echo " fetched $NAME: ${CHAR_COUNT} chars (attempt $attempt)"
return 0
fi
if [ "$attempt" -eq 1 ]; then
echo " retrying $NAME (got ${CHAR_COUNT} chars, missing pricing keywords)..."
sleep 2
fi
done
echo " WARN: $NAME fetch failed after 2 attempts"
echo "" > "$OUTPUT"
return 0
}
for url_name in \
"https://www.cursor.com/pricing cursor" \
"https://antigravity.google/pricing antigravity" \
"https://github.com/features/copilot copilot"; do
URL=$(echo "$url_name" | cut -d' ' -f1)
NAME=$(echo "$url_name" | cut -d' ' -f2)
fetch_with_retry "$URL" "$NAME"
done
# verify at least one file has content
TOTAL=$(cat /tmp/competitor-data/*.txt 2>/dev/null | wc -c)
if [ "$TOTAL" -lt 100 ]; then
echo "no competitor pricing data fetched -- all fetches failed"
echo "pricing_fetched=false" >> "$GITHUB_OUTPUT"
else
echo "fetched $TOTAL chars of competitor pricing data"
echo "pricing_fetched=true" >> "$GITHUB_OUTPUT"
fi
# ---- phase 3: compare pricing against current docs ----
- name: Compare pricing against current docs
if: steps.pricing.outputs.pricing_fetched == 'true'
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
claude -p --model "$CLAUDE_MODEL" \
"you are checking if competitor pricing/features have changed.
read the current comparison docs in docs/comparisons/.
then read the freshly fetched pricing text in /tmp/competitor-data/.
also read /tmp/competitive_changes.json for any new competitor releases.
for each competitor (cursor, antigravity, copilot):
1. extract current pricing tiers and key features from the fetched text
2. compare against what's in docs/comparisons/
3. if anything changed (prices, tier names, features, limits), update the comparison doc
4. if there are new releases in competitive_changes.json, note them in the relevant doc
rules:
- only update facts that clearly changed -- dont rewrite style
- keep the existing doc structure and voice (lowercase, no fluff)
- add a comment like '<!-- updated YYYY-MM-DD -->' near changed sections
- if a fetch failed (empty file), skip that competitor
- be conservative -- if you're not sure something changed, dont update it
write a summary of changes to /tmp/comparison-changes.txt.
if nothing changed, write 'no changes detected'." \
--allowedTools Read,Write,Edit,Glob,Grep
# ---- phase 4: persist state from release checker ----
- name: Persist state
run: |
git config user.name "competitive-update[bot]"
git config user.email "competitive-update[bot]@users.noreply.github.com"
git add .github/state/
git diff --cached --quiet && echo "no state changes" && exit 0
git commit -m "chore: update competitive watcher state"
git push origin main
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ---- phase 5: create PR if changes detected ----
- name: Create PR if changes exist
id: create_pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if git diff --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then
echo "no competitive changes detected"
exit 0
fi
BRANCH="auto/competitive-update-$(date +%Y%m%d)"
git checkout -b "$BRANCH"
git add docs/comparisons/
git diff --cached --quiet && echo "nothing to commit" && exit 0
git commit -m "docs: competitive intelligence update $(date +%Y-%m-%d)"
git push -u origin "$BRANCH"
# build PR body -- use --body-file to avoid shell injection from LLM content
{
echo "## automated competitive update"
echo ""
echo "### sources checked"
echo "- competitor releases (openai/codex, cursor, gemini-cli)"
echo "- pricing pages (cursor, antigravity, copilot)"
echo ""
echo "### changes"
cat /tmp/comparison-changes.txt 2>/dev/null || echo "see diff"
echo ""
echo "---"
echo "automated -- verify pricing data and release info before merging."
} > /tmp/pr-body.md
if ! PR_URL=$(gh pr create \
--title "docs: competitive update $(date +%Y-%m-%d)" \
--body-file /tmp/pr-body.md \
--base main \
--draft 2>/tmp/pr-create-err.txt); then
echo "::error::PR creation failed: $(cat /tmp/pr-create-err.txt)"
exit 1
fi
PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$')
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"
# enable auto-merge after CI passes
gh pr merge "$PR_NUMBER" --auto --merge 2>/dev/null || echo "auto-merge not available (branch protection may not be configured)"
echo "Draft PR created with auto-merge: $PR_URL"
# ---- phase 6: close superseded PRs ----
- name: Close superseded PRs
if: steps.create_pr.outputs.pr_number
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
NEW_PR="${{ steps.create_pr.outputs.pr_number }}"
# find and close old comparison and competitive PRs
for prefix in "auto/comparison" "auto/competitive"; do
gh pr list --state open --json number,headRefName --jq '.[] | select(.headRefName | startswith("'"$prefix"'")) | .number' \
| while read -r OLD_PR; do
if [ "$OLD_PR" != "$NEW_PR" ]; then
echo "closing superseded PR #$OLD_PR"
gh pr close "$OLD_PR" --comment "superseded by #$NEW_PR" --delete-branch 2>/dev/null || true
fi
done
done