social-agent-cli 3.2.0 → 4.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/mapper.ts +8 -340
- package/ai/planner.ts +1 -1
- package/ai/runner.ts +66 -74
- package/cli.ts +3 -4
- package/package.json +1 -1
- package/platforms/browser/driver.ts +10 -11
package/ai/mapper.ts
CHANGED
|
@@ -1,173 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync, mkdtempSync, rmSync, readdirSync } from "node:fs";
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync } from "node:fs";
|
|
3
2
|
import { join } from "node:path";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
3
|
import { PATHS } from "../core/config.js";
|
|
6
4
|
import type { SelectorMap } from "../core/types.js";
|
|
7
5
|
|
|
8
|
-
// ── JSON Schemas ─────────────────────────────────────────────
|
|
9
|
-
|
|
10
|
-
const SELECTOR_MAP_SCHEMA = JSON.stringify({
|
|
11
|
-
type: "object",
|
|
12
|
-
properties: {
|
|
13
|
-
platform: { type: "string" },
|
|
14
|
-
action: { type: "string" },
|
|
15
|
-
description: { type: "string" },
|
|
16
|
-
version: { type: "number" },
|
|
17
|
-
lastUpdated: { type: "string" },
|
|
18
|
-
parameters: { type: "array", items: { type: "string" } },
|
|
19
|
-
steps: {
|
|
20
|
-
type: "array",
|
|
21
|
-
items: {
|
|
22
|
-
type: "object",
|
|
23
|
-
properties: {
|
|
24
|
-
action: { type: "string", enum: ["click", "type", "wait", "keypress", "goto", "upload"] },
|
|
25
|
-
description: { type: "string" },
|
|
26
|
-
selector: { type: "string" },
|
|
27
|
-
value: { type: "string" },
|
|
28
|
-
key: { type: "string" },
|
|
29
|
-
url: { type: "string" },
|
|
30
|
-
waitMs: { type: "number" },
|
|
31
|
-
fallbackSelectors: { type: "array", items: { type: "string" } },
|
|
32
|
-
},
|
|
33
|
-
required: ["action", "description"],
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
required: ["platform", "version", "steps"],
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const HEAL_SCHEMA = JSON.stringify({
|
|
41
|
-
type: "object",
|
|
42
|
-
properties: {
|
|
43
|
-
selector: { type: "string" },
|
|
44
|
-
fallbackSelectors: { type: "array", items: { type: "string" } },
|
|
45
|
-
explanation: { type: "string" },
|
|
46
|
-
},
|
|
47
|
-
required: ["selector", "fallbackSelectors", "explanation"],
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// ── Claude -p Wrapper ────────────────────────────────────────
|
|
51
|
-
|
|
52
|
-
interface ClaudeOptions {
|
|
53
|
-
prompt: string;
|
|
54
|
-
jsonSchema?: string;
|
|
55
|
-
imagePath?: string;
|
|
56
|
-
model?: string;
|
|
57
|
-
effort?: string;
|
|
58
|
-
stream?: boolean;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function claude(opts: ClaudeOptions): Promise<any> {
|
|
62
|
-
const { prompt, jsonSchema, imagePath, model = "opus", effort = "low", stream = true } = opts;
|
|
63
|
-
|
|
64
|
-
const tmpDir = mkdtempSync(join(tmpdir(), "social-agent-"));
|
|
65
|
-
const promptFile = join(tmpDir, "prompt.txt");
|
|
66
|
-
writeFileSync(promptFile, prompt);
|
|
67
|
-
|
|
68
|
-
return new Promise((resolve, reject) => {
|
|
69
|
-
let cmd: string;
|
|
70
|
-
const base = `claude -p --model ${model} --effort ${effort} --max-turns 3 --tools ""`;
|
|
71
|
-
|
|
72
|
-
// Screenshot varsa prompt'a dosya yolunu ekle, Claude Code dosyayı okuyabilir
|
|
73
|
-
if (imagePath && existsSync(imagePath)) {
|
|
74
|
-
writeFileSync(promptFile, `Screenshot: ${imagePath}\n\n${prompt}`);
|
|
75
|
-
}
|
|
76
|
-
cmd = `cat '${promptFile}' | ${base}`;
|
|
77
|
-
if (jsonSchema) cmd += ` --output-format json --json-schema '${jsonSchema}'`;
|
|
78
|
-
|
|
79
|
-
const proc = spawn("bash", ["-c", cmd], {
|
|
80
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
let stdout = "";
|
|
84
|
-
let stderr = "";
|
|
85
|
-
|
|
86
|
-
proc.stdout.on("data", (chunk: Buffer) => {
|
|
87
|
-
const text = chunk.toString();
|
|
88
|
-
stdout += text;
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
proc.stderr.on("data", (chunk: Buffer) => {
|
|
92
|
-
stderr += chunk.toString();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
proc.on("close", (code) => {
|
|
96
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
97
|
-
if (stream) process.stdout.write("\n");
|
|
98
|
-
|
|
99
|
-
const output = stdout.trim();
|
|
100
|
-
|
|
101
|
-
if (jsonSchema) {
|
|
102
|
-
try {
|
|
103
|
-
const parsed = JSON.parse(output);
|
|
104
|
-
|
|
105
|
-
// structured_output alanı varsa ve dolu ise
|
|
106
|
-
if (parsed.structured_output && parsed.structured_output.steps) {
|
|
107
|
-
resolve(parsed.structured_output);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// result alanında JSON string olabilir
|
|
112
|
-
if (parsed.result && typeof parsed.result === "string" && parsed.result.length > 2) {
|
|
113
|
-
try { resolve(JSON.parse(parsed.result)); return; } catch {}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// error_max_turns veya boş result durumunda - result'tan text çıkar
|
|
117
|
-
if (parsed.subtype === "error_max_turns" || !parsed.structured_output) {
|
|
118
|
-
// Claude tool kullanıp bitirmeden durmuş, sonucu elle çıkarmayı dene
|
|
119
|
-
reject(new Error(`Claude tamamlayamadı (${parsed.subtype || "unknown"}). max-turns artırılabilir.`));
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Mesaj dizisi formatı
|
|
124
|
-
if (Array.isArray(parsed)) {
|
|
125
|
-
const last = parsed[parsed.length - 1];
|
|
126
|
-
const text = typeof last.content === "string"
|
|
127
|
-
? last.content
|
|
128
|
-
: last.content?.map((c: any) => c.text || "").join("") || "";
|
|
129
|
-
resolve(JSON.parse(text));
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Direkt obje
|
|
134
|
-
if (parsed.steps || parsed.selector) {
|
|
135
|
-
resolve(parsed);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
resolve(parsed);
|
|
140
|
-
} catch (parseErr: any) {
|
|
141
|
-
// Regex fallback
|
|
142
|
-
const match = output.match(/\{[\s\S]*\}/);
|
|
143
|
-
if (match) {
|
|
144
|
-
try { resolve(JSON.parse(match[0])); return; } catch {}
|
|
145
|
-
}
|
|
146
|
-
reject(new Error("Claude'dan geçerli JSON alınamadı"));
|
|
147
|
-
}
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (code !== 0 && !output) {
|
|
152
|
-
reject(new Error(`Claude hata (code ${code}): ${stderr}`));
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
resolve(output);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
proc.on("error", (err) => {
|
|
160
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
161
|
-
reject(err);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
setTimeout(() => {
|
|
165
|
-
proc.kill();
|
|
166
|
-
reject(new Error("Claude timeout (180s)"));
|
|
167
|
-
}, 180000);
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
6
|
// ── Map CRUD (platform başına tek dosya) ─────────────────────
|
|
172
7
|
|
|
173
8
|
interface PlatformMaps {
|
|
@@ -207,182 +42,15 @@ export function listMaps(platform?: string): SelectorMap[] {
|
|
|
207
42
|
for (const file of readdirSync(PATHS.maps)) {
|
|
208
43
|
if (!file.endsWith(".json")) continue;
|
|
209
44
|
if (platform && file !== `${platform}.json`) continue;
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
results.push(
|
|
45
|
+
const content = JSON.parse(readFileSync(join(PATHS.maps, file), "utf-8"));
|
|
46
|
+
if (content.steps) {
|
|
47
|
+
results.push(content);
|
|
48
|
+
} else {
|
|
49
|
+
for (const map of Object.values(content) as SelectorMap[]) {
|
|
50
|
+
if (map.steps) results.push(map);
|
|
51
|
+
}
|
|
213
52
|
}
|
|
214
53
|
}
|
|
215
54
|
} catch {}
|
|
216
55
|
return results;
|
|
217
56
|
}
|
|
218
|
-
|
|
219
|
-
// ── Map Oluştur ──────────────────────────────────────────────
|
|
220
|
-
|
|
221
|
-
export async function generateMap(
|
|
222
|
-
platform: string,
|
|
223
|
-
screenshotPath: string,
|
|
224
|
-
domSnapshot: string,
|
|
225
|
-
taskDescription = "yeni bir metin postu at",
|
|
226
|
-
actionName = "post"
|
|
227
|
-
): Promise<SelectorMap> {
|
|
228
|
-
const prompt = `Sen bir browser otomasyon uzmanısın. ${platform} platformunda şu görevi yerine getirmek için adımları belirle:
|
|
229
|
-
|
|
230
|
-
GÖREV: "${taskDescription}"
|
|
231
|
-
|
|
232
|
-
Sayfanın Accessibility Tree'si (ARIA Snapshot):
|
|
233
|
-
${domSnapshot}
|
|
234
|
-
|
|
235
|
-
Kurallar:
|
|
236
|
-
- ARIA snapshot'taki role ve name bilgilerini kullanarak selector oluştur
|
|
237
|
-
- aria-label, role, data-testid, id tercih et
|
|
238
|
-
- Dosya/görsel/video yükleme için "upload" action kullan. Upload action şöyle çalışır:
|
|
239
|
-
- selector'da medya/fotoğraf ekleme BUTONUNU ver (ör: button[aria-label*='Fotoğraf'], button[aria-label*='Add media'])
|
|
240
|
-
- Sistem butona tıklayıp fileChooser'ı yakalayacak ve dosyayı programatik olarak set edecek, file picker açılmayacak
|
|
241
|
-
- Eğer selector verilmezse sayfadaki input[type=file]'a direkt set eder
|
|
242
|
-
- value alanına {{IMAGE}} yaz
|
|
243
|
-
- Her adım için 2+ fallback selector
|
|
244
|
-
- Parametreler için placeholder kullan: {{CONTENT}} = metin, {{USERNAME}} = kullanıcı adı, {{URL}} = link, vs.
|
|
245
|
-
- Hangi parametreleri kullandığını "parameters" alanında listele
|
|
246
|
-
- action: "${actionName}"
|
|
247
|
-
- platform: "${platform}", version: 1, lastUpdated: "${new Date().toISOString()}"
|
|
248
|
-
- Gerekiyorsa "goto" action'ı ile belirli bir URL'e git (url alanını kullan)`;
|
|
249
|
-
|
|
250
|
-
console.log(`[claude] "${taskDescription}" analiz ediliyor...\n`);
|
|
251
|
-
|
|
252
|
-
let map: SelectorMap;
|
|
253
|
-
try {
|
|
254
|
-
map = await claude({
|
|
255
|
-
prompt,
|
|
256
|
-
jsonSchema: SELECTOR_MAP_SCHEMA,
|
|
257
|
-
imagePath: screenshotPath,
|
|
258
|
-
stream: false,
|
|
259
|
-
effort: "low",
|
|
260
|
-
});
|
|
261
|
-
} catch (err: any) {
|
|
262
|
-
console.error(`\n[claude] Hata: ${err.message}`);
|
|
263
|
-
throw err;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (!map || !map.steps) {
|
|
267
|
-
throw new Error("Claude geçerli bir map döndürmedi");
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
map.platform = platform;
|
|
271
|
-
map.action = actionName;
|
|
272
|
-
map.description = taskDescription;
|
|
273
|
-
map.lastUpdated = new Date().toISOString();
|
|
274
|
-
saveMap(map);
|
|
275
|
-
return map;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// ── Self-Heal: Kaldığın yerden devam et ──────────────────────
|
|
279
|
-
|
|
280
|
-
const REMAINING_STEPS_SCHEMA = JSON.stringify({
|
|
281
|
-
type: "object",
|
|
282
|
-
properties: {
|
|
283
|
-
analysis: { type: "string" },
|
|
284
|
-
remainingSteps: {
|
|
285
|
-
type: "array",
|
|
286
|
-
items: {
|
|
287
|
-
type: "object",
|
|
288
|
-
properties: {
|
|
289
|
-
action: { type: "string", enum: ["click", "type", "wait", "keypress", "goto", "upload"] },
|
|
290
|
-
description: { type: "string" },
|
|
291
|
-
selector: { type: "string" },
|
|
292
|
-
value: { type: "string" },
|
|
293
|
-
key: { type: "string" },
|
|
294
|
-
url: { type: "string" },
|
|
295
|
-
waitMs: { type: "number" },
|
|
296
|
-
fallbackSelectors: { type: "array", items: { type: "string" } },
|
|
297
|
-
},
|
|
298
|
-
required: ["action", "description"],
|
|
299
|
-
},
|
|
300
|
-
},
|
|
301
|
-
},
|
|
302
|
-
required: ["analysis", "remainingSteps"],
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Bir adım başarısız olduğunda AI ekranı görür,
|
|
307
|
-
* durumu analiz eder ve kalan adımları yeniden planlar.
|
|
308
|
-
* Sadece selector düzeltmez - tamamen yeni akış çizebilir.
|
|
309
|
-
*/
|
|
310
|
-
export async function healAndContinue(
|
|
311
|
-
platform: string,
|
|
312
|
-
failedStepIndex: number,
|
|
313
|
-
originalSteps: SelectorStep[],
|
|
314
|
-
screenshotPath: string,
|
|
315
|
-
domSnapshot: string,
|
|
316
|
-
errorMessage: string,
|
|
317
|
-
postContent: string,
|
|
318
|
-
effort = "low"
|
|
319
|
-
): Promise<SelectorStep[]> {
|
|
320
|
-
const completedSteps = originalSteps.slice(0, failedStepIndex);
|
|
321
|
-
const failedStep = originalSteps[failedStepIndex];
|
|
322
|
-
const remainingOriginal = originalSteps.slice(failedStepIndex);
|
|
323
|
-
|
|
324
|
-
const prompt = `Bir browser otomasyon script'i çalışırken hata oluştu. Ekranın screenshot'ını ve DOM'unu görüyorsun.
|
|
325
|
-
|
|
326
|
-
Platform: ${platform}
|
|
327
|
-
Amaç: "${postContent}" metnini post olarak paylaşmak
|
|
328
|
-
|
|
329
|
-
Tamamlanan adımlar:
|
|
330
|
-
${completedSteps.map((s, i) => ` ${i + 1}. [${s.action}] ${s.description} ✓`).join("\n")}
|
|
331
|
-
|
|
332
|
-
Başarısız olan adım (#${failedStepIndex + 1}):
|
|
333
|
-
[${failedStep.action}] ${failedStep.description}
|
|
334
|
-
Selector: ${failedStep.selector}
|
|
335
|
-
Hata: ${errorMessage}
|
|
336
|
-
|
|
337
|
-
Kalan orijinal adımlar:
|
|
338
|
-
${remainingOriginal.map((s, i) => ` ${failedStepIndex + i + 1}. [${s.action}] ${s.description} → ${s.selector}`).join("\n")}
|
|
339
|
-
|
|
340
|
-
Sayfanın Accessibility Tree'si:
|
|
341
|
-
${domSnapshot}
|
|
342
|
-
|
|
343
|
-
GÖREV: Ekranın mevcut durumunu analiz et. Amaca ulaşmak için kalan adımları yeniden planla.
|
|
344
|
-
- Belki selector değişmiş → yeni selector bul
|
|
345
|
-
- Belki ekstra bir adım gerekiyor (popup kapatma, scroll, vb.)
|
|
346
|
-
- Belki farklı bir yol izlenmeli
|
|
347
|
-
- {{CONTENT}} = post metni placeholder'ı
|
|
348
|
-
- data-testid, aria-label, role tercih et, her adıma 2+ fallback selector ver
|
|
349
|
-
- Dosya yükleme için "upload" action kullan. selector'a medya ekleme butonunu ver, value: {{IMAGE}}`;
|
|
350
|
-
|
|
351
|
-
console.log(`\n[heal] AI ekranı analiz ediyor (effort: ${effort})...\n`);
|
|
352
|
-
|
|
353
|
-
let result: any;
|
|
354
|
-
try {
|
|
355
|
-
result = await claude({
|
|
356
|
-
prompt,
|
|
357
|
-
jsonSchema: REMAINING_STEPS_SCHEMA,
|
|
358
|
-
imagePath: screenshotPath,
|
|
359
|
-
stream: false,
|
|
360
|
-
effort,
|
|
361
|
-
});
|
|
362
|
-
} catch (err: any) {
|
|
363
|
-
console.log(`\n[heal] AI hatası: ${err.message}`);
|
|
364
|
-
return originalSteps.slice(failedStepIndex + 1); // başarısız step'i atla
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// result çeşitli formatlarda gelebilir
|
|
368
|
-
if (!result) {
|
|
369
|
-
return originalSteps.slice(failedStepIndex + 1);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// structured_output içinde olabilir
|
|
373
|
-
if (result.structured_output?.remainingSteps) {
|
|
374
|
-
result = result.structured_output;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// remainingSteps yoksa steps dene
|
|
378
|
-
const steps = result.remainingSteps || result.steps || result;
|
|
379
|
-
|
|
380
|
-
if (!Array.isArray(steps)) {
|
|
381
|
-
console.log(`\n[heal] Plan parse edilemedi, başarısız adım atlanıyor.`);
|
|
382
|
-
return originalSteps.slice(failedStepIndex + 1);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
console.log(`\n[heal] ${steps.length} yeni adım`);
|
|
386
|
-
|
|
387
|
-
return steps;
|
|
388
|
-
}
|
package/ai/planner.ts
CHANGED
|
@@ -151,7 +151,7 @@ GÖREV: Bu komutu yerine getirmek için bir çalıştırma planı oluştur.
|
|
|
151
151
|
Kurallar:
|
|
152
152
|
- Mevcut map varsa "use_map" kullan, mapAction'a action adını yaz
|
|
153
153
|
- Mevcut map yoksa "learn_new" kullan, AI otomatik öğrenecek
|
|
154
|
-
-
|
|
154
|
+
- Komutta fotoğraf/görsel varsa mevcut "post" map'ini kullan, IMAGE parametresini ekle. Sistem upload'ı otomatik handle eder - ayrı map öğretmeye gerek YOK
|
|
155
155
|
- Parametreleri doldur ({{TWEET_URL}}, {{USERNAME}}, {{CONTENT}}, {{IMAGE}} vb.)
|
|
156
156
|
- Belirli kişi/URL verilmemişse platformun keşif/öneri sayfalarını kullan
|
|
157
157
|
- Tekrar eden işlemler için "repeat" kullan
|
package/ai/runner.ts
CHANGED
|
@@ -1,101 +1,93 @@
|
|
|
1
1
|
import { BrowserDriver } from "../platforms/browser/driver.js";
|
|
2
|
-
import { loadMap
|
|
3
|
-
import { createPlan } from "./planner.js";
|
|
2
|
+
import { loadMap } from "./mapper.js";
|
|
4
3
|
import { saveToHistory } from "../core/history.js";
|
|
5
4
|
import type { Post } from "../core/types.js";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import { PATHS } from "../core/config.js";
|
|
8
5
|
|
|
9
6
|
const BROWSER_PLATFORMS: Record<string, string> = {
|
|
10
7
|
x: "https://x.com",
|
|
11
8
|
linkedin: "https://www.linkedin.com",
|
|
12
9
|
};
|
|
13
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Post at - map'i direkt çalıştır, AI çağırmadan
|
|
13
|
+
*/
|
|
14
14
|
export async function runCommand(platform: string, command: string): Promise<void> {
|
|
15
15
|
const url = BROWSER_PLATFORMS[platform];
|
|
16
|
-
if (!url) { console.log(`"${platform}" desteklenmiyor.`);
|
|
16
|
+
if (!url) { console.log(`"${platform}" desteklenmiyor.`); process.exit(1); }
|
|
17
|
+
|
|
18
|
+
// Komuttan CONTENT ve IMAGE çıkar
|
|
19
|
+
const imageMatch = command.match(/(?:fotoğraf|foto|image|görsel)[:\s]*([^\s,]+)/i);
|
|
20
|
+
const image = imageMatch?.[1]?.trim() || "";
|
|
21
|
+
|
|
22
|
+
// Image referansını ve prefix'leri temizle
|
|
23
|
+
let content = command
|
|
24
|
+
.replace(/,?\s*(?:fotoğraf|foto|image|görsel)[:\s]*[^\s,]+/i, "")
|
|
25
|
+
.replace(/^şu mesajı (?:fotoğraf ile )?(?:tweetle|paylaş|gönder)[:\s]*/i, "")
|
|
26
|
+
.replace(/^(?:tweetle|paylaş|post at|mesaj)[:\s]*/i, "")
|
|
27
|
+
.trim();
|
|
28
|
+
|
|
29
|
+
// Map seç
|
|
30
|
+
const map = loadMap(platform, "post");
|
|
31
|
+
if (!map) {
|
|
32
|
+
console.log(`[${platform}] post map'i bulunamadı. "social-agent learn ${platform}" çalıştır.`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
17
35
|
|
|
18
|
-
|
|
19
|
-
console.log(
|
|
36
|
+
console.log(`[${platform}] Post: "${content.substring(0, 50)}${content.length > 50 ? "..." : ""}"`);
|
|
37
|
+
if (image) console.log(`[${platform}] Görsel: ${image}`);
|
|
20
38
|
|
|
21
|
-
|
|
22
|
-
|
|
39
|
+
const driver = new BrowserDriver(platform, url);
|
|
40
|
+
await driver.launch();
|
|
23
41
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
42
|
+
try {
|
|
43
|
+
// Ana sayfaya git
|
|
44
|
+
if (map.steps[0]?.action !== "goto") {
|
|
45
|
+
driver.navigate(url);
|
|
28
46
|
}
|
|
29
47
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
let steps = [...map.steps];
|
|
49
|
+
|
|
50
|
+
// Image varsa: gönder butonundan önce upload step ekle
|
|
51
|
+
if (image && !steps.some(s => s.action === "upload")) {
|
|
52
|
+
const sendIdx = steps.findLastIndex(s => s.action === "click");
|
|
53
|
+
if (sendIdx > -1) {
|
|
54
|
+
steps.splice(sendIdx, 0, {
|
|
55
|
+
action: "upload" as any,
|
|
56
|
+
description: "Fotoğraf yükle",
|
|
57
|
+
selector: "input[type='file']",
|
|
58
|
+
value: "{{IMAGE}}",
|
|
59
|
+
waitMs: 3000,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
45
63
|
|
|
64
|
+
// Step'leri çalıştır
|
|
65
|
+
const errors: string[] = [];
|
|
66
|
+
for (let idx = 0; idx < steps.length; idx++) {
|
|
67
|
+
const s = steps[idx];
|
|
46
68
|
try {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
driver.navigate(url);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
let steps = [...map.steps];
|
|
53
|
-
let healCount = 0;
|
|
54
|
-
|
|
55
|
-
for (let idx = 0; idx < steps.length; idx++) {
|
|
56
|
-
const s = steps[idx];
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
console.log(` ${idx + 1}. ${s.description}`);
|
|
60
|
-
driver.executeStep(s, content, image);
|
|
61
|
-
} catch (err: any) {
|
|
62
|
-
if (healCount >= 2) {
|
|
63
|
-
console.log(` ✗ ${err.message} (atlanıyor)`);
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
healCount++;
|
|
67
|
-
console.log(` ✗ ${err.message}`);
|
|
68
|
-
console.log(` → Düzeltiliyor...`);
|
|
69
|
-
|
|
70
|
-
const ss = join(PATHS.screenshots, `${platform}_heal.png`);
|
|
71
|
-
driver.takeScreenshot(ss);
|
|
72
|
-
const snap = driver.getSnapshot();
|
|
73
|
-
|
|
74
|
-
const fixed = await healAndContinue(
|
|
75
|
-
platform, idx, steps, ss, snap,
|
|
76
|
-
err.message, content || step.description, "medium"
|
|
77
|
-
);
|
|
78
|
-
steps = [...steps.slice(0, idx), ...fixed];
|
|
79
|
-
idx--;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
console.log(` ✓ Tamamlandı`);
|
|
84
|
-
saveToHistory({ platform, content: content || step.description, action: mapAction.includes("post") ? "post" : mapAction, success: true });
|
|
69
|
+
console.log(` ${idx + 1}. ${s.description}`);
|
|
70
|
+
driver.executeStep(s, content, image);
|
|
85
71
|
} catch (err: any) {
|
|
72
|
+
errors.push(`Step ${idx + 1} (${s.description}): ${err.message}`);
|
|
86
73
|
console.log(` ✗ ${err.message}`);
|
|
87
|
-
saveToHistory({ platform, content: content || step.description, action: mapAction, success: false, error: err.message });
|
|
88
|
-
} finally {
|
|
89
|
-
await driver.close();
|
|
90
74
|
}
|
|
75
|
+
}
|
|
91
76
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
77
|
+
if (errors.length === 0) {
|
|
78
|
+
console.log(`\n✓ ${platform} post gönderildi`);
|
|
79
|
+
saveToHistory({ platform, content, action: "post", success: true, image: image || undefined });
|
|
80
|
+
} else {
|
|
81
|
+
console.log(`\n⚠ ${errors.length} hata oluştu:`);
|
|
82
|
+
errors.forEach(e => console.log(` - ${e}`));
|
|
83
|
+
saveToHistory({ platform, content, action: "post", success: false, error: errors.join("; ") });
|
|
95
84
|
}
|
|
96
|
-
|
|
85
|
+
} catch (err: any) {
|
|
86
|
+
console.log(`\n✗ ${err.message}`);
|
|
87
|
+
saveToHistory({ platform, content, action: "post", success: false, error: err.message });
|
|
88
|
+
} finally {
|
|
89
|
+
await driver.close();
|
|
97
90
|
}
|
|
98
91
|
|
|
99
|
-
console.log("[runner] Tamamlandı.");
|
|
100
92
|
process.exit(0);
|
|
101
93
|
}
|
package/cli.ts
CHANGED
|
@@ -189,17 +189,16 @@ switch (command) {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
case "run": {
|
|
192
|
-
const
|
|
193
|
-
const filteredArgs = args.filter(a => a !== "--yes" && a !== "-y");
|
|
192
|
+
const filteredArgs = args.filter(a => !a.startsWith("--"));
|
|
194
193
|
const runPlatform = BROWSER_PLATFORMS[filteredArgs[0]] ? filteredArgs[0] : "x";
|
|
195
194
|
const runCmd = BROWSER_PLATFORMS[filteredArgs[0]]
|
|
196
195
|
? filteredArgs.slice(1).join(" ")
|
|
197
196
|
: filteredArgs.join(" ");
|
|
198
197
|
if (!runCmd) {
|
|
199
|
-
console.log('Kullanım: social-agent run <platform> "
|
|
198
|
+
console.log('Kullanım: social-agent run <platform> "komut"');
|
|
200
199
|
break;
|
|
201
200
|
}
|
|
202
|
-
await runCommand(runPlatform, runCmd
|
|
201
|
+
await runCommand(runPlatform, runCmd);
|
|
203
202
|
break;
|
|
204
203
|
}
|
|
205
204
|
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { existsSync, mkdirSync, copyFileSync } from "node:fs";
|
|
5
5
|
import { PATHS } from "../../core/config.js";
|
|
6
|
-
import { loadMap
|
|
6
|
+
import { loadMap } from "../../ai/mapper.js";
|
|
7
7
|
import { pickChromeProfile, getSavedProfile } from "../../core/profiles.js";
|
|
8
8
|
import type { SelectorMap } from "../../core/types.js";
|
|
9
9
|
|
|
@@ -105,27 +105,26 @@ export class BrowserDriver {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
|
-
* Learn - ARIA snapshot
|
|
108
|
+
* Learn - ARIA snapshot al ve dosyaya yaz (Claude Code okuyup map oluşturur)
|
|
109
109
|
*/
|
|
110
110
|
async learn(taskDescription = "yeni bir metin postu at", actionName = "post"): Promise<SelectorMap> {
|
|
111
111
|
await this.launch();
|
|
112
112
|
this.cmd(`open "${this.homeUrl}"`, 30000);
|
|
113
113
|
this.cmd("wait 3000");
|
|
114
114
|
|
|
115
|
-
const screenshotPath = join(PATHS.screenshots, `${this.platform}
|
|
115
|
+
const screenshotPath = join(PATHS.screenshots, `${this.platform}_learn.png`);
|
|
116
116
|
this.cmd(`screenshot "${screenshotPath}"`);
|
|
117
117
|
|
|
118
|
-
// Compact interactive snapshot
|
|
119
118
|
const snapshot = this.cmd("snapshot -i -c", 10000);
|
|
119
|
+
const snapshotPath = join(PATHS.screenshots, `${this.platform}_snapshot.txt`);
|
|
120
|
+
const { writeFileSync: writeFile } = await import("node:fs");
|
|
121
|
+
writeFile(snapshotPath, snapshot);
|
|
120
122
|
|
|
121
|
-
console.log(`
|
|
122
|
-
console.log(`
|
|
123
|
-
console.log(
|
|
124
|
-
|
|
125
|
-
const map = await generateMap(this.platform, screenshotPath, snapshot, taskDescription, actionName);
|
|
126
|
-
console.log(`[${this.platform}] "${actionName}" map oluşturuldu (${map.steps.length} adım)`);
|
|
123
|
+
console.log(`Screenshot: ${screenshotPath}`);
|
|
124
|
+
console.log(`Snapshot: ${snapshotPath}`);
|
|
125
|
+
console.log(`\nBu dosyaları Claude Code'a ver, map oluştursun.`);
|
|
127
126
|
process.exit(0);
|
|
128
|
-
return
|
|
127
|
+
return {} as SelectorMap;
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
navigate(url: string): void {
|