From f25f910a8261169bc295852985c59071a3c368ef Mon Sep 17 00:00:00 2001 From: matevip Date: Mon, 11 May 2026 17:22:30 +0800 Subject: [PATCH] feat(skill): support `optional: true` frontmatter for off-by-default bundled skills --- .../installer/BuiltinSkillSeedService.java | 29 +- .../main/resources/skills/x_intel/SKILL.md | 275 ++++++++++++++++++ 2 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 mateclaw-server/src/main/resources/skills/x_intel/SKILL.md diff --git a/mateclaw-server/src/main/java/vip/mate/skill/installer/BuiltinSkillSeedService.java b/mateclaw-server/src/main/java/vip/mate/skill/installer/BuiltinSkillSeedService.java index 61f3446b..dd956087 100644 --- a/mateclaw-server/src/main/java/vip/mate/skill/installer/BuiltinSkillSeedService.java +++ b/mateclaw-server/src/main/java/vip/mate/skill/installer/BuiltinSkillSeedService.java @@ -309,7 +309,16 @@ public class BuiltinSkillSeedService implements ApplicationRunner { row.setDescription(nullIfBlank(parsed.getDescription())); row.setSkillType(SKILL_TYPE_BUILTIN); row.setBuiltin(true); - row.setEnabled(true); + // Frontmatter `optional: true` flips the initial seed to enabled=false, + // so heavyweight bundled skills (paid CLI dependencies, external OAuth, + // niche integrations) ship dark — the user opts in from the Skills page + // when they actually want them. The default (frontmatter absent or + // false) preserves the historical "all bundled skills active" behavior. + // {@link #mergeIntoExisting} deliberately does NOT touch `enabled`, so + // once a user activates an optional skill, subsequent boots keep their + // choice and a downgrade in frontmatter never silently disables it. + boolean optional = booleanFromFrontmatter(parsed, "optional", false); + row.setEnabled(!optional); row.setSkillContent(content); row.setVersion(stringFromFrontmatter(parsed, "version", DEFAULT_VERSION)); row.setIcon(stringFromFrontmatter(parsed, "icon", DEFAULT_ICON)); @@ -406,6 +415,24 @@ public class BuiltinSkillSeedService implements ApplicationRunner { return dirty; } + /** + * Read a boolean frontmatter key, tolerant of the YAML / casual-string + * forms the parser might surface ({@code true} / {@code "true"} / + * {@code "yes"} / {@code "1"}). Anything else falls back to the supplied + * default so a typo doesn't silently flip behavior. + */ + private boolean booleanFromFrontmatter(SkillFrontmatterParser.ParsedSkillMd parsed, + String key, boolean fallback) { + Map fm = parsed.getFrontmatter(); + if (fm == null) return fallback; + Object value = fm.get(key); + if (value == null) return fallback; + if (value instanceof Boolean b) return b; + String s = value.toString().trim().toLowerCase(); + if (s.isEmpty()) return fallback; + return s.equals("true") || s.equals("yes") || s.equals("1") || s.equals("on"); + } + @SuppressWarnings("unchecked") private String stringFromFrontmatter(SkillFrontmatterParser.ParsedSkillMd parsed, String key, String fallback) { diff --git a/mateclaw-server/src/main/resources/skills/x_intel/SKILL.md b/mateclaw-server/src/main/resources/skills/x_intel/SKILL.md new file mode 100644 index 00000000..19a80eaa --- /dev/null +++ b/mateclaw-server/src/main/resources/skills/x_intel/SKILL.md @@ -0,0 +1,275 @@ +--- +name: x_intel +description: "Read X (Twitter) posts, search, timelines and user profiles via the official xurl CLI." +nameZh: X 情报采集 +nameEn: X Intel +version: 1.0.0 +icon: 🐦 +author: MateClaw +optional: true +tags: + - x + - twitter + - social-media + - research + - xurl +platforms: + - linux + - macos +dependencies: + commands: + - xurl + tools: + - execute_shell_command +--- + +# x_intel — X (Twitter) information gathering + +`x_intel` lets an agent pull posts, search results, timelines and user profiles from X (Twitter) through `xurl`, the X developer platform's official CLI. **This skill is read-only by design** — it intentionally omits posting, replying, deleting, DM-sending and any other write surface. For a separate publishing skill, see follow-up work. + +Use this skill for: + +- looking up a single post by ID or URL +- searching posts with the X search query syntax (`from:user`, `lang:en`, `#hashtag`, ...) +- reading the agent operator's home timeline, mentions, bookmarks, likes +- inspecting a user profile by handle +- walking the social graph (who someone follows / is followed by) +- raw read access to any X API v2 GET endpoint when the shortcuts don't fit + +--- + +## Credential safety (mandatory) + +Critical rules when invoked inside an agent session: + +- **Never** read, print, parse, summarize, upload or quote `~/.xurl` into chat context. It is a YAML token store. +- **Never** ask the user to paste credentials/tokens into the conversation. +- **Never** suggest or run the auth commands with inline secrets in an agent session. +- **Never** pass `--verbose` / `-v` — it prints auth headers to stdout. +- The only credential-touching command this skill ever runs is `xurl auth status` (status only, no secrets). + +Forbidden flags in any agent-issued command (each accepts inline secrets): +`--bearer-token`, `--consumer-key`, `--consumer-secret`, `--access-token`, `--token-secret`, `--client-id`, `--client-secret`. + +App registration and the OAuth 2.0 PKCE flow must be performed by the user **outside** the agent session (see "User setup" below). Tokens persist in `~/.xurl` (YAML); OAuth 2.0 refreshes automatically. + +--- + +## Install + +The agent should verify, not install. Direct the user to install if missing. + +```bash +# Shell script (Linux + macOS, installs to ~/.local/bin, no sudo) +curl -fsSL https://raw.githubusercontent.com/xdevplatform/xurl/main/install.sh | bash + +# Homebrew (macOS) +brew install --cask xdevplatform/tap/xurl + +# Go (cross-platform) +go install github.com/xdevplatform/xurl@latest +``` + +Verify: + +```bash +xurl --help +xurl auth status +``` + +--- + +## User setup (user runs these, NOT the agent) + +The agent must not perform these steps — they involve pasting secrets. Direct the user to this section verbatim. + +1. Open the X developer dashboard: +2. In the app's User Authentication Settings, set the redirect URI to `http://localhost:8080/callback` and the app type to **Web app, automated app or bot**. +3. Copy the app's Client ID and Client Secret. +4. Register the app locally: + ```bash + xurl auth apps add my-app --client-id YOUR_CLIENT_ID --client-secret YOUR_CLIENT_SECRET + ``` +5. Authenticate (this opens a browser for OAuth 2.0 PKCE): + ```bash + xurl auth oauth2 --app my-app + ``` + If X returns `UsernameNotFound` or a 403 on the post-OAuth `/2/users/me` lookup, pass the handle explicitly (xurl v1.1.0+): + ```bash + xurl auth oauth2 --app my-app YOUR_HANDLE + ``` +6. Mark this app as the default so all commands use it: + ```bash + xurl auth default my-app + ``` +7. Verify: + ```bash + xurl auth status + xurl whoami + ``` + +> **Most common mistake:** omitting `--app my-app` from `xurl auth oauth2`. The OAuth token then lands in the built-in `default` profile, which has no client-id/client-secret, and every later read fails. Re-run `xurl auth oauth2 --app my-app` and `xurl auth default my-app` to fix. + +--- + +## Read-only command reference + +All commands return JSON to stdout. The agent parses JSON directly; no extra tooling needed. + +| Action | Command | +| --- | --- | +| Who is the bound account | `xurl whoami` | +| Look up a user | `xurl user @handle` | +| Read one post (ID or URL) | `xurl read POST_ID` | +| Search posts | `xurl search "QUERY" -n 10` | +| Home timeline | `xurl timeline -n 20` | +| Mentions of bound account | `xurl mentions -n 20` | +| Bookmarks list | `xurl bookmarks -n 20` | +| Likes list | `xurl likes -n 20` | +| Following list | `xurl following -n 50` | +| Followers list | `xurl followers -n 50` | +| Another user's graph | `xurl following --of HANDLE -n 20` | +| Auth status | `xurl auth status` | + +Notes: + +- `POST_ID` accepts a full `https://x.com/user/status/...` URL — xurl extracts the ID. +- Handles work with or without the leading `@`. + +### Search query language + +X's search supports operators inside the quoted query string: + +```bash +xurl search "from:elonmusk -is:retweet" -n 20 +xurl search "#buildinpublic lang:en since:2026-01-01" -n 25 +xurl search "OR" -n 10 # literal OR — must be quoted +xurl search "(rust OR go) lang:en" -n 10 +xurl search "to:NASA -is:reply" -n 10 +``` + +Common operators: `from:`, `to:`, `@`, `#`, `is:retweet`, `is:reply`, `is:quote`, `lang:`, `since:`, `until:`, `has:media`, `has:links`. See the X search syntax docs for the full list. + +--- + +## Raw v2 read access + +For anything beyond the shortcuts, hit any v2 GET endpoint directly: + +```bash +# Public user fields +xurl /2/users/by/username/elonmusk?user.fields=public_metrics,description,verified + +# Single tweet with metrics + author expansion +xurl /2/tweets/1234567890?tweet.fields=public_metrics,created_at&expansions=author_id + +# Recent search with extra fields (paid tier) +xurl /2/tweets/search/recent?query=langchain&tweet.fields=created_at,public_metrics&max_results=25 + +# Full URLs also work +xurl https://api.x.com/2/users/me +``` + +Streaming endpoints are auto-detected; force with `-s` if needed. **Streaming endpoints can be expensive — do not start one without confirming intent with the user.** + +--- + +## Common workflows + +### Profile a user + +```bash +xurl user @handle +xurl /2/users/by/username/handle?user.fields=public_metrics,description,verified,created_at +xurl following --of handle -n 20 # who they pay attention to +``` + +### Triage a trending term + +```bash +xurl search "topic lang:en -is:retweet" -n 25 +# Pick interesting IDs from the JSON, then drill in: +xurl read 1234567890 +xurl user @ORIGINAL_POSTER +``` + +### Catch up on activity + +```bash +xurl whoami +xurl mentions -n 20 +xurl timeline -n 20 +xurl bookmarks -n 10 +``` + +### Conversation context + +```bash +xurl read https://x.com/user/status/1234567890 +# Conversation expansion via raw v2 +xurl /2/tweets/search/recent?query=conversation_id:1234567890&max_results=25 +``` + +--- + +## Output format + +Every command emits X API v2 shape JSON to stdout: + +```json +{ + "data": { "id": "1234567890", "text": "Hello world!" }, + "includes": { "users": [{ "id": "...", "username": "..." }] } +} +``` + +Errors are also JSON: + +```json +{ "errors": [ { "message": "Not authorized", "code": 403 } ] } +``` + +The non-zero exit code distinguishes errors from empty results. + +--- + +## Agent workflow + +1. Verify prerequisites: `xurl --help` (the command exists) and `xurl auth status` (the user has at least one app with `oauth2` tokens, marked `▸` as default). +2. **Parse `auth status` output before any other command.** If the default app shows `oauth2: (none)` but a non-default app has valid tokens, instruct the user to run `xurl auth default ` — this is the most common config glitch and does not require a re-login. +3. If `auth status` shows no apps or no tokens, **stop**. Tell the user to follow the "User setup" section. Do not attempt to register apps or run any auth flow yourself. +4. Start with the cheapest read first (`xurl whoami` / `xurl user @handle` / `xurl search ... -n 3`) to confirm reachability and the request shape. +5. Treat 401 / 403 / 429 distinctly: 401 → re-auth needed, 403 → scope or plan, 429 → wait and retry (X rate-limits per-endpoint). +6. Never paste `~/.xurl` content back into the conversation, even when troubleshooting. +7. When in doubt about cost: X's API has paid tiers and per-endpoint rate limits. Do not run unbounded loops or streams without the user's explicit confirmation. + +--- + +## Troubleshooting + +| Symptom | Cause | Fix | +| --- | --- | --- | +| `auth status` shows `oauth2: (none)` on default | Token saved to built-in `default` profile (no client-id/secret) | Re-run `xurl auth oauth2 --app my-app` then `xurl auth default my-app` | +| `unauthorized_client` during OAuth | App type set to "Native App" in X dashboard | Change to "Web app, automated app or bot" | +| `UsernameNotFound` / 403 right after OAuth | X not returning username from `/2/users/me` | `xurl auth oauth2 --app my-app YOUR_HANDLE` (xurl v1.1.0+) | +| 401 on every read | Token expired or wrong default app | Check `xurl auth status` — verify `▸` points to the app with oauth2 tokens | +| `client-forbidden` / `client-not-enrolled` | X platform enrollment | Developer dashboard → Apps → Manage → Production environment | +| `CreditsDepleted` | $0 balance on X API | Buy credits in Developer Console → Billing | +| 429 on search/timeline | Hit per-endpoint rate limit | Pause, retry with smaller `-n`, or wait for the reset window | + +--- + +## Notes + +- **Cost:** X API access is paid for meaningful usage. Many failures are plan or rate-limit problems, not skill problems. +- **Scopes:** OAuth 2.0 tokens use broad scopes; a 403 on a specific read usually means the token is missing a scope — have the user re-run `xurl auth oauth2`. +- **Token refresh:** OAuth 2.0 tokens auto-refresh; nothing to do. +- **Multiple apps:** `xurl --app NAME ...` runs one read against a specific app without changing the default. +- **Token storage:** `~/.xurl` is YAML. Treat it like a private key. Never read or send it to LLM context. + +--- + +## Attribution + +- Underlying CLI: (X developer platform). +- This skill wraps the CLI's read commands and documents agent-side safety rules. No code is shipped beyond this SKILL.md.