mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-31 04:07:52 +00:00
fix(recipes/x-to-brain): use /users/by/username for app-only bearer health check
Takeover of #2343. /users/me requires user-context OAuth and always fails under the app-only bearer the recipe collects. Health check + setup curls now use /users/by/username/$X_HANDLE, with X_HANDLE declared in secrets so the installer prompts for it. Recipe version 0.8.1 -> 0.8.2. Co-authored-by: ethanbeard <ethanbeard@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
ethanbeard
Claude Fable 5
parent
0612b0daa8
commit
5df66c3855
+13
-8
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: x-to-brain
|
||||
name: X-to-Brain
|
||||
version: 0.8.1
|
||||
version: 0.8.2
|
||||
description: Twitter timeline, mentions, and keyword monitoring flow into brain pages. Tracks deletions, engagement velocity, OCR on images, and real-time alerts.
|
||||
category: sense
|
||||
requires: []
|
||||
@@ -9,9 +9,12 @@ secrets:
|
||||
- name: X_BEARER_TOKEN
|
||||
description: X API v2 Bearer token (Basic tier minimum, $200/mo for full archive search)
|
||||
where: https://developer.x.com/en/portal/dashboard — create a project + app, copy the Bearer Token from "Keys and tokens"
|
||||
- name: X_HANDLE
|
||||
description: Your X username without the @ (used for the app-only health check — /users/me requires user-context OAuth, which app-only bearer tokens don't have)
|
||||
where: Your X profile — the handle in your profile URL, e.g. x.com/yourhandle → yourhandle
|
||||
health_checks:
|
||||
- type: http
|
||||
url: "https://api.x.com/2/users/me"
|
||||
url: "https://api.x.com/2/users/by/username/$X_HANDLE"
|
||||
auth: bearer
|
||||
auth_token: "$X_BEARER_TOKEN"
|
||||
label: "X API"
|
||||
@@ -110,15 +113,17 @@ Tell the user:
|
||||
4. Inside the project, create a new App
|
||||
5. Go to the app's 'Keys and tokens' tab
|
||||
6. Under 'Bearer Token', click 'Generate' (or 'Regenerate')
|
||||
7. Copy the Bearer Token and paste it to me
|
||||
7. Copy the Bearer Token and paste it to me, along with your X handle (without the @)
|
||||
|
||||
Note: Free tier gives read-only access with low limits. Basic tier ($200/mo)
|
||||
gives search/recent endpoint and higher limits. Pro tier gets full archive search."
|
||||
|
||||
Validate immediately:
|
||||
Set both `X_BEARER_TOKEN` and `X_HANDLE` in the environment. Validate immediately
|
||||
(app-only bearer tokens cannot call `/users/me` — that endpoint requires
|
||||
user-context OAuth — so validation uses the by-username lookup):
|
||||
```bash
|
||||
curl -sf -H "Authorization: Bearer $X_BEARER_TOKEN" \
|
||||
"https://api.x.com/2/users/me" \
|
||||
"https://api.x.com/2/users/by/username/$X_HANDLE" \
|
||||
&& echo "PASS: X API connected" \
|
||||
|| echo "FAIL: X API token invalid"
|
||||
```
|
||||
@@ -134,10 +139,10 @@ starting with 'AAA...', (3) if you just created the app, the token is valid imme
|
||||
```bash
|
||||
# Look up the user's X user ID from their handle
|
||||
curl -sf -H "Authorization: Bearer $X_BEARER_TOKEN" \
|
||||
"https://api.x.com/2/users/by/username/USERNAME" | grep -o '"id":"[^"]*"'
|
||||
"https://api.x.com/2/users/by/username/$X_HANDLE" | grep -o '"id":"[^"]*"'
|
||||
```
|
||||
|
||||
Ask the user for their X handle (e.g., @yourhandle). Look up their user ID.
|
||||
Look up the user ID from the handle collected in Step 1.
|
||||
Save it — the collector needs the numeric ID, not the handle.
|
||||
|
||||
### Step 3: Configure the Collector
|
||||
@@ -205,7 +210,7 @@ The agent should review collected data 2-3x daily and run enrichment.
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.gbrain/integrations/x-to-brain
|
||||
echo '{"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","event":"setup_complete","source_version":"0.8.1","status":"ok","details":{"user_id":"X_USER_ID"}}' >> ~/.gbrain/integrations/x-to-brain/heartbeat.jsonl
|
||||
echo '{"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","event":"setup_complete","source_version":"0.8.2","status":"ok","details":{"user_id":"X_USER_ID"}}' >> ~/.gbrain/integrations/x-to-brain/heartbeat.jsonl
|
||||
```
|
||||
|
||||
## Production Patterns (v0.8.1)
|
||||
|
||||
@@ -263,6 +263,35 @@ describe('twilio-voice-brain recipe', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('x-to-brain recipe', () => {
|
||||
test('health check works with an app-only bearer token (#2343)', () => {
|
||||
const { readFileSync } = require('fs');
|
||||
const content = readFileSync(
|
||||
new URL('../recipes/x-to-brain.md', import.meta.url),
|
||||
'utf-8'
|
||||
);
|
||||
const recipe = parseRecipe(content, 'x-to-brain.md');
|
||||
expect(recipe).not.toBeNull();
|
||||
const httpChecks = recipe!.frontmatter.health_checks
|
||||
.filter((c: any) => typeof c === 'object' && c.type === 'http');
|
||||
expect(httpChecks.length).toBeGreaterThan(0);
|
||||
const secretNames = new Set(recipe!.frontmatter.secrets.map((s: any) => s.name));
|
||||
for (const check of httpChecks as any[]) {
|
||||
// /users/me requires user-context OAuth; the recipe only collects an
|
||||
// app-only bearer token, so probing it always fails.
|
||||
expect(check.url).not.toContain('/users/me');
|
||||
// Every $VAR the check expands must be declared in secrets, or the
|
||||
// installer never prompts for it and the check fails for everyone.
|
||||
const vars = [check.url, check.auth_token, check.auth_user, check.auth_pass]
|
||||
.filter((v: unknown): v is string => typeof v === 'string')
|
||||
.flatMap((v: string) => v.match(/\$[A-Z_][A-Z0-9_]*/g) ?? [])
|
||||
.map((v: string) => v.slice(1));
|
||||
expect(vars.length).toBeGreaterThan(0);
|
||||
for (const name of vars) expect(secretNames.has(name)).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// --- All recipes parse without error ---
|
||||
|
||||
describe('all recipes', () => {
|
||||
|
||||
Reference in New Issue
Block a user