social-agent-cli 3.0.2 → 3.1.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/package.json +1 -1
- package/platforms/browser/driver.ts +79 -96
package/package.json
CHANGED
|
@@ -1,75 +1,103 @@
|
|
|
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(`curl -sf http://127.0.0.1:${CDP_PORT}/json/version`, { 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 < 30; i++) {
|
|
77
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
78
|
+
try {
|
|
79
|
+
execSync(`curl -sf http://127.0.0.1:${CDP_PORT}/json/version`, { stdio: "pipe", timeout: 3000 });
|
|
80
|
+
console.log(`[${this.platform}] Chrome bağlandı`);
|
|
81
|
+
return;
|
|
82
|
+
} catch {}
|
|
83
|
+
}
|
|
84
|
+
throw new Error("Chrome başlatılamadı - Chrome'u kapat ve tekrar dene");
|
|
58
85
|
}
|
|
59
86
|
|
|
87
|
+
async close(): Promise<void> {}
|
|
88
|
+
|
|
60
89
|
/**
|
|
61
|
-
* Login
|
|
90
|
+
* Login
|
|
62
91
|
*/
|
|
63
92
|
async login(): Promise<void> {
|
|
64
|
-
await this.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
execSync(`agent-browser ${profileFlag} open "${this.homeUrl}"`, { stdio: "inherit", timeout: 30000 });
|
|
93
|
+
await pickChromeProfile(this.platform);
|
|
94
|
+
await this.launch();
|
|
95
|
+
this.cmd(`open "${this.homeUrl}"`, 30000);
|
|
68
96
|
|
|
69
97
|
console.log(`\n[${this.platform}] Tarayıcı açıldı: ${this.homeUrl}`);
|
|
70
98
|
const { createInterface } = await import("node:readline");
|
|
71
99
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
72
|
-
await new Promise<void>(
|
|
100
|
+
await new Promise<void>(resolve => {
|
|
73
101
|
rl.question("Hesabın açıksa Enter'a bas: ", () => { rl.close(); resolve(); });
|
|
74
102
|
});
|
|
75
103
|
console.log(`[${this.platform}] Kayıt tamamlandı.`);
|
|
@@ -77,46 +105,36 @@ export class BrowserDriver {
|
|
|
77
105
|
}
|
|
78
106
|
|
|
79
107
|
/**
|
|
80
|
-
*
|
|
108
|
+
* Learn - ARIA snapshot ile map oluştur
|
|
81
109
|
*/
|
|
82
110
|
async learn(taskDescription = "yeni bir metin postu at", actionName = "post"): Promise<SelectorMap> {
|
|
83
|
-
await this.
|
|
84
|
-
|
|
85
|
-
// Sayfaya git
|
|
111
|
+
await this.launch();
|
|
86
112
|
this.cmd(`open "${this.homeUrl}"`, 30000);
|
|
87
113
|
this.cmd("wait 3000");
|
|
88
114
|
|
|
89
|
-
// Screenshot al
|
|
90
115
|
const screenshotPath = join(PATHS.screenshots, `${this.platform}_learn_${actionName}.png`);
|
|
91
116
|
this.cmd(`screenshot "${screenshotPath}"`);
|
|
92
117
|
|
|
93
|
-
//
|
|
94
|
-
const snapshot = this.cmd("snapshot", 10000);
|
|
118
|
+
// Compact interactive snapshot
|
|
119
|
+
const snapshot = this.cmd("snapshot -i -c", 10000);
|
|
95
120
|
|
|
96
121
|
console.log(`[${this.platform}] Screenshot: ${screenshotPath}`);
|
|
97
122
|
console.log(`[${this.platform}] Snapshot: ${snapshot.length} bytes`);
|
|
98
123
|
console.log(`[${this.platform}] Claude analiz ediyor...`);
|
|
99
124
|
|
|
100
125
|
const map = await generateMap(this.platform, screenshotPath, snapshot, taskDescription, actionName);
|
|
101
|
-
|
|
102
126
|
console.log(`[${this.platform}] "${actionName}" map oluşturuldu (${map.steps.length} adım)`);
|
|
103
127
|
process.exit(0);
|
|
104
128
|
return map;
|
|
105
129
|
}
|
|
106
130
|
|
|
107
|
-
/**
|
|
108
|
-
* Sayfaya git
|
|
109
|
-
*/
|
|
110
131
|
navigate(url: string): void {
|
|
111
132
|
this.cmd(`open "${url}"`, 30000);
|
|
112
133
|
this.cmd("wait 2000");
|
|
113
134
|
}
|
|
114
135
|
|
|
115
|
-
/**
|
|
116
|
-
* Tek bir step çalıştır
|
|
117
|
-
*/
|
|
118
136
|
executeStep(step: any, content: string, imagePath?: string): void {
|
|
119
|
-
const
|
|
137
|
+
const val = (step.value || "").replace("{{CONTENT}}", content).replace("{{IMAGE}}", imagePath || "");
|
|
120
138
|
|
|
121
139
|
switch (step.action) {
|
|
122
140
|
case "goto":
|
|
@@ -129,37 +147,25 @@ export class BrowserDriver {
|
|
|
129
147
|
|
|
130
148
|
case "click": {
|
|
131
149
|
const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
|
|
132
|
-
let clicked = false;
|
|
133
150
|
for (const sel of sels) {
|
|
134
151
|
if (sel.includes("{{")) continue;
|
|
135
|
-
try { this.cmd(`click "${sel}"`);
|
|
152
|
+
try { this.cmd(`click "${sel}"`); if (step.waitMs) this.cmd(`wait ${step.waitMs}`); return; } catch {}
|
|
136
153
|
}
|
|
137
|
-
|
|
138
|
-
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
139
|
-
break;
|
|
154
|
+
throw new Error(`Click: ${sels[0]}`);
|
|
140
155
|
}
|
|
141
156
|
|
|
142
157
|
case "type": {
|
|
143
158
|
const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
|
|
144
|
-
|
|
159
|
+
const escaped = val.replace(/"/g, '\\"');
|
|
145
160
|
for (const sel of sels) {
|
|
146
161
|
if (sel.includes("{{")) continue;
|
|
147
162
|
try {
|
|
148
|
-
this.cmd(`
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
}
|
|
163
|
+
this.cmd(`type "${sel}" "${escaped}"`, 30000);
|
|
164
|
+
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
165
|
+
return;
|
|
166
|
+
} catch {}
|
|
159
167
|
}
|
|
160
|
-
|
|
161
|
-
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
162
|
-
break;
|
|
168
|
+
throw new Error(`Type: ${sels[0]}`);
|
|
163
169
|
}
|
|
164
170
|
|
|
165
171
|
case "keypress":
|
|
@@ -168,45 +174,22 @@ export class BrowserDriver {
|
|
|
168
174
|
break;
|
|
169
175
|
|
|
170
176
|
case "upload": {
|
|
171
|
-
const filePath =
|
|
177
|
+
const filePath = val || imagePath || "";
|
|
172
178
|
if (!filePath || filePath.includes("{{")) break;
|
|
173
|
-
|
|
174
179
|
const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
|
|
175
|
-
let uploaded = false;
|
|
176
180
|
for (const sel of sels) {
|
|
177
|
-
try {
|
|
178
|
-
this.cmd(`upload "${sel}" "${filePath}"`);
|
|
179
|
-
uploaded = true;
|
|
180
|
-
break;
|
|
181
|
-
} catch {}
|
|
181
|
+
try { this.cmd(`upload "${sel}" "${filePath}"`); if (step.waitMs) this.cmd(`wait ${step.waitMs}`); return; } catch {}
|
|
182
182
|
}
|
|
183
|
-
|
|
184
|
-
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
185
|
-
break;
|
|
183
|
+
throw new Error(`Upload: ${filePath}`);
|
|
186
184
|
}
|
|
187
185
|
}
|
|
188
186
|
}
|
|
189
187
|
|
|
190
|
-
/**
|
|
191
|
-
* Snapshot al (heal için)
|
|
192
|
-
*/
|
|
193
188
|
getSnapshot(): string {
|
|
194
|
-
try { return this.cmd("snapshot", 10000); } catch { return ""; }
|
|
189
|
+
try { return this.cmd("snapshot -i -c", 10000); } catch { return ""; }
|
|
195
190
|
}
|
|
196
191
|
|
|
197
|
-
/**
|
|
198
|
-
* Screenshot al
|
|
199
|
-
*/
|
|
200
192
|
takeScreenshot(path: string): void {
|
|
201
193
|
try { this.cmd(`screenshot "${path}"`); } catch {}
|
|
202
194
|
}
|
|
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
195
|
}
|