social-agent-cli 2.3.2 → 3.0.1

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.1",
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,212 @@
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}" --headed` : "--auto-connect";
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
+ // Login için --profile ile --headed
66
+ const profileFlag = `--profile "${this.profilePath}" --headed`;
67
+ execSync(`agent-browser ${profileFlag} open "${this.homeUrl}"`, { stdio: "pipe", timeout: 30000 });
138
68
 
139
- console.log(`\n[${this.platform}] Sekme açıldı: ${this.homeUrl}`);
69
+ console.log(`\n[${this.platform}] Tarayıcı açıldı: ${this.homeUrl}`);
140
70
  const { createInterface } = await import("node:readline");
141
71
  const rl = createInterface({ input: process.stdin, output: process.stdout });
142
72
  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
- });
73
+ rl.question("Hesabın açıksa Enter'a bas: ", () => { rl.close(); resolve(); });
147
74
  });
148
-
149
75
  console.log(`[${this.platform}] Kayıt tamamlandı.`);
150
- closeReadline();
151
- await this.close();
76
+ process.exit(0);
152
77
  }
153
78
 
154
79
  /**
155
80
  * Sayfayı öğren - AI ile selector map oluştur
156
81
  */
157
82
  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);
83
+ await this.setupProfile();
161
84
 
85
+ // Sayfaya git
86
+ this.cmd(`open "${this.homeUrl}"`, 30000);
87
+ this.cmd("wait 3000");
88
+
89
+ // Screenshot al
162
90
  const screenshotPath = join(PATHS.screenshots, `${this.platform}_learn_${actionName}.png`);
163
- await page.screenshot({ path: screenshotPath, fullPage: false });
91
+ this.cmd(`screenshot "${screenshotPath}"`);
164
92
 
165
- // ARIA Snapshot - accessibility tree (98x daha küçük, sadece interaktif elementler)
166
- const ariaSnapshot = await page.locator('body').ariaSnapshot();
93
+ // ARIA snapshot al (agent-browser'ın en güçlü özelliği)
94
+ const snapshot = this.cmd("snapshot", 10000);
167
95
 
168
96
  console.log(`[${this.platform}] Screenshot: ${screenshotPath}`);
169
- console.log(`[${this.platform}] Accessibility tree: ${ariaSnapshot.length} bytes`);
97
+ console.log(`[${this.platform}] Snapshot: ${snapshot.length} bytes`);
170
98
  console.log(`[${this.platform}] Claude analiz ediyor...`);
171
99
 
172
- const map = await generateMap(this.platform, screenshotPath, ariaSnapshot, taskDescription, actionName);
100
+ const map = await generateMap(this.platform, screenshotPath, snapshot, taskDescription, actionName);
173
101
 
174
102
  console.log(`[${this.platform}] "${actionName}" map oluşturuldu (${map.steps.length} adım)`);
175
- await this.close();
103
+ process.exit(0);
176
104
  return map;
177
105
  }
178
106
 
179
107
  /**
180
- * Post at - map kullanarak adımları uygula, hata olursa heal et
108
+ * Sayfaya git
181
109
  */
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
- }
110
+ navigate(url: string): void {
111
+ this.cmd(`open "${url}"`, 30000);
112
+ this.cmd("wait 2000");
265
113
  }
266
114
 
267
115
  /**
268
- * Tek bir step'i uygula
269
- */
270
- /**
271
- * Parametre map'i - post objesinden + ekstra params'dan placeholder'ları doldurur
116
+ * Tek bir step çalıştır
272
117
  */
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[];
118
+ executeStep(step: any, content: string, imagePath?: string): void {
119
+ const value = (step.value || "").replace("{{CONTENT}}", content).replace("{{IMAGE}}", imagePath || "");
287
120
 
288
121
  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);
122
+ case "goto":
123
+ this.navigate(step.url || this.homeUrl);
293
124
  break;
294
- }
295
125
 
296
126
  case "wait":
297
- await page.waitForTimeout(step.waitMs || 1000);
127
+ this.cmd(`wait ${step.waitMs || 1000}`);
298
128
  break;
299
129
 
300
130
  case "click": {
131
+ const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
301
132
  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 */ }
133
+ for (const sel of sels) {
134
+ if (sel.includes("{{")) continue;
135
+ try { this.cmd(`click "${sel}"`); clicked = true; break; } catch {}
309
136
  }
310
- if (!clicked) throw new Error(`Click failed: ${selectors.join(", ")}`);
311
- if (step.waitMs) await page.waitForTimeout(step.waitMs);
137
+ if (!clicked) throw new Error(`Click: ${sels[0]}`);
138
+ if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
312
139
  break;
313
140
  }
314
141
 
315
142
  case "type": {
316
- const text = this.replacePlaceholders(step.value || "", post, params);
317
-
143
+ const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
318
144
  let typed = false;
319
- for (const sel of selectors) {
145
+ for (const sel of sels) {
146
+ if (sel.includes("{{")) continue;
320
147
  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
- }
148
+ this.cmd(`fill "${sel}" "${value.replace(/"/g, '\\"')}"`);
333
149
  typed = true;
334
150
  break;
335
- } catch { /* next */ }
151
+ } catch {
152
+ try {
153
+ this.cmd(`click "${sel}"`);
154
+ this.cmd(`keyboard inserttext "${value.replace(/"/g, '\\"')}"`);
155
+ typed = true;
156
+ break;
157
+ } catch {}
158
+ }
336
159
  }
337
- if (!typed) throw new Error(`Type failed: ${selectors.join(", ")}`);
338
- if (step.waitMs) await page.waitForTimeout(step.waitMs);
160
+ if (!typed) throw new Error(`Type: ${sels[0]}`);
161
+ if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
339
162
  break;
340
163
  }
341
164
 
342
165
  case "keypress":
343
- await page.keyboard.press(step.key || "Enter");
344
- if (step.waitMs) await page.waitForTimeout(step.waitMs);
166
+ this.cmd(`press ${step.key || "Enter"}`);
167
+ if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
345
168
  break;
346
169
 
347
- case "screenshot": {
348
- const p = join(PATHS.screenshots, `${this.platform}_step.png`);
349
- await page.screenshot({ path: p });
170
+ case "upload": {
171
+ const filePath = value || imagePath || "";
172
+ if (!filePath || filePath.includes("{{")) break;
173
+
174
+ const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
175
+ let uploaded = false;
176
+ for (const sel of sels) {
177
+ try {
178
+ this.cmd(`upload "${sel}" "${filePath}"`);
179
+ uploaded = true;
180
+ break;
181
+ } catch {}
182
+ }
183
+ if (!uploaded) throw new Error(`Upload: ${filePath}`);
184
+ if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
350
185
  break;
351
186
  }
352
187
  }
353
188
  }
189
+
190
+ /**
191
+ * Snapshot al (heal için)
192
+ */
193
+ getSnapshot(): string {
194
+ try { return this.cmd("snapshot", 10000); } catch { return ""; }
195
+ }
196
+
197
+ /**
198
+ * Screenshot al
199
+ */
200
+ takeScreenshot(path: string): void {
201
+ try { this.cmd(`screenshot "${path}"`); } catch {}
202
+ }
203
+
204
+ async close(): Promise<void> {
205
+ // agent-browser daemon otomatik yönetir
206
+ }
207
+
208
+ async launch(): Promise<any> {
209
+ await this.setupProfile();
210
+ return null; // agent-browser'da page objesi yok, cmd ile çalışıyor
211
+ }
354
212
  }