social-agent-cli 2.3.2 → 3.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/ai/runner.ts CHANGED
@@ -37,19 +37,16 @@ export async function runCommand(platform: string, command: string): Promise<voi
37
37
  if (repeatCount > 1) console.log(` [${r + 1}/${repeatCount}]`);
38
38
 
39
39
  const params = { ...step.parameters };
40
- const post: Post = {
41
- content: params["{{CONTENT}}"] || params["CONTENT"] || "",
42
- images: (params["{{IMAGE}}"] || params["IMAGE"]) ? [params["{{IMAGE}}"] || params["IMAGE"]] : undefined,
43
- platform,
44
- };
40
+ const content = params["{{CONTENT}}"] || params["CONTENT"] || "";
41
+ const image = params["{{IMAGE}}"] || params["IMAGE"] || "";
45
42
 
46
43
  const driver = new BrowserDriver(platform, url);
47
- const page = await driver.launch();
44
+ await driver.launch();
48
45
 
49
46
  try {
47
+ // Ana sayfaya git (ilk step goto değilse)
50
48
  if (map.steps[0]?.action !== "goto") {
51
- await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30000 });
52
- await page.waitForTimeout(3000);
49
+ driver.navigate(url);
53
50
  }
54
51
 
55
52
  let steps = [...map.steps];
@@ -57,19 +54,10 @@ export async function runCommand(platform: string, command: string): Promise<voi
57
54
 
58
55
  for (let idx = 0; idx < steps.length; idx++) {
59
56
  const s = steps[idx];
60
- const rp = (t: string) => replacePlaceholders(t, post, params);
61
57
 
62
58
  try {
63
59
  console.log(` ${idx + 1}. ${s.description}`);
64
-
65
- if (s.action === "goto") {
66
- let gotoUrl = rp(s.url || url);
67
- if (gotoUrl.includes("{{")) gotoUrl = url;
68
- await page.goto(gotoUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
69
- } else {
70
- await executeStep(page, s, post, params);
71
- }
72
- if (s.waitMs) await page.waitForTimeout(s.waitMs);
60
+ driver.executeStep(s, content, image);
73
61
  } catch (err: any) {
74
62
  if (healCount >= 2) {
75
63
  console.log(` ✗ ${err.message} (atlanıyor)`);
@@ -80,12 +68,12 @@ export async function runCommand(platform: string, command: string): Promise<voi
80
68
  console.log(` → Düzeltiliyor...`);
81
69
 
82
70
  const ss = join(PATHS.screenshots, `${platform}_heal.png`);
83
- await page.screenshot({ path: ss }).catch(() => {});
84
- const aria = await page.locator("body").ariaSnapshot().catch(() => "");
71
+ driver.takeScreenshot(ss);
72
+ const snap = driver.getSnapshot();
85
73
 
86
74
  const fixed = await healAndContinue(
87
- platform, idx, steps, ss, aria as string,
88
- err.message, post.content || step.description, "medium"
75
+ platform, idx, steps, ss, snap,
76
+ err.message, content || step.description, "medium"
89
77
  );
90
78
  steps = [...steps.slice(0, idx), ...fixed];
91
79
  idx--;
@@ -93,17 +81,16 @@ export async function runCommand(platform: string, command: string): Promise<voi
93
81
  }
94
82
 
95
83
  console.log(` ✓ Tamamlandı`);
96
- saveToHistory({ platform, content: post.content || step.description, action: mapAction.includes("post") ? "post" : mapAction, success: true });
84
+ saveToHistory({ platform, content: content || step.description, action: mapAction.includes("post") ? "post" : mapAction, success: true });
97
85
  } catch (err: any) {
98
86
  console.log(` ✗ ${err.message}`);
99
- saveToHistory({ platform, content: post.content || step.description, action: mapAction, success: false, error: err.message });
87
+ saveToHistory({ platform, content: content || step.description, action: mapAction, success: false, error: err.message });
100
88
  } finally {
101
89
  await driver.close();
102
90
  }
103
91
 
104
92
  if (r < repeatCount - 1) {
105
- const delay = 2000 + Math.random() * 3000;
106
- await new Promise((res) => setTimeout(res, delay));
93
+ await new Promise((res) => setTimeout(res, 2000 + Math.random() * 3000));
107
94
  }
108
95
  }
109
96
  console.log("");
@@ -112,93 +99,3 @@ export async function runCommand(platform: string, command: string): Promise<voi
112
99
  console.log("[runner] Tamamlandı.");
113
100
  process.exit(0);
114
101
  }
115
-
116
- function replacePlaceholders(text: string, post: Post, params: Record<string, string>): string {
117
- if (!text) return text;
118
- let r = text.replace("{{CONTENT}}", post.content || "");
119
- for (const [k, v] of Object.entries(params)) {
120
- r = r.replace(k.startsWith("{{") ? k : `{{${k}}}`, v);
121
- }
122
- return r;
123
- }
124
-
125
- async function executeStep(page: any, step: any, post: Post, params: Record<string, string>): Promise<void> {
126
- const rp = (t: string) => replacePlaceholders(t, post, params);
127
- const selectors = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean).map((s: string) => rp(s));
128
-
129
- switch (step.action) {
130
- case "wait":
131
- await page.waitForTimeout(step.waitMs || 1000);
132
- break;
133
-
134
- case "click": {
135
- for (const sel of selectors) {
136
- if (sel.includes("{{")) continue;
137
- try {
138
- await page.waitForSelector(sel, { timeout: 5000 });
139
- await page.click(sel);
140
- return;
141
- } catch {}
142
- }
143
- throw new Error(`Click: ${selectors[0]}`);
144
- }
145
-
146
- case "type": {
147
- const text = rp(step.value || "");
148
- for (const sel of selectors) {
149
- if (sel.includes("{{")) continue;
150
- try {
151
- await page.waitForSelector(sel, { timeout: 5000 });
152
- const ce = await page.$eval(sel, (el: Element) =>
153
- el.getAttribute("contenteditable") === "true" || el.getAttribute("role") === "textbox"
154
- ).catch(() => false);
155
- if (ce) {
156
- await page.click(sel);
157
- await page.evaluate(([s, t]: string[]) => {
158
- const el = document.querySelector(s);
159
- if (el) { (el as HTMLElement).focus(); document.execCommand("insertText", false, t); }
160
- }, [sel, text]);
161
- } else {
162
- await page.fill(sel, text);
163
- }
164
- return;
165
- } catch {}
166
- }
167
- throw new Error(`Type: ${selectors[0]}`);
168
- }
169
-
170
- case "keypress":
171
- await page.keyboard.press(step.key || "Enter");
172
- break;
173
-
174
- case "upload": {
175
- const filePath = rp(step.value || "{{IMAGE}}");
176
- if (!filePath || filePath.includes("{{")) break;
177
-
178
- // 1. input[type=file] direkt (file picker açmaz)
179
- try {
180
- const inputs = await page.locator('input[type="file"]').all();
181
- for (const inp of inputs) {
182
- try { await inp.setInputFiles(filePath); return; } catch {}
183
- }
184
- } catch {}
185
-
186
- // 2. fileChooser event listener ile butona tıkla
187
- for (const sel of selectors) {
188
- let done = false;
189
- const handler = async (fc: any) => { await fc.setFiles(filePath); done = true; };
190
- page.on("filechooser", handler);
191
- try {
192
- await page.click(sel);
193
- // fileChooser event'inin tetiklenmesini bekle
194
- for (let w = 0; w < 20 && !done; w++) await page.waitForTimeout(250);
195
- page.removeListener("filechooser", handler);
196
- if (done) return;
197
- } catch {
198
- page.removeListener("filechooser", handler);
199
- }
200
- }
201
- throw new Error(`Upload: ${filePath}`);
202
- }
203
- }
204
- }
@@ -2,18 +2,23 @@
2
2
 
3
3
  import { execSync } from "node:child_process";
4
4
 
5
+ // agent-browser kurulumu
5
6
  try {
6
- console.log(" Playwright Chromium yükleniyor...");
7
- execSync("npx playwright install chromium", { stdio: "pipe" });
8
- console.log(" ✓ Playwright Chromium yüklendi");
7
+ execSync("agent-browser --version", { stdio: "pipe" });
8
+ console.log(" agent-browser zaten kurulu");
9
9
  } catch {
10
- console.log(" ⚠ Playwright Chromium otomatik yüklenemedi.");
11
- console.log(" Manuel kur: npx playwright install chromium");
10
+ try {
11
+ console.log(" agent-browser kuruluyor...");
12
+ execSync("npm install -g agent-browser", { stdio: "pipe" });
13
+ execSync("agent-browser install", { stdio: "pipe" });
14
+ console.log(" ✓ agent-browser kuruldu");
15
+ } catch {
16
+ console.log(" ⚠ agent-browser otomatik kurulamadı.");
17
+ console.log(" Manuel kur: npm install -g agent-browser && agent-browser install");
18
+ }
12
19
  }
13
20
 
14
21
  console.log(`
15
22
  ✓ social-agent kuruldu!
16
-
17
- Başlamak için:
18
- social-agent setup
23
+ Başlamak için: social-agent setup
19
24
  `);
@@ -1,37 +1,22 @@
1
- import { chromium } from "playwright";
1
+ import { execSync } from "node:child_process";
2
2
 
3
3
  /**
4
- * HTML dosyasını JPEG'e çevirir
4
+ * HTML dosyasını JPEG/PNG'ye çevirir
5
5
  * Kullanım: npx tsx core/screenshot.ts /tmp/input.html /tmp/output.jpeg 1200 675
6
6
  */
7
- async function main() {
8
- const [, , htmlPath, outputPath, widthStr = "1200", heightStr = "675"] = process.argv;
7
+ const [, , htmlPath, outputPath, widthStr = "1200", heightStr = "675"] = process.argv;
9
8
 
10
- if (!htmlPath || !outputPath) {
11
- console.error("Kullanım: npx tsx core/screenshot.ts <html-path> <output-path> [width] [height]");
12
- process.exit(1);
13
- }
14
-
15
- const width = parseInt(widthStr);
16
- const height = parseInt(heightStr);
17
-
18
- const browser = await chromium.launch({ headless: true });
19
- const page = await browser.newPage({ viewport: { width, height } });
20
- await page.goto(`file://${htmlPath}`);
21
- await page.waitForTimeout(500);
22
-
23
- const isJpeg = outputPath.endsWith(".jpeg") || outputPath.endsWith(".jpg");
24
- await page.screenshot({
25
- path: outputPath,
26
- type: isJpeg ? "jpeg" : "png",
27
- quality: isJpeg ? 100 : undefined,
28
- });
29
-
30
- await browser.close();
31
- console.log(outputPath);
9
+ if (!htmlPath || !outputPath) {
10
+ console.error("Kullanım: npx tsx core/screenshot.ts <html-path> <output-path> [width] [height]");
11
+ process.exit(1);
32
12
  }
33
13
 
34
- main().catch((err) => {
14
+ try {
15
+ execSync(`agent-browser --viewport ${widthStr}x${heightStr} open "file://${htmlPath}"`, { stdio: "pipe", timeout: 15000 });
16
+ execSync(`agent-browser wait 500`, { stdio: "pipe" });
17
+ execSync(`agent-browser screenshot "${outputPath}"`, { stdio: "pipe", timeout: 10000 });
18
+ console.log(outputPath);
19
+ } catch (err: any) {
35
20
  console.error(err.message);
36
21
  process.exit(1);
37
- });
22
+ }
package/install.ts CHANGED
@@ -148,7 +148,8 @@ Gönderme komutları (HER ZAMAN bu komutları kullan, asla cd veya npx tsx kulla
148
148
  if (!existsSync(join(PKG_DIR, "node_modules"))) {
149
149
  execSync("npm install", { cwd: PKG_DIR, stdio: "pipe" });
150
150
  }
151
- try { execSync("npx playwright install chromium", { cwd: ROOT, stdio: "pipe" }); } catch {}
151
+ // agent-browser Chrome kurulumu
152
+ try { execSync("agent-browser install", { stdio: "pipe", timeout: 120000 }); } catch {}
152
153
 
153
154
  // Claude Code permissions - social-agent komutları otomatik onay
154
155
  const claudeSettingsPath = join(homedir(), ".claude", "settings.json");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "social-agent-cli",
3
- "version": "2.3.2",
3
+ "version": "3.0.0",
4
4
  "description": "AI-powered social media agent - free APIs + browser automation with self-healing selectors",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,7 +24,6 @@
24
24
  "tsconfig.json"
25
25
  ],
26
26
  "dependencies": {
27
- "playwright": "^1.50.0",
28
27
  "tsx": "^4.19.0"
29
28
  },
30
29
  "devDependencies": {
@@ -1,354 +1,210 @@
1
- import { chromium, type Browser, type BrowserContext, type Page } from "playwright";
1
+ import { execSync } from "node:child_process";
2
2
  import { join } from "node:path";
3
- import { homedir } from "node:os";
4
- import { execSync, spawn, type ChildProcess } from "node:child_process";
5
3
  import { PATHS } from "../../core/config.js";
6
- import { loadMap, generateMap, healAndContinue } from "../../ai/mapper.js";
7
- import { pickChromeProfile, getSavedProfile, closeReadline } from "../../core/profiles.js";
8
- import type { Post, PostResult, SelectorMap, SelectorStep } from "../../core/types.js";
9
-
10
- const MAX_HEAL_ATTEMPTS = 3;
11
- const CHROME_PATH = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
12
- const CDP_PORT = 9222;
13
-
4
+ import { loadMap, generateMap } from "../../ai/mapper.js";
5
+ import { pickChromeProfile, getSavedProfile } from "../../core/profiles.js";
6
+ import type { SelectorMap } from "../../core/types.js";
7
+
8
+ /**
9
+ * Browser Driver - agent-browser (Rust) ile browser otomasyonu
10
+ * Playwright yerine native CDP, çok daha hızlı ve güvenilir
11
+ */
14
12
  export class BrowserDriver {
15
- private browser: Browser | null = null;
16
- private context: BrowserContext | null = null;
17
- private page: Page | null = null;
18
- private chromeProcess: ChildProcess | null = null;
19
- private weStartedChrome = false;
13
+ private profilePath: string = "";
20
14
 
21
15
  constructor(private platform: string, private homeUrl: string) {}
22
16
 
23
- /**
24
- * Çalışan Chrome'un CDP'sine bağlanmayı dene
25
- */
26
- private async tryConnectCDP(): Promise<boolean> {
17
+ private cmd(command: string, timeout = 15000): string {
18
+ const profileFlag = this.profilePath ? `--profile "${this.profilePath}"` : "";
27
19
  try {
28
- const res = await fetch(`http://127.0.0.1:${CDP_PORT}/json/version`, {
29
- signal: AbortSignal.timeout(1000),
30
- });
31
- if (res.ok) {
32
- const data = (await res.json()) as any;
33
- this.browser = await chromium.connectOverCDP(`http://127.0.0.1:${CDP_PORT}`);
34
- this.context = this.browser.contexts()[0];
35
- return true;
36
- }
37
- } catch {}
38
- return false;
39
- }
40
-
41
- /**
42
- * Chrome'u debug port ile başlat
43
- * Chrome kendi default data-dir'iyle CDP kabul etmiyor,
44
- * bu yüzden profil dosyalarını ayrı bir dizine kopyalayıp oradan açıyoruz.
45
- */
46
- private async startChrome(profileDirName: string): Promise<void> {
47
- const srcDataDir = join(homedir(), "Library", "Application Support", "Google", "Chrome");
48
- const workDir = join(PATHS.profiles, "chrome-cdp");
49
- const workProfileDir = join(workDir, profileDirName);
50
-
51
- // Çalışma dizinini oluştur
52
- const { mkdirSync, copyFileSync, existsSync: exists } = await import("node:fs");
53
- mkdirSync(workProfileDir, { recursive: true });
54
-
55
- // Cookie ve login verilerini kopyala
56
- const filesToCopy = [
57
- "Cookies", "Cookies-journal",
58
- "Login Data", "Login Data-journal",
59
- "Web Data", "Web Data-journal",
60
- "Preferences", "Secure Preferences",
61
- ];
62
- const srcProfileDir = join(srcDataDir, profileDirName);
63
- for (const f of filesToCopy) {
64
- const src = join(srcProfileDir, f);
65
- if (exists(src)) {
66
- try { copyFileSync(src, join(workProfileDir, f)); } catch {}
67
- }
68
- }
69
- // Local State ana dizinde
70
- const localState = join(srcDataDir, "Local State");
71
- if (exists(localState)) {
72
- try { copyFileSync(localState, join(workDir, "Local State")); } catch {}
73
- }
74
-
75
- this.chromeProcess = spawn(CHROME_PATH, [
76
- `--remote-debugging-port=${CDP_PORT}`,
77
- `--user-data-dir=${workDir}`,
78
- `--profile-directory=${profileDirName}`,
79
- "--no-first-run",
80
- "--no-default-browser-check",
81
- ], {
82
- detached: true,
83
- stdio: "ignore",
84
- });
85
- this.chromeProcess.unref();
86
- this.weStartedChrome = true;
87
-
88
- for (let i = 0; i < 30; i++) {
89
- await new Promise((r) => setTimeout(r, 500));
90
- if (await this.tryConnectCDP()) return;
20
+ return execSync(`agent-browser ${profileFlag} ${command}`, {
21
+ encoding: "utf-8",
22
+ timeout,
23
+ stdio: ["pipe", "pipe", "pipe"],
24
+ }).trim();
25
+ } catch (err: any) {
26
+ const stderr = err.stderr?.toString() || "";
27
+ const stdout = err.stdout?.toString() || "";
28
+ throw new Error(stderr || stdout || err.message);
91
29
  }
92
- throw new Error("Chrome başlatıldı ama CDP bağlantısı kurulamadı");
93
30
  }
94
31
 
95
32
  /**
96
- * Chrome'u veya mevcut olana bağlan
33
+ * Profil seç ve hazırla
97
34
  */
98
- async launch(askProfile = false): Promise<Page> {
35
+ private async setupProfile(askProfile = false): Promise<void> {
99
36
  const profile = askProfile
100
37
  ? await pickChromeProfile(this.platform)
101
38
  : getSavedProfile(this.platform);
102
39
 
103
40
  if (!profile) {
104
- throw new Error(
105
- `[${this.platform}] profil seçilmemiş. Önce: npx tsx cli.ts login ${this.platform}`
106
- );
41
+ throw new Error(`[${this.platform}] profil seçilmemiş. Önce: social-agent login ${this.platform}`);
107
42
  }
108
43
 
109
- // Önce açık Chrome'a bağlanmayı dene
110
- const connected = await this.tryConnectCDP();
44
+ // agent-browser profil dizini
45
+ this.profilePath = join(PATHS.profiles, `ab-${this.platform}`);
111
46
 
112
- if (!connected) {
113
- // Chrome kapalı, başlat
114
- console.log(`[${this.platform}] Chrome başlatılıyor (${profile.name})...`);
115
- await this.startChrome(profile.dirName);
116
- } else {
117
- console.log(`[${this.platform}] Açık Chrome'a bağlanıldı.`);
118
- }
47
+ // İlk kullanımda Chrome cookie'lerini kopyala
48
+ const { existsSync, mkdirSync, copyFileSync } = await import("node:fs");
49
+ mkdirSync(this.profilePath, { recursive: true });
119
50
 
120
- // Yeni sekme
121
- this.page = await this.context!.newPage();
122
- return this.page;
123
- }
124
-
125
- async close(): Promise<void> {
126
- // Sadece sekmeyi kapat, Chrome'u kapatma
127
- if (this.page) {
128
- try { await this.page.close(); } catch {}
51
+ const cookieFiles = ["Cookies", "Cookies-journal", "Login Data", "Login Data-journal", "Preferences"];
52
+ for (const f of cookieFiles) {
53
+ const src = join(profile.fullPath, f);
54
+ if (existsSync(src)) {
55
+ try { copyFileSync(src, join(this.profilePath, f)); } catch {}
56
+ }
129
57
  }
130
58
  }
131
59
 
132
60
  /**
133
- * Login - profil seç, Chrome'da x.com
61
+ * Login - profil seç, tarayıcı
134
62
  */
135
63
  async login(): Promise<void> {
136
- const page = await this.launch(true);
137
- await page.goto(this.homeUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
64
+ await this.setupProfile(true);
65
+ this.cmd(`--headed open "${this.homeUrl}"`, 30000);
138
66
 
139
- console.log(`\n[${this.platform}] Sekme açıldı: ${this.homeUrl}`);
67
+ console.log(`\n[${this.platform}] Tarayıcı açıldı: ${this.homeUrl}`);
140
68
  const { createInterface } = await import("node:readline");
141
69
  const rl = createInterface({ input: process.stdin, output: process.stdout });
142
70
  await new Promise<void>((resolve) => {
143
- rl.question("Hesabın açıksa Enter'a bas, değilse giriş yap sonra Enter: ", () => {
144
- rl.close();
145
- resolve();
146
- });
71
+ rl.question("Hesabın açıksa Enter'a bas: ", () => { rl.close(); resolve(); });
147
72
  });
148
-
149
73
  console.log(`[${this.platform}] Kayıt tamamlandı.`);
150
- closeReadline();
151
- await this.close();
74
+ process.exit(0);
152
75
  }
153
76
 
154
77
  /**
155
78
  * Sayfayı öğren - AI ile selector map oluştur
156
79
  */
157
80
  async learn(taskDescription = "yeni bir metin postu at", actionName = "post"): Promise<SelectorMap> {
158
- const page = await this.launch();
159
- await page.goto(this.homeUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
160
- await page.waitForTimeout(3000);
81
+ await this.setupProfile();
161
82
 
83
+ // Sayfaya git
84
+ this.cmd(`open "${this.homeUrl}"`, 30000);
85
+ this.cmd("wait 3000");
86
+
87
+ // Screenshot al
162
88
  const screenshotPath = join(PATHS.screenshots, `${this.platform}_learn_${actionName}.png`);
163
- await page.screenshot({ path: screenshotPath, fullPage: false });
89
+ this.cmd(`screenshot "${screenshotPath}"`);
164
90
 
165
- // ARIA Snapshot - accessibility tree (98x daha küçük, sadece interaktif elementler)
166
- const ariaSnapshot = await page.locator('body').ariaSnapshot();
91
+ // ARIA snapshot al (agent-browser'ın en güçlü özelliği)
92
+ const snapshot = this.cmd("snapshot", 10000);
167
93
 
168
94
  console.log(`[${this.platform}] Screenshot: ${screenshotPath}`);
169
- console.log(`[${this.platform}] Accessibility tree: ${ariaSnapshot.length} bytes`);
95
+ console.log(`[${this.platform}] Snapshot: ${snapshot.length} bytes`);
170
96
  console.log(`[${this.platform}] Claude analiz ediyor...`);
171
97
 
172
- const map = await generateMap(this.platform, screenshotPath, ariaSnapshot, taskDescription, actionName);
98
+ const map = await generateMap(this.platform, screenshotPath, snapshot, taskDescription, actionName);
173
99
 
174
100
  console.log(`[${this.platform}] "${actionName}" map oluşturuldu (${map.steps.length} adım)`);
175
- await this.close();
101
+ process.exit(0);
176
102
  return map;
177
103
  }
178
104
 
179
105
  /**
180
- * Post at - map kullanarak adımları uygula, hata olursa heal et
106
+ * Sayfaya git
181
107
  */
182
- async post(post: Post): Promise<PostResult> {
183
- let map = loadMap(this.platform);
184
- if (!map) {
185
- console.log(`[${this.platform}] Map yok, öğrenme başlatılıyor...`);
186
- map = await this.learn();
187
- }
188
-
189
- const page = await this.launch();
190
-
191
- try {
192
- await page.goto(this.homeUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
193
- await page.waitForTimeout(2000);
194
-
195
- let steps = [...map.steps];
196
- let i = 0;
197
-
198
- while (i < steps.length) {
199
- const step = steps[i];
200
- try {
201
- console.log(`[${this.platform}] Step ${i + 1}/${steps.length}: ${step.description}`);
202
- await this.executeStep(page, step, post);
203
- i++;
204
- } catch (err: any) {
205
- console.log(`[${this.platform}] Step #${i + 1} başarısız: ${err.message}`);
206
-
207
- // Screenshot + DOM al, AI'a gönder
208
- const healScreenshot = join(PATHS.screenshots, `${this.platform}_heal.png`);
209
- await page.screenshot({ path: healScreenshot });
210
-
211
- const domSnap = await page.evaluate(`(function() {
212
- var SKIP = {"script":1,"style":1,"noscript":1,"link":1,"meta":1};
213
- function ser(el, d, b) {
214
- if (d > 15 || b.c <= 0) return "";
215
- var t = el.tagName.toLowerCase();
216
- if (SKIP[t]) return "";
217
- if (t === "svg") return "<svg/>";
218
- var st = window.getComputedStyle(el);
219
- if (st.display === "none" || st.visibility === "hidden") return "";
220
- var at = [];
221
- for (var i = 0; i < el.attributes.length; i++) {
222
- var a = el.attributes[i];
223
- if (a.name === "style") continue;
224
- at.push(a.name + '="' + (a.value.length > 150 ? a.value.substring(0,150) : a.value).replace(/"/g,"'") + '"');
225
- }
226
- var as = at.length ? " " + at.join(" ") : "";
227
- var dt = "";
228
- for (var j = 0; j < el.childNodes.length; j++) {
229
- if (el.childNodes[j].nodeType === 3) { var tx = (el.childNodes[j].textContent||"").trim(); if (tx) dt += tx.substring(0,100); }
230
- }
231
- var ch = "";
232
- for (var k = 0; k < el.children.length; k++) ch += ser(el.children[k], d+1, b);
233
- if (!as && !dt && !ch) return "";
234
- var r = "<"+t+as+">"+dt+ch+"</"+t+">";
235
- b.c -= r.length;
236
- return r;
237
- }
238
- return ser(document.body, 0, {c:500000});
239
- })()`);
240
-
241
- // AI kaldığı yerden devam etsin
242
- const newSteps = await healAndContinue(
243
- this.platform,
244
- i,
245
- steps,
246
- healScreenshot,
247
- domSnap as string,
248
- err.message,
249
- post.content
250
- );
251
-
252
- // Kalan adımları yeni planla değiştir
253
- steps = [...steps.slice(0, i), ...newSteps];
254
- // i'yi artırma - yeni ilk adımı deneyecek
255
- }
256
- }
257
-
258
- await page.waitForTimeout(2000);
259
- await this.close();
260
- return { success: true, platform: this.platform };
261
- } catch (err: any) {
262
- await this.close();
263
- return { success: false, platform: this.platform, error: err.message };
264
- }
108
+ navigate(url: string): void {
109
+ this.cmd(`open "${url}"`, 30000);
110
+ this.cmd("wait 2000");
265
111
  }
266
112
 
267
113
  /**
268
- * Tek bir step'i uygula
269
- */
270
- /**
271
- * Parametre map'i - post objesinden + ekstra params'dan placeholder'ları doldurur
114
+ * Tek bir step çalıştır
272
115
  */
273
- private replacePlaceholders(text: string, post: Post, params?: Record<string, string>): string {
274
- let result = text;
275
- result = result.replace("{{CONTENT}}", post.content || "");
276
- result = result.replace("{{IMAGE}}", post.images?.[0] || "");
277
- if (params) {
278
- for (const [key, val] of Object.entries(params)) {
279
- result = result.replace(`{{${key}}}`, val);
280
- }
281
- }
282
- return result;
283
- }
284
-
285
- private async executeStep(page: Page, step: SelectorStep, post: Post, params?: Record<string, string>): Promise<void> {
286
- const selectors = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean) as string[];
116
+ executeStep(step: any, content: string, imagePath?: string): void {
117
+ const value = (step.value || "").replace("{{CONTENT}}", content).replace("{{IMAGE}}", imagePath || "");
287
118
 
288
119
  switch (step.action) {
289
- case "goto": {
290
- const url = this.replacePlaceholders(step.url || this.homeUrl, post, params);
291
- await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30000 });
292
- if (step.waitMs) await page.waitForTimeout(step.waitMs);
120
+ case "goto":
121
+ this.navigate(step.url || this.homeUrl);
293
122
  break;
294
- }
295
123
 
296
124
  case "wait":
297
- await page.waitForTimeout(step.waitMs || 1000);
125
+ this.cmd(`wait ${step.waitMs || 1000}`);
298
126
  break;
299
127
 
300
128
  case "click": {
129
+ const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
301
130
  let clicked = false;
302
- for (const sel of selectors) {
303
- try {
304
- await page.waitForSelector(sel, { timeout: 5000 });
305
- await page.click(sel);
306
- clicked = true;
307
- break;
308
- } catch { /* next */ }
131
+ for (const sel of sels) {
132
+ if (sel.includes("{{")) continue;
133
+ try { this.cmd(`click "${sel}"`); clicked = true; break; } catch {}
309
134
  }
310
- if (!clicked) throw new Error(`Click failed: ${selectors.join(", ")}`);
311
- if (step.waitMs) await page.waitForTimeout(step.waitMs);
135
+ if (!clicked) throw new Error(`Click: ${sels[0]}`);
136
+ if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
312
137
  break;
313
138
  }
314
139
 
315
140
  case "type": {
316
- const text = this.replacePlaceholders(step.value || "", post, params);
317
-
141
+ const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
318
142
  let typed = false;
319
- for (const sel of selectors) {
143
+ for (const sel of sels) {
144
+ if (sel.includes("{{")) continue;
320
145
  try {
321
- await page.waitForSelector(sel, { timeout: 5000 });
322
- const isContentEditable = await page.$eval(sel, (el: Element) =>
323
- el.getAttribute("contenteditable") === "true" ||
324
- el.getAttribute("role") === "textbox"
325
- ).catch(() => false);
326
-
327
- if (isContentEditable) {
328
- await page.click(sel);
329
- await page.keyboard.type(text, { delay: 30 });
330
- } else {
331
- await page.fill(sel, text);
332
- }
146
+ this.cmd(`fill "${sel}" "${value.replace(/"/g, '\\"')}"`);
333
147
  typed = true;
334
148
  break;
335
- } catch { /* next */ }
149
+ } catch {
150
+ try {
151
+ this.cmd(`click "${sel}"`);
152
+ this.cmd(`keyboard inserttext "${value.replace(/"/g, '\\"')}"`);
153
+ typed = true;
154
+ break;
155
+ } catch {}
156
+ }
336
157
  }
337
- if (!typed) throw new Error(`Type failed: ${selectors.join(", ")}`);
338
- if (step.waitMs) await page.waitForTimeout(step.waitMs);
158
+ if (!typed) throw new Error(`Type: ${sels[0]}`);
159
+ if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
339
160
  break;
340
161
  }
341
162
 
342
163
  case "keypress":
343
- await page.keyboard.press(step.key || "Enter");
344
- if (step.waitMs) await page.waitForTimeout(step.waitMs);
164
+ this.cmd(`press ${step.key || "Enter"}`);
165
+ if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
345
166
  break;
346
167
 
347
- case "screenshot": {
348
- const p = join(PATHS.screenshots, `${this.platform}_step.png`);
349
- await page.screenshot({ path: p });
168
+ case "upload": {
169
+ const filePath = value || imagePath || "";
170
+ if (!filePath || filePath.includes("{{")) break;
171
+
172
+ const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
173
+ let uploaded = false;
174
+ for (const sel of sels) {
175
+ try {
176
+ this.cmd(`upload "${sel}" "${filePath}"`);
177
+ uploaded = true;
178
+ break;
179
+ } catch {}
180
+ }
181
+ if (!uploaded) throw new Error(`Upload: ${filePath}`);
182
+ if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
350
183
  break;
351
184
  }
352
185
  }
353
186
  }
187
+
188
+ /**
189
+ * Snapshot al (heal için)
190
+ */
191
+ getSnapshot(): string {
192
+ try { return this.cmd("snapshot", 10000); } catch { return ""; }
193
+ }
194
+
195
+ /**
196
+ * Screenshot al
197
+ */
198
+ takeScreenshot(path: string): void {
199
+ try { this.cmd(`screenshot "${path}"`); } catch {}
200
+ }
201
+
202
+ async close(): Promise<void> {
203
+ // agent-browser daemon otomatik yönetir
204
+ }
205
+
206
+ async launch(): Promise<any> {
207
+ await this.setupProfile();
208
+ return null; // agent-browser'da page objesi yok, cmd ile çalışıyor
209
+ }
354
210
  }