Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Post text, images, videos, and long-form articles to X (Twitter) via real Chrome browser automation.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/check-paste-permissions.ts
1import { spawnSync } from 'node:child_process';2import fs from 'node:fs';3import { mkdtemp, rm, writeFile } from 'node:fs/promises';4import os from 'node:os';5import path from 'node:path';6import process from 'node:process';7import { findChromeExecutable, CHROME_CANDIDATES_FULL, getDefaultProfileDir } from './x-utils.js';89interface CheckResult {10name: string;11ok: boolean;12detail: string;13}1415const results: CheckResult[] = [];1617function log(label: string, ok: boolean, detail: string): void {18results.push({ name: label, ok, detail });19const icon = ok ? '✅' : '❌';20console.log(`${icon} ${label}: ${detail}`);21}2223function warn(label: string, detail: string): void {24results.push({ name: label, ok: true, detail });25console.log(`⚠️ ${label}: ${detail}`);26}2728async function checkChrome(): Promise<void> {29const chromePath = findChromeExecutable(CHROME_CANDIDATES_FULL);30if (chromePath) {31log('Chrome', true, chromePath);32} else {33log('Chrome', false, 'Not found. Set X_BROWSER_CHROME_PATH env var or install Chrome.');34}35}3637async function checkProfileIsolation(): Promise<void> {38const profileDir = getDefaultProfileDir();39const userChromeDir = process.platform === 'darwin'40? path.join(os.homedir(), 'Library', 'Application Support', 'Google', 'Chrome')41: process.platform === 'win32'42? path.join(os.homedir(), 'AppData', 'Local', 'Google', 'Chrome', 'User Data')43: path.join(os.homedir(), '.config', 'google-chrome');4445const isIsolated = !profileDir.startsWith(userChromeDir);46log('Profile isolation', isIsolated, `Skill profile: ${profileDir}`);4748if (isIsolated) {49const exists = fs.existsSync(profileDir);50if (exists) {51log('Profile dir', true, 'Exists and accessible');52} else {53try {54fs.mkdirSync(profileDir, { recursive: true });55log('Profile dir', true, 'Created successfully');56} catch (e) {57log('Profile dir', false, `Cannot create: ${e instanceof Error ? e.message : String(e)}`);58}59}60}61}6263async function checkAccessibility(): Promise<void> {64if (process.platform !== 'darwin') {65log('Accessibility', true, `Skipped (not macOS, platform: ${process.platform})`);66return;67}6869const result = spawnSync('osascript', ['-e', `70tell application "System Events"71set frontApp to name of first application process whose frontmost is true72return frontApp73end tell74`], { stdio: 'pipe', timeout: 10_000 });7576if (result.status === 0) {77const app = result.stdout?.toString().trim();78log('Accessibility (System Events)', true, `Frontmost app: ${app}`);79} else {80const stderr = result.stderr?.toString().trim() || '';81if (stderr.includes('not allowed assistive access') || stderr.includes('1002')) {82log('Accessibility (System Events)', false,83'Denied. Grant access: System Settings → Privacy & Security → Accessibility → enable your terminal app');84} else {85log('Accessibility (System Events)', false, `Failed: ${stderr}`);86}87}88}8990async function checkClipboardCopy(): Promise<void> {91if (process.platform !== 'darwin') {92log('Clipboard copy (image)', true, `Skipped (not macOS)`);93return;94}9596const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'x-check-'));97try {98const testPng = path.join(tmpDir, 'test.png');99const swiftSrc = `import AppKit100import Foundation101let size = NSSize(width: 2, height: 2)102let image = NSImage(size: size)103image.lockFocus()104NSColor.red.set()105NSBezierPath.fill(NSRect(origin: .zero, size: size))106image.unlockFocus()107guard let tiff = image.tiffRepresentation,108let rep = NSBitmapImageRep(data: tiff),109let png = rep.representation(using: .png, properties: [:]) else {110FileHandle.standardError.write("Failed to create test PNG\\n".data(using: .utf8)!)111exit(1)112}113try png.write(to: URL(fileURLWithPath: CommandLine.arguments[1]))114`;115const genScript = path.join(tmpDir, 'gen.swift');116await writeFile(genScript, swiftSrc, 'utf8');117const genResult = spawnSync('swift', [genScript, testPng], { stdio: 'pipe', timeout: 30_000 });118if (genResult.status !== 0) {119log('Clipboard copy (image)', false, `Cannot create test image: ${genResult.stderr?.toString().trim()}`);120return;121}122123const clipSrc = `import AppKit124import Foundation125guard let image = NSImage(contentsOfFile: CommandLine.arguments[1]) else {126FileHandle.standardError.write("Failed to load image\\n".data(using: .utf8)!)127exit(1)128}129let pb = NSPasteboard.general130pb.clearContents()131if !pb.writeObjects([image]) {132FileHandle.standardError.write("Failed to write to clipboard\\n".data(using: .utf8)!)133exit(1)134}135`;136const clipScript = path.join(tmpDir, 'clip.swift');137await writeFile(clipScript, clipSrc, 'utf8');138const clipResult = spawnSync('swift', [clipScript, testPng], { stdio: 'pipe', timeout: 30_000 });139if (clipResult.status === 0) {140log('Clipboard copy (image)', true, 'Can copy image to clipboard via Swift/AppKit');141} else {142log('Clipboard copy (image)', false, `Failed: ${clipResult.stderr?.toString().trim()}`);143}144} finally {145await rm(tmpDir, { recursive: true, force: true });146}147}148149async function checkPasteKeystroke(): Promise<void> {150if (process.platform === 'darwin') {151const result = spawnSync('osascript', ['-e', `152tell application "System Events"153-- Dry run: just check we CAN query key sending capability154set canSend to true155return canSend156end tell157`], { stdio: 'pipe', timeout: 10_000 });158159if (result.status === 0) {160log('Paste keystroke (osascript)', true, 'System Events can send keystrokes');161} else {162const stderr = result.stderr?.toString().trim() || '';163log('Paste keystroke (osascript)', false, `Cannot send keystrokes: ${stderr}`);164}165} else if (process.platform === 'linux') {166const xdotool = spawnSync('which', ['xdotool'], { stdio: 'pipe' });167const ydotool = spawnSync('which', ['ydotool'], { stdio: 'pipe' });168if (xdotool.status === 0) {169log('Paste keystroke', true, 'xdotool available (X11)');170} else if (ydotool.status === 0) {171log('Paste keystroke', true, 'ydotool available (Wayland)');172} else {173log('Paste keystroke', false, 'No tool found. Install xdotool (X11) or ydotool (Wayland).');174}175} else if (process.platform === 'win32') {176log('Paste keystroke', true, 'Windows uses PowerShell SendKeys (built-in)');177}178}179180async function checkBun(): Promise<void> {181const result = spawnSync('npx', ['-y', 'bun', '--version'], { stdio: 'pipe', timeout: 30_000 });182if (result.status === 0) {183log('Bun runtime', true, `v${result.stdout?.toString().trim()}`);184} else {185log('Bun runtime', false, 'Cannot run bun. Install: brew install oven-sh/bun/bun (macOS) or npm install -g bun');186}187}188189async function checkRunningChromeConflict(): Promise<void> {190if (process.platform !== 'darwin') return;191192const result = spawnSync('pgrep', ['-f', 'Google Chrome'], { stdio: 'pipe' });193const pids = result.stdout?.toString().trim().split('\n').filter(Boolean) || [];194195if (pids.length > 0) {196warn('Running Chrome instances', `${pids.length} Chrome process(es) detected. The skill uses --user-data-dir for isolation, so this is safe. Paste keystroke targets Chrome by app name (minor risk if multiple Chrome windows visible).`);197} else {198log('Running Chrome instances', true, 'No existing Chrome processes');199}200}201202async function main(): Promise<void> {203console.log('=== baoyu-post-to-x: Permission & Environment Check ===\n');204205await checkChrome();206await checkProfileIsolation();207await checkBun();208await checkAccessibility();209await checkClipboardCopy();210await checkPasteKeystroke();211await checkRunningChromeConflict();212213console.log('\n--- Summary ---');214const failed = results.filter((r) => !r.ok);215if (failed.length === 0) {216console.log('All checks passed. Ready to post to X.');217} else {218console.log(`${failed.length} issue(s) found:`);219for (const f of failed) {220console.log(` ❌ ${f.name}: ${f.detail}`);221}222process.exit(1);223}224}225226await main().catch((err) => {227console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);228process.exit(1);229});230