openclaw-twitter-skill 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +109 -0
  3. package/SKILL.md +211 -0
  4. package/index.js +263 -0
  5. package/package.json +43 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xiaoxi / OpenClaw
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # OpenClaw Twitter Skill
2
+
3
+ [![npm version](https://img.shields.io/npm/v/openclaw-twitter-skill.svg)](https://www.npmjs.com/package/openclaw-twitter-skill)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+
6
+ Stable, automated Twitter (X) posting skill for [OpenClaw](https://openclaw.ai) agents.
7
+
8
+ ## Problem
9
+
10
+ OpenClaw agents can control Chrome via browser tools, but Twitter posting is unreliable — login checks fail, buttons don't get clicked, content gets lost. This skill standardizes the entire process into a battle-tested 5-step workflow.
11
+
12
+ ## The 5-Step Workflow
13
+
14
+ ```
15
+ Step 1 → Navigate to x.com/compose/post (direct URL, never sidebar)
16
+ Step 2 → Verify login (compose box visible?)
17
+ Step 3 → Type content (type(), not paste)
18
+ Step 4 → Screenshot → user confirms content
19
+ Step 5 → Click Post → verify success toast
20
+ ```
21
+
22
+ ## Installation
23
+
24
+ ### As an OpenClaw Skill
25
+
26
+ Copy `SKILL.md` to your agent's skills directory:
27
+
28
+ ```bash
29
+ npm install -g openclaw-twitter-skill
30
+
31
+ # Copy the skill file
32
+ cp "$(npm root -g)/openclaw-twitter-skill/SKILL.md" ~/.agents/skills/twitter-post/SKILL.md
33
+ ```
34
+
35
+ ### CLI Usage
36
+
37
+ ```bash
38
+ # Simulate the full posting workflow
39
+ oct-post post "Hello from OpenClaw! 🚀 #AI #Automation"
40
+
41
+ # Validate content against posting rules
42
+ oct-post validate "Testing content #test #openclaw"
43
+
44
+ # Dry run (validate only, don't mark as ready)
45
+ oct-post post "Draft tweet #draft" --dry-run
46
+
47
+ # Show workflow info
48
+ oct-post info
49
+
50
+ # Help
51
+ oct-post --help
52
+ ```
53
+
54
+ ## Content Guidelines
55
+
56
+ | Rule | Spec |
57
+ |------|------|
58
+ | Mode | New post only (no replies) |
59
+ | Length | < 200 characters recommended (280 max) |
60
+ | Emoji | 1–3, placed naturally |
61
+ | Hashtags | 1–3 relevant tags (supports CJK: `#人工智能`) |
62
+ | Links | Only if user explicitly provides them |
63
+ | Media | Text-only by default; image upload requires user interaction |
64
+
65
+ ## Error Handling
66
+
67
+ - **Not logged in** → Stop immediately, ask user to login in Chrome
68
+ - **Post failed** → Screenshot + specific error description
69
+ - **Never retry silently** → Always report what happened
70
+ - **Never skip screenshot** → User must confirm before posting
71
+ - **One retry max** → Only for server errors, with 30s wait
72
+
73
+ ## Key Lessons
74
+
75
+ 1. Always use `/compose/post` direct URL (sidebar buttons break across UI updates)
76
+ 2. Use `type()` not paste (Twitter CSP may block clipboard)
77
+ 3. Screenshot before posting (biggest reliability win)
78
+ 4. Wait 1–1.5s for Post button to enable after typing
79
+ 5. One post per session (don't chain without spacing)
80
+ 6. Check success toast, not URL change
81
+ 7. Use `snapshot()` for logic checks, `screenshot()` for user confirmation
82
+
83
+ ## Development
84
+
85
+ ```bash
86
+ # Run tests
87
+ npm test
88
+
89
+ # Test CLI locally
90
+ node index.js post "Test tweet #dev" --dry-run
91
+ node index.js validate "Check this content #test"
92
+ ```
93
+
94
+ ## Release
95
+
96
+ Push a version tag to trigger auto-release:
97
+
98
+ ```bash
99
+ npm version patch # or minor / major
100
+ git push origin main --tags
101
+ ```
102
+
103
+ GitHub Actions will:
104
+ 1. Create a GitHub Release with auto-generated notes
105
+ 2. Publish to NPM (requires `NPM_TOKEN` secret in repo settings)
106
+
107
+ ## License
108
+
109
+ MIT
package/SKILL.md ADDED
@@ -0,0 +1,211 @@
1
+ ---
2
+ name: twitter-post
3
+ description: >
4
+ Stable automated Twitter (X) posting skill for OpenClaw agents.
5
+ Uses browser tools to navigate to x.com/compose/post, verify login,
6
+ type content, screenshot for confirmation, and click Post.
7
+ Use when: "发推", "tweet", "post to X", "发帖", "share on Twitter",
8
+ "推特发布", "帮我发一条推", or any social media posting request targeting X/Twitter.
9
+ ---
10
+
11
+ # Twitter Post Skill
12
+
13
+ Standardized 5-step workflow for OpenClaw agents to post on X (Twitter) via browser tools.
14
+
15
+ ## Prerequisites
16
+
17
+ - The user must already be logged into x.com in the Chrome OpenClaw profile.
18
+ - The agent must have access to `browser` tools (navigate, act, type, screenshot, snapshot, wait).
19
+
20
+ ## Quick Start
21
+
22
+ When the user asks to post to Twitter, follow these steps **exactly in order**:
23
+
24
+ ```
25
+ Step 1 → Open https://x.com/compose/post
26
+ Step 2 → Verify login (is the compose box visible?)
27
+ Step 3 → Type content into the compose box
28
+ Step 4 → Screenshot → show user for confirmation
29
+ Step 5 → Click Post → verify success
30
+ ```
31
+
32
+ **CRITICAL RULES (read before proceeding):**
33
+ - NEVER reply to an existing tweet — new post mode only.
34
+ - NEVER post without showing the user a screenshot first.
35
+ - NEVER retry silently on failure.
36
+ - NEVER modify user's content without explicit permission.
37
+
38
+ ## Detailed Instructions
39
+
40
+ ### Step 1: Open Compose Page
41
+
42
+ ```javascript
43
+ // ALWAYS use the direct compose URL — never click sidebar buttons
44
+ await browser.navigate("https://x.com/compose/post");
45
+ await browser.wait(3000); // wait for page load
46
+ ```
47
+
48
+ **Why direct URL?** Twitter's sidebar UI changes frequently. The `/compose/post` modal route is the most stable entry point. Sidebar "Post" button, `Ctrl+Enter` shortcut, and floating action buttons all break across UI updates.
49
+
50
+ ### Step 2: Verify Login
51
+
52
+ After navigation, take a snapshot to check page state:
53
+
54
+ ```javascript
55
+ const snapshot = await browser.snapshot();
56
+ // Examine the snapshot text for indicators below
57
+ ```
58
+
59
+ **Check for these indicators:**
60
+
61
+ | State | What you'll see in snapshot | Action |
62
+ |-------|---------------------------|--------|
63
+ | ✅ Logged in | `contenteditable` text area, "Post" button, character counter | Proceed to Step 3 |
64
+ | ❌ Not logged in | "Sign in" / "Log in" text, email/password fields | **STOP** — report to user |
65
+ | ⚠️ Rate limited | "Rate limit" / "Try again later" / error banner | **STOP** — screenshot and report |
66
+ | ⚠️ Account suspended | "Your account is suspended" message | **STOP** — screenshot and report |
67
+
68
+ **If not logged in, respond with:**
69
+ > ⚠️ Twitter 未登录。请先在 Chrome OpenClaw profile 中手动登录 x.com,然后重试。
70
+
71
+ Do NOT attempt to enter credentials or guess passwords. This is a hard stop.
72
+
73
+ ### Step 3: Type Content
74
+
75
+ ```javascript
76
+ // Click the compose text area first to focus it
77
+ await browser.act("click the tweet compose text area");
78
+ await browser.wait(500);
79
+
80
+ // Type the content character by character (do NOT paste)
81
+ await browser.type("Your tweet content here #OpenClaw");
82
+ await browser.wait(1000); // wait for content to settle
83
+ ```
84
+
85
+ **Content rules (MUST follow):**
86
+
87
+ | Rule | Spec |
88
+ |------|------|
89
+ | Mode | New post ONLY — never reply to existing tweets |
90
+ | Length | Maximum 200 characters (leave room for rendering) |
91
+ | Emoji | 1–3 emojis, placed naturally (not emoji spam) |
92
+ | Hashtags | 1–3 relevant hashtags at the end |
93
+ | Language | Match user's language preference |
94
+ | Links | Only if the user explicitly provides them |
95
+ | Media | Text-only by default (see Media Attachments section below) |
96
+
97
+ **Content template:**
98
+ ```
99
+ [Core message, 1-2 sentences] [1-2 emoji]
100
+
101
+ #Tag1 #Tag2
102
+ ```
103
+
104
+ **Why type() not paste?** Twitter's CSP and anti-bot measures may block clipboard paste events. `type()` simulates real keyboard input and is consistently more reliable.
105
+
106
+ ### Step 4: Screenshot for Confirmation
107
+
108
+ ```javascript
109
+ // MANDATORY — never skip this step
110
+ const screenshot = await browser.screenshot();
111
+ ```
112
+
113
+ Present the screenshot to the user and say:
114
+ > 📸 请确认推文内容是否正确。确认后我将点击发布。
115
+
116
+ **Wait for user confirmation before proceeding.** If the user wants changes:
117
+ 1. Select all text in the compose box: `await browser.act("select all text in the compose area");`
118
+ 2. Delete it: `await browser.act("press Backspace");`
119
+ 3. Type the new content
120
+ 4. Screenshot again
121
+
122
+ ### Step 5: Click Post & Verify
123
+
124
+ ```javascript
125
+ // Wait for the Post button to be enabled (it takes ~500ms-1s after typing)
126
+ await browser.wait(1500);
127
+
128
+ // Click the Post button
129
+ await browser.act('click the "Post" button');
130
+
131
+ // Wait for the post to be submitted
132
+ await browser.wait(3000);
133
+
134
+ // Take a verification screenshot
135
+ const result = await browser.screenshot();
136
+ ```
137
+
138
+ **Success indicators (check in screenshot/snapshot):**
139
+ - "Your post was sent" toast notification at the bottom
140
+ - Compose modal closes and returns to timeline
141
+ - A "View" link appears in the toast
142
+
143
+ **Failure indicators:**
144
+ - Post button still visible → it wasn't clicked
145
+ - "Something went wrong" toast → Twitter server error
146
+ - Error popup / red text → content validation error
147
+ - Page unchanged → JavaScript error, nothing happened
148
+
149
+ **On success, respond:**
150
+ > ✅ 推文已发布成功!
151
+
152
+ **On failure, respond with screenshot:**
153
+ > ❌ 发布失败。截图如上,请检查。[具体错误描述]
154
+
155
+ ## Media Attachments (Optional)
156
+
157
+ If the user wants to attach an image:
158
+
159
+ ```javascript
160
+ // After Step 3 (typing content), before Step 4 (screenshot):
161
+ await browser.act('click the "Add photos or video" button (media icon)');
162
+ await browser.wait(1000);
163
+ // The file picker will open — this requires user interaction
164
+ // Report to user: "请在弹出的文件选择器中选择要上传的图片"
165
+ ```
166
+
167
+ **Limitations:**
168
+ - File picker is a native OS dialog — the agent cannot select files programmatically.
169
+ - Wait for the image to upload and thumbnail to appear before proceeding.
170
+ - If no media button is found, proceed with text-only post.
171
+
172
+ ## Multi-Tweet Handling
173
+
174
+ If the user provides multiple tweets to post:
175
+
176
+ 1. **Post them ONE AT A TIME** — complete the full 5-step cycle for each.
177
+ 2. **Wait at least 30 seconds between posts** to avoid rate limiting.
178
+ 3. **Get confirmation for each tweet separately** — do not batch confirmations.
179
+ 4. **If any tweet fails, STOP** — do not continue with remaining tweets.
180
+
181
+ ## Error Handling Rules
182
+
183
+ 1. **NEVER retry silently.** If posting fails, report the error with a screenshot immediately.
184
+ 2. **NEVER guess or fill in credentials.** Login is the user's responsibility.
185
+ 3. **NEVER post without user confirmation** (Step 4 screenshot approval).
186
+ 4. **NEVER modify the user's content** without telling them.
187
+ 5. **Report specific errors:** "Button disabled", "Page not loaded", "Rate limited" — not just "failed".
188
+ 6. **One retry maximum:** For "Something went wrong" server errors only, wait 30 seconds and retry the full workflow once. If it fails again, stop.
189
+
190
+ ## Common Failure Modes & Fixes
191
+
192
+ | Problem | Cause | Fix |
193
+ |---------|-------|-----|
194
+ | Compose box not found | Page didn't fully load | `wait(5000)` then `snapshot()` to check again |
195
+ | Post button disabled | Content empty or invalid | Verify text was actually typed via `snapshot()` |
196
+ | "Something went wrong" | Twitter server error | Wait 30s, retry once, then report |
197
+ | Page shows login form | Session expired | Ask user to re-login in Chrome profile |
198
+ | Content truncated | Typed too fast | Add `wait(500)` between segments if content is long |
199
+ | Modal doesn't open | URL didn't navigate properly | Try `navigate()` again with a fresh page load |
200
+ | "Post" button not found | UI layout changed | Use `snapshot()` to find the actual button text/label |
201
+
202
+ ## Lessons Learned (经验教训)
203
+
204
+ 1. **`/compose/post` is king** — Sidebar "Post" button and keyboard shortcuts are unreliable across UI updates. Always navigate directly.
205
+ 2. **Type, don't paste** — `type()` simulates human input; paste may be blocked by Twitter's CSP.
206
+ 3. **Always screenshot before posting** — This is the single biggest reliability improvement. Users catch errors, and you have evidence of what was attempted.
207
+ 4. **Wait before clicking Post** — The Post button takes 500ms–1s to become active after content is entered. Clicking too early does nothing.
208
+ 5. **One post per session** — Don't chain multiple posts without spacing. Complete one full cycle before starting another.
209
+ 6. **Check toast, not URL** — After posting, the URL may not change immediately. Look for the success toast notification.
210
+ 7. **snapshot() over screenshot() for logic** — Use `snapshot()` (DOM text) for programmatic checks, `screenshot()` (image) for user-facing confirmation.
211
+ 8. **act() is your friend** — When specific selectors break, describe the action in natural language: `act('click the blue Post button')` is more robust than CSS selectors.
package/index.js ADDED
@@ -0,0 +1,263 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * OpenClaw Twitter Skill CLI (oct-post)
5
+ *
6
+ * Provides a CLI interface for the standardized 5-step Twitter posting workflow.
7
+ * In standalone mode, this validates and previews the workflow.
8
+ * When used by an OpenClaw agent, the SKILL.md drives the actual browser execution.
9
+ *
10
+ * Zero dependencies — uses only Node.js built-in modules.
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ // ─── Helpers ───────────────────────────────────────────────
19
+
20
+ const NO_COLOR = process.env.NO_COLOR || !process.stdout.isTTY;
21
+
22
+ const C = NO_COLOR
23
+ ? { reset: '', red: '', green: '', yellow: '', cyan: '', dim: '', bold: '' }
24
+ : {
25
+ reset: '\x1b[0m',
26
+ red: '\x1b[31m',
27
+ green: '\x1b[32m',
28
+ yellow: '\x1b[33m',
29
+ cyan: '\x1b[36m',
30
+ dim: '\x1b[2m',
31
+ bold: '\x1b[1m',
32
+ };
33
+
34
+ function log(icon, msg) { console.log(`${icon} ${msg}`); }
35
+ function ok(msg) { log(`${C.green}✔${C.reset}`, msg); }
36
+ function warn(msg) { log(`${C.yellow}⚠${C.reset}`, msg); }
37
+ function fail(msg) { log(`${C.red}✖${C.reset}`, msg); }
38
+ function info(msg) { log(`${C.cyan}ℹ${C.reset}`, msg); }
39
+ function step(n, msg) { console.log(`\n${C.cyan}[Step ${n}/5]${C.reset} ${msg}`); }
40
+
41
+ function getPkg() {
42
+ return JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8'));
43
+ }
44
+
45
+ // ─── Content Validation ────────────────────────────────────
46
+
47
+ /**
48
+ * Validate tweet content against posting rules.
49
+ * Returns { valid, errors[], warnings[], stats{} }
50
+ */
51
+ function validateContent(text) {
52
+ const errors = [];
53
+ const warnings = [];
54
+
55
+ if (!text || text.trim().length === 0) {
56
+ errors.push('Content is empty.');
57
+ return { valid: false, errors, warnings, stats: {} };
58
+ }
59
+
60
+ const trimmed = text.trim();
61
+ const len = trimmed.length;
62
+
63
+ // Hashtags: support ASCII and Unicode word chars (CJK, etc.)
64
+ const hashtags = trimmed.match(/#[\w\u4e00-\u9fff\u3400-\u4dbf\uF900-\uFAFF]+/gu) || [];
65
+
66
+ // Emojis: broad Unicode emoji detection
67
+ const emojis = trimmed.match(
68
+ /[\u{1F600}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{2702}-\u{27B0}\u{FE00}-\u{FE0F}\u{200D}\u{20E3}]/gu
69
+ ) || [];
70
+
71
+ // Hard limit: Twitter's 280 chars
72
+ if (len > 280) {
73
+ errors.push(`Exceeds Twitter limit: ${len}/280 characters.`);
74
+ } else if (len > 200) {
75
+ warnings.push(`Content is ${len} chars. Recommended < 200 for readability.`);
76
+ }
77
+
78
+ // Hashtag guidance
79
+ if (hashtags.length > 3) {
80
+ warnings.push(`Too many hashtags (${hashtags.length}). Recommended 1–3.`);
81
+ } else if (hashtags.length === 0) {
82
+ warnings.push('No hashtags found. Consider adding 1–3 relevant tags.');
83
+ }
84
+
85
+ // Emoji guidance
86
+ if (emojis.length > 5) {
87
+ warnings.push(`Many emojis (${emojis.length}). Recommended 1–3.`);
88
+ }
89
+
90
+ // Check for potential reply patterns
91
+ if (trimmed.startsWith('@')) {
92
+ warnings.push('Content starts with @mention — this may create a reply instead of a new post.');
93
+ }
94
+
95
+ return {
96
+ valid: errors.length === 0,
97
+ errors,
98
+ warnings,
99
+ stats: { length: len, hashtags: hashtags.length, emojis: emojis.length },
100
+ };
101
+ }
102
+
103
+ // Expose for testing
104
+ module.exports = { validateContent };
105
+
106
+ // ─── Commands ──────────────────────────────────────────────
107
+
108
+ function cmdPost(content, opts) {
109
+ console.log(`\n${'─'.repeat(50)}`);
110
+ console.log(` 🐦 OpenClaw Twitter Posting Workflow`);
111
+ console.log(`${'─'.repeat(50)}`);
112
+
113
+ const result = validateContent(content);
114
+
115
+ if (!result.valid) {
116
+ result.errors.forEach(e => fail(e));
117
+ process.exit(1);
118
+ }
119
+ result.warnings.forEach(w => warn(w));
120
+
121
+ info(`Content: ${result.stats.length} chars | ${result.stats.hashtags} hashtags | ${result.stats.emojis} emojis`);
122
+
123
+ step(1, 'Navigate to https://x.com/compose/post');
124
+ ok('Open compose page');
125
+
126
+ step(2, 'Verify login status');
127
+ ok('Check for compose text area');
128
+
129
+ step(3, 'Type content');
130
+ console.log(`${C.dim} "${content}"${C.reset}`);
131
+ ok('Content entered');
132
+
133
+ step(4, 'Screenshot for confirmation');
134
+ if (opts.skipConfirm) {
135
+ warn('--skip-confirm: Skipping user confirmation (use with caution)');
136
+ } else {
137
+ ok('Screenshot taken → awaiting user confirmation');
138
+ }
139
+
140
+ step(5, 'Click Post & verify');
141
+ ok('Click "Post" button → check success toast');
142
+
143
+ console.log(`\n${'─'.repeat(50)}`);
144
+ if (opts.dryRun) {
145
+ warn('DRY RUN — No actual posting. Workflow validated successfully.');
146
+ } else {
147
+ ok('Workflow ready. Agent will execute via browser tools.');
148
+ }
149
+ console.log(`${'─'.repeat(50)}\n`);
150
+ }
151
+
152
+ function cmdValidate(content) {
153
+ console.log('\n🔍 Validating tweet content...\n');
154
+ const result = validateContent(content);
155
+
156
+ if (result.valid) {
157
+ ok('Content is valid');
158
+ } else {
159
+ result.errors.forEach(e => fail(e));
160
+ }
161
+ result.warnings.forEach(w => warn(w));
162
+
163
+ console.log(`\n📊 Stats:`);
164
+ console.log(` Length: ${result.stats.length}/280 (recommended < 200)`);
165
+ console.log(` Hashtags: ${result.stats.hashtags} (recommended 1–3)`);
166
+ console.log(` Emojis: ${result.stats.emojis} (recommended 1–3)`);
167
+
168
+ process.exit(result.valid ? 0 : 1);
169
+ }
170
+
171
+ function cmdInfo() {
172
+ const pkg = getPkg();
173
+ console.log(`\n🐦 ${C.bold}${pkg.name}${C.reset} v${pkg.version}`);
174
+ console.log(` ${pkg.description}\n`);
175
+ console.log(`${C.bold}Workflow:${C.reset} 5-step standardized posting`);
176
+ console.log(' 1. Open → https://x.com/compose/post');
177
+ console.log(' 2. Login → verify compose box visible');
178
+ console.log(' 3. Type → enter content (< 200 chars)');
179
+ console.log(' 4. Shot → screenshot for user confirmation');
180
+ console.log(' 5. Post → click button & verify success\n');
181
+ console.log(`${C.dim}Homepage: ${pkg.homepage}${C.reset}`);
182
+ }
183
+
184
+ // ─── CLI Parsing (zero-dependency) ─────────────────────────
185
+
186
+ function printHelp() {
187
+ const pkg = getPkg();
188
+ console.log(`
189
+ ${C.bold}${pkg.name}${C.reset} v${pkg.version}
190
+
191
+ ${C.bold}USAGE${C.reset}
192
+ oct-post <command> [options]
193
+
194
+ ${C.bold}COMMANDS${C.reset}
195
+ post <content> Simulate the 5-step posting workflow
196
+ validate <content> Validate tweet content against rules
197
+ info Show skill info and workflow summary
198
+
199
+ ${C.bold}OPTIONS${C.reset}
200
+ --dry-run Validate only, don't mark as ready to post
201
+ --skip-confirm Skip screenshot confirmation step
202
+ -h, --help Show this help message
203
+ -v, --version Show version number
204
+
205
+ ${C.bold}EXAMPLES${C.reset}
206
+ oct-post post "Hello from OpenClaw! 🚀 #AI #Automation"
207
+ oct-post post "Draft tweet #draft" --dry-run
208
+ oct-post validate "Testing content #test"
209
+ oct-post info
210
+ `);
211
+ }
212
+
213
+ function main() {
214
+ const args = process.argv.slice(2);
215
+
216
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
217
+ printHelp();
218
+ process.exit(0);
219
+ }
220
+
221
+ if (args[0] === '--version' || args[0] === '-v') {
222
+ console.log(getPkg().version);
223
+ process.exit(0);
224
+ }
225
+
226
+ const cmd = args[0];
227
+
228
+ switch (cmd) {
229
+ case 'post': {
230
+ const content = args.find((a, i) => i > 0 && !a.startsWith('--'));
231
+ if (!content) {
232
+ fail('Missing content. Usage: oct-post post "your tweet"');
233
+ process.exit(1);
234
+ }
235
+ cmdPost(content, {
236
+ dryRun: args.includes('--dry-run'),
237
+ skipConfirm: args.includes('--skip-confirm'),
238
+ });
239
+ break;
240
+ }
241
+ case 'validate': {
242
+ const content = args.find((a, i) => i > 0 && !a.startsWith('--'));
243
+ if (!content) {
244
+ fail('Missing content. Usage: oct-post validate "your tweet"');
245
+ process.exit(1);
246
+ }
247
+ cmdValidate(content);
248
+ break;
249
+ }
250
+ case 'info':
251
+ cmdInfo();
252
+ break;
253
+ default:
254
+ fail(`Unknown command: ${cmd}`);
255
+ printHelp();
256
+ process.exit(1);
257
+ }
258
+ }
259
+
260
+ // Only run CLI when executed directly (not when require'd for testing)
261
+ if (require.main === module) {
262
+ main();
263
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "openclaw-twitter-skill",
3
+ "version": "1.2.0",
4
+ "description": "OpenClaw agent skill for stable automated Twitter (X) posting via browser tools. Standardized 5-step workflow with login check, content validation, screenshot confirmation, and error reporting.",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "oct-post": "index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "SKILL.md",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "test": "node tests/test.js",
17
+ "prepublishOnly": "node tests/test.js"
18
+ },
19
+ "keywords": [
20
+ "openclaw",
21
+ "twitter",
22
+ "x",
23
+ "automation",
24
+ "skill",
25
+ "agent",
26
+ "browser",
27
+ "social-media",
28
+ "posting"
29
+ ],
30
+ "author": "xiaoxi <xiaoxi@openclaw.ai>",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/adminlove520/openclaw-twitter-skill.git"
35
+ },
36
+ "homepage": "https://github.com/adminlove520/openclaw-twitter-skill#readme",
37
+ "bugs": {
38
+ "url": "https://github.com/adminlove520/openclaw-twitter-skill/issues"
39
+ },
40
+ "engines": {
41
+ "node": ">=16.0.0"
42
+ }
43
+ }