social-agent-cli 3.0.2 → 3.1.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/platforms/browser/driver.ts +78 -96
package/package.json
CHANGED
|
@@ -1,75 +1,102 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
1
|
+
import { execSync, spawn } from "node:child_process";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { existsSync, mkdirSync, copyFileSync } from "node:fs";
|
|
3
5
|
import { PATHS } from "../../core/config.js";
|
|
4
6
|
import { loadMap, generateMap } from "../../ai/mapper.js";
|
|
5
7
|
import { pickChromeProfile, getSavedProfile } from "../../core/profiles.js";
|
|
6
8
|
import type { SelectorMap } from "../../core/types.js";
|
|
7
9
|
|
|
10
|
+
const CHROME_PATH = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
|
11
|
+
const CDP_PORT = 9222;
|
|
12
|
+
|
|
8
13
|
/**
|
|
9
|
-
* Browser Driver - agent-browser
|
|
10
|
-
* Playwright yerine native CDP, çok daha hızlı ve güvenilir
|
|
14
|
+
* Browser Driver - agent-browser + gerçek Chrome profili (CDP)
|
|
11
15
|
*/
|
|
12
16
|
export class BrowserDriver {
|
|
13
|
-
private
|
|
17
|
+
private chromeStarted = false;
|
|
18
|
+
private profileDirName = "";
|
|
14
19
|
|
|
15
20
|
constructor(private platform: string, private homeUrl: string) {}
|
|
16
21
|
|
|
22
|
+
/**
|
|
23
|
+
* agent-browser komutu çalıştır (CDP ile)
|
|
24
|
+
*/
|
|
17
25
|
private cmd(command: string, timeout = 15000): string {
|
|
18
|
-
const profileFlag = this.profilePath ? `--profile "${this.profilePath}" --headed` : "--auto-connect";
|
|
19
26
|
try {
|
|
20
|
-
return execSync(`agent-browser ${
|
|
27
|
+
return execSync(`agent-browser --cdp ${CDP_PORT} ${command}`, {
|
|
21
28
|
encoding: "utf-8",
|
|
22
29
|
timeout,
|
|
23
30
|
stdio: ["pipe", "pipe", "pipe"],
|
|
24
31
|
}).trim();
|
|
25
32
|
} catch (err: any) {
|
|
26
|
-
|
|
27
|
-
const stdout = err.stdout?.toString() || "";
|
|
28
|
-
throw new Error(stderr || stdout || err.message);
|
|
33
|
+
throw new Error(err.stderr?.toString()?.trim() || err.stdout?.toString()?.trim() || err.message);
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
/**
|
|
33
|
-
*
|
|
38
|
+
* Chrome'u gerçek profil ile CDP modunda başlat
|
|
34
39
|
*/
|
|
35
|
-
|
|
36
|
-
const profile =
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (!profile) {
|
|
41
|
-
throw new Error(`[${this.platform}] profil seçilmemiş. Önce: social-agent login ${this.platform}`);
|
|
42
|
-
}
|
|
40
|
+
async launch(): Promise<void> {
|
|
41
|
+
const profile = getSavedProfile(this.platform);
|
|
42
|
+
if (!profile) throw new Error(`Profil yok. Önce: social-agent login ${this.platform}`);
|
|
43
|
+
this.profileDirName = profile.dirName;
|
|
43
44
|
|
|
44
|
-
//
|
|
45
|
-
|
|
45
|
+
// CDP'ye bağlanmayı dene
|
|
46
|
+
try {
|
|
47
|
+
execSync(`agent-browser --cdp ${CDP_PORT} url`, { stdio: "pipe", timeout: 2000 });
|
|
48
|
+
return; // zaten çalışıyor
|
|
49
|
+
} catch {}
|
|
46
50
|
|
|
47
|
-
//
|
|
48
|
-
const
|
|
49
|
-
mkdirSync(
|
|
51
|
+
// Chrome'u CDP ile başlat
|
|
52
|
+
const workDir = join(PATHS.profiles, "chrome-cdp");
|
|
53
|
+
mkdirSync(join(workDir, profile.dirName), { recursive: true });
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
for (const f of
|
|
55
|
+
// Cookie'leri kopyala
|
|
56
|
+
for (const f of ["Cookies", "Cookies-journal", "Login Data", "Login Data-journal", "Preferences", "Secure Preferences"]) {
|
|
53
57
|
const src = join(profile.fullPath, f);
|
|
54
|
-
if (existsSync(src)) {
|
|
55
|
-
try { copyFileSync(src, join(this.profilePath, f)); } catch {}
|
|
56
|
-
}
|
|
58
|
+
if (existsSync(src)) { try { copyFileSync(src, join(workDir, profile.dirName, f)); } catch {} }
|
|
57
59
|
}
|
|
60
|
+
const ls = join(homedir(), "Library", "Application Support", "Google", "Chrome", "Local State");
|
|
61
|
+
if (existsSync(ls)) { try { copyFileSync(ls, join(workDir, "Local State")); } catch {} }
|
|
62
|
+
|
|
63
|
+
// agent-browser daemon'u kapat
|
|
64
|
+
execSync("agent-browser close", { stdio: "pipe" }).toString();
|
|
65
|
+
|
|
66
|
+
const child = spawn(CHROME_PATH, [
|
|
67
|
+
`--remote-debugging-port=${CDP_PORT}`,
|
|
68
|
+
`--user-data-dir=${workDir}`,
|
|
69
|
+
`--profile-directory=${profile.dirName}`,
|
|
70
|
+
"--no-first-run", "--no-default-browser-check",
|
|
71
|
+
], { detached: true, stdio: "ignore" });
|
|
72
|
+
child.unref();
|
|
73
|
+
this.chromeStarted = true;
|
|
74
|
+
|
|
75
|
+
// Bağlantıyı bekle
|
|
76
|
+
for (let i = 0; i < 20; i++) {
|
|
77
|
+
await new Promise(r => setTimeout(r, 500));
|
|
78
|
+
try {
|
|
79
|
+
execSync(`agent-browser --cdp ${CDP_PORT} url`, { stdio: "pipe", timeout: 2000 });
|
|
80
|
+
return;
|
|
81
|
+
} catch {}
|
|
82
|
+
}
|
|
83
|
+
throw new Error("Chrome başlatılamadı");
|
|
58
84
|
}
|
|
59
85
|
|
|
86
|
+
async close(): Promise<void> {}
|
|
87
|
+
|
|
60
88
|
/**
|
|
61
|
-
* Login
|
|
89
|
+
* Login
|
|
62
90
|
*/
|
|
63
91
|
async login(): Promise<void> {
|
|
64
|
-
await this.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
execSync(`agent-browser ${profileFlag} open "${this.homeUrl}"`, { stdio: "inherit", timeout: 30000 });
|
|
92
|
+
await pickChromeProfile(this.platform);
|
|
93
|
+
await this.launch();
|
|
94
|
+
this.cmd(`open "${this.homeUrl}"`, 30000);
|
|
68
95
|
|
|
69
96
|
console.log(`\n[${this.platform}] Tarayıcı açıldı: ${this.homeUrl}`);
|
|
70
97
|
const { createInterface } = await import("node:readline");
|
|
71
98
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
72
|
-
await new Promise<void>(
|
|
99
|
+
await new Promise<void>(resolve => {
|
|
73
100
|
rl.question("Hesabın açıksa Enter'a bas: ", () => { rl.close(); resolve(); });
|
|
74
101
|
});
|
|
75
102
|
console.log(`[${this.platform}] Kayıt tamamlandı.`);
|
|
@@ -77,46 +104,36 @@ export class BrowserDriver {
|
|
|
77
104
|
}
|
|
78
105
|
|
|
79
106
|
/**
|
|
80
|
-
*
|
|
107
|
+
* Learn - ARIA snapshot ile map oluştur
|
|
81
108
|
*/
|
|
82
109
|
async learn(taskDescription = "yeni bir metin postu at", actionName = "post"): Promise<SelectorMap> {
|
|
83
|
-
await this.
|
|
84
|
-
|
|
85
|
-
// Sayfaya git
|
|
110
|
+
await this.launch();
|
|
86
111
|
this.cmd(`open "${this.homeUrl}"`, 30000);
|
|
87
112
|
this.cmd("wait 3000");
|
|
88
113
|
|
|
89
|
-
// Screenshot al
|
|
90
114
|
const screenshotPath = join(PATHS.screenshots, `${this.platform}_learn_${actionName}.png`);
|
|
91
115
|
this.cmd(`screenshot "${screenshotPath}"`);
|
|
92
116
|
|
|
93
|
-
//
|
|
94
|
-
const snapshot = this.cmd("snapshot", 10000);
|
|
117
|
+
// Compact interactive snapshot
|
|
118
|
+
const snapshot = this.cmd("snapshot -i -c", 10000);
|
|
95
119
|
|
|
96
120
|
console.log(`[${this.platform}] Screenshot: ${screenshotPath}`);
|
|
97
121
|
console.log(`[${this.platform}] Snapshot: ${snapshot.length} bytes`);
|
|
98
122
|
console.log(`[${this.platform}] Claude analiz ediyor...`);
|
|
99
123
|
|
|
100
124
|
const map = await generateMap(this.platform, screenshotPath, snapshot, taskDescription, actionName);
|
|
101
|
-
|
|
102
125
|
console.log(`[${this.platform}] "${actionName}" map oluşturuldu (${map.steps.length} adım)`);
|
|
103
126
|
process.exit(0);
|
|
104
127
|
return map;
|
|
105
128
|
}
|
|
106
129
|
|
|
107
|
-
/**
|
|
108
|
-
* Sayfaya git
|
|
109
|
-
*/
|
|
110
130
|
navigate(url: string): void {
|
|
111
131
|
this.cmd(`open "${url}"`, 30000);
|
|
112
132
|
this.cmd("wait 2000");
|
|
113
133
|
}
|
|
114
134
|
|
|
115
|
-
/**
|
|
116
|
-
* Tek bir step çalıştır
|
|
117
|
-
*/
|
|
118
135
|
executeStep(step: any, content: string, imagePath?: string): void {
|
|
119
|
-
const
|
|
136
|
+
const val = (step.value || "").replace("{{CONTENT}}", content).replace("{{IMAGE}}", imagePath || "");
|
|
120
137
|
|
|
121
138
|
switch (step.action) {
|
|
122
139
|
case "goto":
|
|
@@ -129,37 +146,25 @@ export class BrowserDriver {
|
|
|
129
146
|
|
|
130
147
|
case "click": {
|
|
131
148
|
const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
|
|
132
|
-
let clicked = false;
|
|
133
149
|
for (const sel of sels) {
|
|
134
150
|
if (sel.includes("{{")) continue;
|
|
135
|
-
try { this.cmd(`click "${sel}"`);
|
|
151
|
+
try { this.cmd(`click "${sel}"`); if (step.waitMs) this.cmd(`wait ${step.waitMs}`); return; } catch {}
|
|
136
152
|
}
|
|
137
|
-
|
|
138
|
-
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
139
|
-
break;
|
|
153
|
+
throw new Error(`Click: ${sels[0]}`);
|
|
140
154
|
}
|
|
141
155
|
|
|
142
156
|
case "type": {
|
|
143
157
|
const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
|
|
144
|
-
let typed = false;
|
|
145
158
|
for (const sel of sels) {
|
|
146
159
|
if (sel.includes("{{")) continue;
|
|
147
160
|
try {
|
|
148
|
-
this.cmd(`
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
this.cmd(`click "${sel}"`);
|
|
154
|
-
this.cmd(`keyboard inserttext "${value.replace(/"/g, '\\"')}"`);
|
|
155
|
-
typed = true;
|
|
156
|
-
break;
|
|
157
|
-
} catch {}
|
|
158
|
-
}
|
|
161
|
+
this.cmd(`click "${sel}"`);
|
|
162
|
+
this.cmd(`keyboard inserttext "${val.replace(/"/g, '\\"')}"`);
|
|
163
|
+
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
164
|
+
return;
|
|
165
|
+
} catch {}
|
|
159
166
|
}
|
|
160
|
-
|
|
161
|
-
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
162
|
-
break;
|
|
167
|
+
throw new Error(`Type: ${sels[0]}`);
|
|
163
168
|
}
|
|
164
169
|
|
|
165
170
|
case "keypress":
|
|
@@ -168,45 +173,22 @@ export class BrowserDriver {
|
|
|
168
173
|
break;
|
|
169
174
|
|
|
170
175
|
case "upload": {
|
|
171
|
-
const filePath =
|
|
176
|
+
const filePath = val || imagePath || "";
|
|
172
177
|
if (!filePath || filePath.includes("{{")) break;
|
|
173
|
-
|
|
174
178
|
const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
|
|
175
|
-
let uploaded = false;
|
|
176
179
|
for (const sel of sels) {
|
|
177
|
-
try {
|
|
178
|
-
this.cmd(`upload "${sel}" "${filePath}"`);
|
|
179
|
-
uploaded = true;
|
|
180
|
-
break;
|
|
181
|
-
} catch {}
|
|
180
|
+
try { this.cmd(`upload "${sel}" "${filePath}"`); if (step.waitMs) this.cmd(`wait ${step.waitMs}`); return; } catch {}
|
|
182
181
|
}
|
|
183
|
-
|
|
184
|
-
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
185
|
-
break;
|
|
182
|
+
throw new Error(`Upload: ${filePath}`);
|
|
186
183
|
}
|
|
187
184
|
}
|
|
188
185
|
}
|
|
189
186
|
|
|
190
|
-
/**
|
|
191
|
-
* Snapshot al (heal için)
|
|
192
|
-
*/
|
|
193
187
|
getSnapshot(): string {
|
|
194
|
-
try { return this.cmd("snapshot", 10000); } catch { return ""; }
|
|
188
|
+
try { return this.cmd("snapshot -i -c", 10000); } catch { return ""; }
|
|
195
189
|
}
|
|
196
190
|
|
|
197
|
-
/**
|
|
198
|
-
* Screenshot al
|
|
199
|
-
*/
|
|
200
191
|
takeScreenshot(path: string): void {
|
|
201
192
|
try { this.cmd(`screenshot "${path}"`); } catch {}
|
|
202
193
|
}
|
|
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
|
-
}
|
|
212
194
|
}
|