wolverine-ai 6.6.2 → 7.0.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.
- package/package.json +1 -1
- package/src/claw/claw-runner.js +5 -0
- package/src/core/runner.js +17 -0
- package/src/security/code-guard.js +569 -0
- package/src/security/self-improve.js +289 -0
- package/wolverine-claw/config/settings.json +1 -1
- package/wolverine-claw/skills/browser/README.md +17 -0
- package/wolverine-claw/skills/browser/SKILL.md +11 -0
- package/wolverine-claw/skills/browser/_meta.json +6 -0
- package/wolverine-claw/skills/browser/index.js +41 -0
- package/wolverine-claw/skills/discord/SKILL.md +369 -0
- package/wolverine-claw/skills/discord/_meta.json +6 -0
- package/wolverine-claw/skills/telegram/SKILL.md +43 -0
- package/wolverine-claw/skills/telegram/_meta.json +6 -0
- package/wolverine-claw/skills/telegram/references/telegram-bot-api.md +63 -0
- package/wolverine-claw/skills/telegram/references/telegram-commands-playbook.md +26 -0
- package/wolverine-claw/skills/telegram/references/telegram-request-templates.md +42 -0
- package/wolverine-claw/skills/telegram/references/telegram-update-routing.md +23 -0
- package/wolverine-claw/skills/twitter-post/SKILL.md +98 -0
- package/wolverine-claw/skills/twitter-post/_meta.json +6 -0
- package/wolverine-claw/skills/twitter-post/scripts/tweet.js +198 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Twitter/X Official API v2 — Post tweets via OAuth 1.0a
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node tweet.js "Your tweet text here"
|
|
7
|
+
* node tweet.js --reply-to 123456789 "Reply text"
|
|
8
|
+
* node tweet.js --quote 123456789 "Quote tweet text"
|
|
9
|
+
* node tweet.js --thread "First tweet" "Second tweet" "Third tweet"
|
|
10
|
+
*
|
|
11
|
+
* Environment variables (required):
|
|
12
|
+
* TWITTER_CONSUMER_KEY - OAuth 1.0a Consumer Key (API Key)
|
|
13
|
+
* TWITTER_CONSUMER_SECRET - OAuth 1.0a Consumer Secret (API Key Secret)
|
|
14
|
+
* TWITTER_ACCESS_TOKEN - OAuth 1.0a Access Token
|
|
15
|
+
* TWITTER_ACCESS_TOKEN_SECRET - OAuth 1.0a Access Token Secret
|
|
16
|
+
*
|
|
17
|
+
* Optional:
|
|
18
|
+
* HTTPS_PROXY - HTTP proxy (e.g. http://127.0.0.1:7897)
|
|
19
|
+
* TWITTER_DRY_RUN=1 - Print tweet without sending
|
|
20
|
+
*
|
|
21
|
+
* Output (JSON): { ok, id, url, remaining, limit }
|
|
22
|
+
*/
|
|
23
|
+
const crypto = require('crypto');
|
|
24
|
+
const https = require('https');
|
|
25
|
+
const http = require('http');
|
|
26
|
+
const tls = require('tls');
|
|
27
|
+
const { URL } = require('url');
|
|
28
|
+
|
|
29
|
+
// --- Config ---
|
|
30
|
+
const CK = process.env.TWITTER_CONSUMER_KEY;
|
|
31
|
+
const CS = process.env.TWITTER_CONSUMER_SECRET;
|
|
32
|
+
const AT = process.env.TWITTER_ACCESS_TOKEN;
|
|
33
|
+
const ATS = process.env.TWITTER_ACCESS_TOKEN_SECRET;
|
|
34
|
+
const PROXY = process.env.HTTPS_PROXY || process.env.https_proxy || '';
|
|
35
|
+
const DRY = process.env.TWITTER_DRY_RUN === '1';
|
|
36
|
+
|
|
37
|
+
function die(msg) { console.error(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
|
|
38
|
+
if (!CK || !CS || !AT || !ATS) die('Missing TWITTER_CONSUMER_KEY / TWITTER_CONSUMER_SECRET / TWITTER_ACCESS_TOKEN / TWITTER_ACCESS_TOKEN_SECRET');
|
|
39
|
+
|
|
40
|
+
// --- OAuth 1.0a ---
|
|
41
|
+
function penc(s) {
|
|
42
|
+
return encodeURIComponent(s).replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16).toUpperCase());
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function oauthSign(method, url, params) {
|
|
46
|
+
const paramStr = Object.keys(params).sort().map(k => `${penc(k)}=${penc(params[k])}`).join('&');
|
|
47
|
+
const baseStr = `${method}&${penc(url)}&${penc(paramStr)}`;
|
|
48
|
+
const sigKey = `${penc(CS)}&${penc(ATS)}`;
|
|
49
|
+
return crypto.createHmac('sha1', sigKey).update(baseStr).digest('base64');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function authHeader(method, url) {
|
|
53
|
+
const p = {
|
|
54
|
+
oauth_consumer_key: CK,
|
|
55
|
+
oauth_nonce: crypto.randomBytes(32).toString('hex'),
|
|
56
|
+
oauth_signature_method: 'HMAC-SHA1',
|
|
57
|
+
oauth_timestamp: Math.floor(Date.now() / 1000).toString(),
|
|
58
|
+
oauth_token: AT,
|
|
59
|
+
oauth_version: '1.0'
|
|
60
|
+
};
|
|
61
|
+
p.oauth_signature = oauthSign(method, url, p);
|
|
62
|
+
return 'OAuth ' + Object.keys(p).map(k => `${penc(k)}="${penc(p[k])}"`).join(', ');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// --- HTTP with optional proxy ---
|
|
66
|
+
function request(method, urlStr, body) {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const u = new URL(urlStr);
|
|
69
|
+
const bodyStr = body ? JSON.stringify(body) : '';
|
|
70
|
+
const headers = { 'Authorization': authHeader(method, urlStr) };
|
|
71
|
+
if (body) {
|
|
72
|
+
headers['Content-Type'] = 'application/json';
|
|
73
|
+
headers['Content-Length'] = Buffer.byteLength(bodyStr);
|
|
74
|
+
}
|
|
75
|
+
const opts = { hostname: u.hostname, path: u.pathname + u.search, method, headers };
|
|
76
|
+
|
|
77
|
+
function doRequest(socket) {
|
|
78
|
+
if (socket) { opts.socket = socket; opts.createConnection = () => socket; }
|
|
79
|
+
const req = https.request(opts, resp => {
|
|
80
|
+
let d = ''; resp.on('data', c => d += c);
|
|
81
|
+
resp.on('end', () => {
|
|
82
|
+
try { resolve({ status: resp.statusCode, data: JSON.parse(d), headers: resp.headers }); }
|
|
83
|
+
catch { resolve({ status: resp.statusCode, data: d, headers: resp.headers }); }
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
req.on('error', reject);
|
|
87
|
+
if (bodyStr) req.write(bodyStr);
|
|
88
|
+
req.end();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (PROXY) {
|
|
92
|
+
const pu = new URL(PROXY);
|
|
93
|
+
const proxyReq = http.request({
|
|
94
|
+
hostname: pu.hostname, port: pu.port,
|
|
95
|
+
method: 'CONNECT', path: `${u.hostname}:443`
|
|
96
|
+
});
|
|
97
|
+
proxyReq.on('connect', (res, socket) => {
|
|
98
|
+
if (res.statusCode !== 200) return reject(new Error(`Proxy CONNECT failed: ${res.statusCode}`));
|
|
99
|
+
const tlsSocket = tls.connect({ socket, servername: u.hostname }, () => doRequest(tlsSocket));
|
|
100
|
+
tlsSocket.on('error', reject);
|
|
101
|
+
});
|
|
102
|
+
proxyReq.on('error', reject);
|
|
103
|
+
proxyReq.end();
|
|
104
|
+
} else {
|
|
105
|
+
doRequest(null);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// --- Tweet weight (Twitter's weighted char count) ---
|
|
111
|
+
function tweetWeight(text) {
|
|
112
|
+
let w = 0;
|
|
113
|
+
for (const ch of text) {
|
|
114
|
+
w += (ch.codePointAt(0) > 0x1FFF) ? 2 : 1;
|
|
115
|
+
}
|
|
116
|
+
// URLs count as 23 chars
|
|
117
|
+
const urlRegex = /https?:\/\/\S+/g;
|
|
118
|
+
const urls = text.match(urlRegex) || [];
|
|
119
|
+
for (const url of urls) {
|
|
120
|
+
let urlWeight = 0;
|
|
121
|
+
for (const ch of url) urlWeight += (ch.codePointAt(0) > 0x1FFF) ? 2 : 1;
|
|
122
|
+
w = w - urlWeight + 23;
|
|
123
|
+
}
|
|
124
|
+
return w;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// --- Post a single tweet ---
|
|
128
|
+
async function postTweet(text, opts = {}) {
|
|
129
|
+
const weight = tweetWeight(text);
|
|
130
|
+
if (weight > 280) die(`Tweet too long: ${weight}/280 weighted chars`);
|
|
131
|
+
|
|
132
|
+
if (DRY) {
|
|
133
|
+
console.error(`[DRY RUN] ${weight}/280: ${text}`);
|
|
134
|
+
return { ok: true, id: 'dry-run', url: '#', remaining: '-', limit: '-' };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const body = { text };
|
|
138
|
+
if (opts.replyTo) body.reply = { in_reply_to_tweet_id: opts.replyTo };
|
|
139
|
+
if (opts.quoteTweetId) body.quote_tweet_id = opts.quoteTweetId;
|
|
140
|
+
|
|
141
|
+
const r = await request('POST', 'https://api.twitter.com/2/tweets', body);
|
|
142
|
+
if (r.status === 201) {
|
|
143
|
+
const id = r.data.data.id;
|
|
144
|
+
return {
|
|
145
|
+
ok: true,
|
|
146
|
+
id,
|
|
147
|
+
url: `https://x.com/i/status/${id}`,
|
|
148
|
+
remaining: r.headers['x-rate-limit-remaining'],
|
|
149
|
+
limit: r.headers['x-rate-limit-limit']
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
die(`API error ${r.status}: ${JSON.stringify(r.data)}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// --- CLI ---
|
|
156
|
+
async function main() {
|
|
157
|
+
const args = process.argv.slice(2);
|
|
158
|
+
let replyTo = null, quoteTweetId = null, thread = false;
|
|
159
|
+
const texts = [];
|
|
160
|
+
|
|
161
|
+
for (let i = 0; i < args.length; i++) {
|
|
162
|
+
if (args[i] === '--reply-to' && args[i + 1]) { replyTo = args[++i]; continue; }
|
|
163
|
+
if (args[i] === '--quote' && args[i + 1]) { quoteTweetId = args[++i]; continue; }
|
|
164
|
+
if (args[i] === '--thread') { thread = true; continue; }
|
|
165
|
+
if (args[i] === '--help' || args[i] === '-h') {
|
|
166
|
+
console.log(`Usage: node tweet.js [options] "text" ["text2" ...]
|
|
167
|
+
Options:
|
|
168
|
+
--reply-to <id> Reply to a tweet
|
|
169
|
+
--quote <id> Quote tweet
|
|
170
|
+
--thread Post multiple args as a thread
|
|
171
|
+
--help Show this help
|
|
172
|
+
|
|
173
|
+
Env vars: TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET, TWITTER_ACCESS_TOKEN, TWITTER_ACCESS_TOKEN_SECRET
|
|
174
|
+
Optional: HTTPS_PROXY, TWITTER_DRY_RUN=1`);
|
|
175
|
+
process.exit(0);
|
|
176
|
+
}
|
|
177
|
+
texts.push(args[i]);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (texts.length === 0) die('No tweet text provided. Use --help for usage.');
|
|
181
|
+
|
|
182
|
+
if (thread && texts.length > 1) {
|
|
183
|
+
// Post thread
|
|
184
|
+
const results = [];
|
|
185
|
+
let prevId = replyTo;
|
|
186
|
+
for (const t of texts) {
|
|
187
|
+
const r = await postTweet(t, { replyTo: prevId, quoteTweetId: results.length === 0 ? quoteTweetId : undefined });
|
|
188
|
+
results.push(r);
|
|
189
|
+
prevId = r.id;
|
|
190
|
+
}
|
|
191
|
+
console.log(JSON.stringify({ ok: true, thread: results }));
|
|
192
|
+
} else {
|
|
193
|
+
const r = await postTweet(texts.join(' '), { replyTo, quoteTweetId });
|
|
194
|
+
console.log(JSON.stringify(r));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
main().catch(e => die(e.message));
|