Initial commit with translated description

This commit is contained in:
2026-03-29 08:36:14 +08:00
commit f1841f83e3
8 changed files with 757 additions and 0 deletions

240
scripts/chat.js Normal file
View File

@@ -0,0 +1,240 @@
#!/usr/bin/env node
/**
* xAI/Grok Chat Script
*
* Usage:
* node chat.js "Your prompt here"
* node chat.js --model grok-3-mini "Quick question"
* node chat.js --image /path/to/image.jpg "Describe this"
* node chat.js --system "You are a pirate" "Tell me about ships"
*/
const https = require('https');
const fs = require('fs');
const path = require('path');
const API_BASE = 'api.x.ai';
const DEFAULT_MODEL = 'grok-3';
function getApiKey() {
return process.env.XAI_API_KEY || null;
}
function parseArgs(args) {
const result = {
model: process.env.XAI_MODEL || DEFAULT_MODEL,
system: null,
image: null,
prompt: '',
stream: false,
json: false,
};
let i = 0;
while (i < args.length) {
const arg = args[i];
if (arg === '--model' || arg === '-m') {
result.model = args[++i];
} else if (arg === '--system' || arg === '-s') {
result.system = args[++i];
} else if (arg === '--image' || arg === '-i') {
result.image = args[++i];
} else if (arg === '--stream') {
result.stream = true;
} else if (arg === '--json' || arg === '-j') {
result.json = true;
} else if (!arg.startsWith('-')) {
result.prompt = args.slice(i).join(' ');
break;
}
i++;
}
return result;
}
function imageToBase64(imagePath) {
const absolutePath = path.resolve(imagePath);
// Validate file extension is an allowed image type
const ext = path.extname(absolutePath).toLowerCase();
const mimeTypes = {
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.gif': 'image/gif',
'.webp': 'image/webp',
};
if (!mimeTypes[ext]) {
throw new Error(`Unsupported image format: ${ext}. Allowed: ${Object.keys(mimeTypes).join(', ')}`);
}
if (!fs.existsSync(absolutePath)) {
throw new Error(`Image not found: ${absolutePath}`);
}
const imageData = fs.readFileSync(absolutePath);
const base64 = imageData.toString('base64');
const mimeType = mimeTypes[ext];
return `data:${mimeType};base64,${base64}`;
}
async function chat(options) {
const apiKey = getApiKey();
if (!apiKey) {
console.error('❌ No API key found. Set XAI_API_KEY or configure in clawdbot.');
console.error(' Get your key at: https://console.x.ai');
process.exit(1);
}
// Build messages
const messages = [];
if (options.system) {
messages.push({ role: 'system', content: options.system });
}
// Build user message content
let userContent;
if (options.image) {
// Vision request
const imageUrl = imageToBase64(options.image);
userContent = [
{ type: 'image_url', image_url: { url: imageUrl } },
{ type: 'text', text: options.prompt || 'Describe this image.' },
];
// Use vision model if not specified
if (options.model === DEFAULT_MODEL) {
options.model = 'grok-2-vision-1212';
}
} else {
userContent = options.prompt;
}
messages.push({ role: 'user', content: userContent });
const payload = {
model: options.model,
messages: messages,
stream: options.stream,
};
return new Promise((resolve, reject) => {
const req = https.request({
hostname: API_BASE,
path: '/v1/chat/completions',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
}, (res) => {
let data = '';
res.on('data', chunk => {
data += chunk;
if (options.stream) {
// Handle streaming responses
const lines = data.split('\n');
data = lines.pop(); // Keep incomplete line
for (const line of lines) {
if (line.startsWith('data: ') && line !== 'data: [DONE]') {
try {
const json = JSON.parse(line.slice(6));
const content = json.choices?.[0]?.delta?.content;
if (content) {
process.stdout.write(content);
}
} catch (e) {
// Ignore parse errors in stream
}
}
}
}
});
res.on('end', () => {
if (res.statusCode !== 200) {
console.error(`❌ API Error (${res.statusCode}):`, data);
process.exit(1);
}
if (!options.stream) {
try {
const response = JSON.parse(data);
const content = response.choices?.[0]?.message?.content;
if (options.json) {
console.log(JSON.stringify(response, null, 2));
} else {
console.log(content || '(no response)');
}
resolve(response);
} catch (e) {
console.error('❌ Failed to parse response:', e.message);
process.exit(1);
}
} else {
console.log(); // Newline after streaming
resolve();
}
});
});
req.on('error', (e) => {
console.error('❌ Request failed:', e.message);
process.exit(1);
});
req.write(JSON.stringify(payload));
req.end();
});
}
// Main
const args = process.argv.slice(2);
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
console.log(`
🤖 xAI/Grok Chat
Usage:
node chat.js [options] "Your prompt"
Options:
--model, -m <model> Model to use (default: grok-3)
--system, -s <text> System prompt
--image, -i <path> Image for vision analysis
--stream Stream the response
--json, -j Output full JSON response
--help, -h Show this help
Models:
grok-3 Most capable
grok-3-mini Fast and efficient
grok-3-fast Speed optimized
grok-2-vision-1212 Vision/image analysis
Examples:
node chat.js "What is quantum computing?"
node chat.js -m grok-3-mini "Quick: capital of France?"
node chat.js -i photo.jpg "What's in this image?"
node chat.js -s "You are a poet" "Write about the moon"
`);
process.exit(0);
}
const options = parseArgs(args);
if (!options.prompt && !options.image) {
console.error('❌ Please provide a prompt');
process.exit(1);
}
chat(options);

74
scripts/models.js Normal file
View File

@@ -0,0 +1,74 @@
#!/usr/bin/env node
/**
* List available xAI models
*/
const https = require('https');
const API_BASE = 'api.x.ai';
function getApiKey() {
return process.env.XAI_API_KEY || null;
}
async function listModels() {
const apiKey = getApiKey();
if (!apiKey) {
console.error('❌ No API key found. Set XAI_API_KEY or configure in clawdbot.');
process.exit(1);
}
return new Promise((resolve, reject) => {
const req = https.request({
hostname: API_BASE,
path: '/v1/models',
method: 'GET',
headers: {
'Authorization': `Bearer ${apiKey}`,
},
}, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
if (res.statusCode !== 200) {
console.error(`❌ API Error (${res.statusCode}):`, data);
process.exit(1);
}
try {
const response = JSON.parse(data);
const models = response.data || [];
console.log('🤖 Available xAI Models:\n');
for (const model of models) {
console.log(`${model.id}`);
if (model.description) {
console.log(` ${model.description}`);
}
}
if (models.length === 0) {
console.log(' (no models available)');
}
console.log('\nUsage: node chat.js --model <model-id> "Your prompt"');
resolve(models);
} catch (e) {
console.error('❌ Failed to parse response:', e.message);
process.exit(1);
}
});
});
req.on('error', (e) => {
console.error('❌ Request failed:', e.message);
process.exit(1);
});
req.end();
});
}
listModels();

242
scripts/search-x.js Normal file
View File

@@ -0,0 +1,242 @@
#!/usr/bin/env node
/**
* xAI X Search Script - Uses Responses API with x_search tool
*
* Usage:
* node search-x.js "Remotion best practices"
* node search-x.js --days 30 "AI video creation"
* node search-x.js --handles @remotion_dev,@jonnyburger "updates"
*/
const https = require('https');
const API_BASE = 'api.x.ai';
const DEFAULT_MODEL = 'grok-4-1-fast'; // Optimized for agentic search
function getApiKey() {
return process.env.XAI_API_KEY || null;
}
function parseArgs(args) {
const result = {
model: DEFAULT_MODEL,
query: '',
days: 30,
handles: [],
excludeHandles: [],
json: false,
requireCitations: true,
};
let i = 0;
while (i < args.length) {
const arg = args[i];
if (arg === '--model' || arg === '-m') {
result.model = args[++i];
} else if (arg === '--days' || arg === '-d') {
result.days = parseInt(args[++i], 10);
} else if (arg === '--handles' || arg === '-h') {
result.handles = args[++i].split(',').map(h => h.trim());
} else if (arg === '--exclude') {
result.excludeHandles = args[++i].split(',').map(h => h.trim());
} else if (arg === '--json' || arg === '-j') {
result.json = true;
} else if (arg === '--no-require-citations') {
result.requireCitations = false;
} else if (!arg.startsWith('-')) {
result.query = args.slice(i).join(' ');
break;
}
i++;
}
return result;
}
function getDateRange(days) {
const to = new Date();
const from = new Date();
from.setDate(from.getDate() - days);
return {
from_date: from.toISOString().split('T')[0],
to_date: to.toISOString().split('T')[0],
};
}
async function searchX(options) {
const apiKey = getApiKey();
if (!apiKey) {
console.error('❌ No API key found. Set XAI_API_KEY or configure in clawdbot.');
process.exit(1);
}
const dateRange = getDateRange(options.days);
// Build x_search tool config
const xSearchTool = {
type: 'x_search',
x_search: {
from_date: dateRange.from_date,
to_date: dateRange.to_date,
}
};
if (options.handles.length > 0) {
xSearchTool.x_search.allowed_x_handles = options.handles;
}
if (options.excludeHandles.length > 0) {
xSearchTool.x_search.excluded_x_handles = options.excludeHandles;
}
const payload = {
model: options.model,
input: `Search X/Twitter and find real posts about: ${options.query}
Give me actual tweets with:
- Username/handle
- The actual tweet content
- Date if available
- Link to the tweet
Only include REAL posts you find. If you can't find any, say so.`,
tools: [xSearchTool],
};
return new Promise((resolve, reject) => {
const req = https.request({
hostname: API_BASE,
path: '/v1/responses',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
}, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
if (res.statusCode !== 200) {
console.error(`❌ API Error (${res.statusCode}):`, data);
process.exit(1);
}
try {
const response = JSON.parse(data);
if (options.json) {
console.log(JSON.stringify(response, null, 2));
resolve(response);
return;
}
// Extract content from Responses API format
let content = '(no response)';
if (response.output) {
for (const item of response.output) {
if (item.type === 'message' && item.content) {
for (const c of item.content) {
if (c.type === 'output_text' && c.text) {
content = c.text;
}
}
}
}
}
// Extract citations from annotations
let xCitations = [];
if (response.output) {
for (const item of response.output) {
if (item.content) {
for (const c of item.content) {
if (c.annotations) {
for (const ann of c.annotations) {
if (ann.type === 'url_citation' && ann.url) {
if (ann.url.includes('x.com') || ann.url.includes('twitter.com')) {
xCitations.push(ann);
}
}
}
}
}
}
}
}
// Dedupe by URL
xCitations = [...new Map(xCitations.map(c => [c.url, c])).values()];
if (options.requireCitations && xCitations.length === 0) {
console.error('⚠️ No X/Twitter citations found in response.');
console.error(' Grok may not have found relevant posts, or X search may not be enabled.');
console.error('\nResponse anyway:\n');
}
console.log(content);
if (xCitations.length > 0) {
console.log('\n📎 Citations:');
for (const cite of xCitations) {
console.log(` ${cite.url}`);
}
}
resolve(response);
} catch (e) {
console.error('❌ Failed to parse response:', e.message);
console.error('Raw:', data.slice(0, 500));
process.exit(1);
}
});
});
req.on('error', (e) => {
console.error('❌ Request failed:', e.message);
process.exit(1);
});
req.write(JSON.stringify(payload));
req.end();
});
}
// Main
const args = process.argv.slice(2);
if (args.length === 0 || args.includes('--help')) {
console.log(`
🔍 xAI X Search (Responses API)
Usage:
node search-x.js [options] "Your search query"
Options:
--model, -m <model> Model (default: grok-4-1-fast)
--days, -d <n> Search last N days (default: 30)
--handles <list> Only search these handles (comma-separated)
--exclude <list> Exclude these handles (comma-separated)
--json, -j Output full JSON response
--no-require-citations Don't warn if no X citations found
--help Show this help
Examples:
node search-x.js "Remotion video framework tips"
node search-x.js --days 7 "Claude AI"
node search-x.js --handles @remotion_dev "new features"
node search-x.js --days 30 "AI video creation best practices"
`);
process.exit(0);
}
const options = parseArgs(args);
if (!options.query) {
console.error('❌ Please provide a search query');
process.exit(1);
}
console.error(`🔍 Searching X for: "${options.query}" (last ${options.days} days)...\n`);
searchX(options);