social-agent-cli 1.0.0 → 1.0.2
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/knowledge.ts +47 -0
- package/ai/mapper.ts +348 -0
- package/ai/planner.ts +180 -0
- package/ai/runner.ts +323 -0
- package/cli.ts +233 -0
- package/core/config.ts +44 -0
- package/core/history.ts +96 -0
- package/core/profiles.ts +150 -0
- package/core/screenshot.ts +37 -0
- package/core/types.ts +39 -0
- package/install.ts +283 -0
- package/package.json +8 -5
- package/platforms/bluesky.ts +125 -0
- package/platforms/browser/driver.ts +388 -0
- package/platforms/mastodon.ts +87 -0
- package/tsconfig.json +15 -0
package/ai/runner.ts
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { BrowserDriver } from "../platforms/browser/driver.js";
|
|
2
|
+
import { loadMap } from "./mapper.js";
|
|
3
|
+
import { createPlan, type ExecutionPlan, type PlanStep } from "./planner.js";
|
|
4
|
+
import { healAndContinue } from "./mapper.js";
|
|
5
|
+
import { saveToHistory } from "../core/history.js";
|
|
6
|
+
import type { Post, PostResult } from "../core/types.js";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { PATHS } from "../core/config.js";
|
|
9
|
+
|
|
10
|
+
const BROWSER_PLATFORMS: Record<string, string> = {
|
|
11
|
+
x: "https://x.com",
|
|
12
|
+
linkedin: "https://www.linkedin.com",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Doğal dil komutunu planla ve çalıştır.
|
|
17
|
+
*/
|
|
18
|
+
export async function runCommand(
|
|
19
|
+
platform: string,
|
|
20
|
+
command: string
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
const url = BROWSER_PLATFORMS[platform];
|
|
23
|
+
if (!url) {
|
|
24
|
+
console.log(`"${platform}" browser platformu değil.`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 1. Plan oluştur
|
|
29
|
+
const plan = await createPlan(platform, command);
|
|
30
|
+
|
|
31
|
+
// 2. Onay al
|
|
32
|
+
console.log("");
|
|
33
|
+
const { createInterface } = await import("node:readline");
|
|
34
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
35
|
+
const answer = await new Promise<string>((resolve) => {
|
|
36
|
+
rl.question("Bu planı çalıştırayım mı? (E/h): ", (a) => { rl.close(); resolve(a.trim()); });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (answer && answer.toLowerCase() !== "e") {
|
|
40
|
+
console.log("İptal edildi.");
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 3. Adımları çalıştır
|
|
45
|
+
console.log("\n[runner] Plan çalıştırılıyor...\n");
|
|
46
|
+
|
|
47
|
+
for (const [i, step] of plan.steps.entries()) {
|
|
48
|
+
console.log(`━━━ Adım ${i + 1}/${plan.steps.length}: ${step.description} ━━━`);
|
|
49
|
+
|
|
50
|
+
if (step.type === "learn_new") {
|
|
51
|
+
console.log(`[runner] Yeni aksiyon öğreniliyor: "${step.learnDescription}"`);
|
|
52
|
+
const driver = new BrowserDriver(platform, url);
|
|
53
|
+
await driver.learn(
|
|
54
|
+
step.learnDescription || step.description,
|
|
55
|
+
step.learnActionName || "custom"
|
|
56
|
+
);
|
|
57
|
+
console.log(`[runner] Öğrenildi!\n`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const mapAction = step.type === "use_map"
|
|
61
|
+
? step.mapAction!
|
|
62
|
+
: step.learnActionName || "custom";
|
|
63
|
+
|
|
64
|
+
const map = loadMap(platform, mapAction);
|
|
65
|
+
if (!map) {
|
|
66
|
+
console.log(`[runner] HATA: "${mapAction}" map'i bulunamadı, atlanıyor.`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const repeatCount = step.repeat || 1;
|
|
71
|
+
|
|
72
|
+
for (let r = 0; r < repeatCount; r++) {
|
|
73
|
+
if (repeatCount > 1) console.log(` [${r + 1}/${repeatCount}]`);
|
|
74
|
+
|
|
75
|
+
const post: Post = {
|
|
76
|
+
content: step.parameters["{{CONTENT}}"] || step.parameters["CONTENT"] || "",
|
|
77
|
+
platform,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const driver = new BrowserDriver(platform, url);
|
|
81
|
+
const page = await driver.launch();
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
let steps = [...map.steps];
|
|
85
|
+
let stepIdx = 0;
|
|
86
|
+
let healAttempt = 0;
|
|
87
|
+
|
|
88
|
+
while (stepIdx < steps.length) {
|
|
89
|
+
const mapStep = steps[stepIdx];
|
|
90
|
+
|
|
91
|
+
// Placeholder'ları doldur - boş kalanları akıllıca handle et
|
|
92
|
+
const resolvedParams = resolveParams(step.parameters, r);
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
if (mapStep.action === "goto") {
|
|
96
|
+
let gotoUrl = mapStep.url || url;
|
|
97
|
+
gotoUrl = replacePlaceholders(gotoUrl, post, resolvedParams);
|
|
98
|
+
|
|
99
|
+
// Eğer hâlâ {{...}} placeholder varsa, bu adımı atla ve ana sayfaya git
|
|
100
|
+
if (gotoUrl.includes("{{")) {
|
|
101
|
+
console.log(` Step ${stepIdx + 1}: ${mapStep.description} (ana sayfa kullanılıyor)`);
|
|
102
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
103
|
+
} else {
|
|
104
|
+
console.log(` Step ${stepIdx + 1}: ${mapStep.description}`);
|
|
105
|
+
await page.goto(gotoUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
106
|
+
}
|
|
107
|
+
if (mapStep.waitMs) await page.waitForTimeout(mapStep.waitMs);
|
|
108
|
+
} else {
|
|
109
|
+
console.log(` Step ${stepIdx + 1}: ${mapStep.description}`);
|
|
110
|
+
await executeMapStep(page, mapStep, post, resolvedParams);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
stepIdx++;
|
|
114
|
+
healAttempt = 0; // başarılı, heal sayacını sıfırla
|
|
115
|
+
} catch (err: any) {
|
|
116
|
+
healAttempt++;
|
|
117
|
+
const efforts = ["low", "medium", "medium"];
|
|
118
|
+
const effort = efforts[Math.min(healAttempt - 1, efforts.length - 1)];
|
|
119
|
+
|
|
120
|
+
console.log(` ✗ Hata: ${err.message}`);
|
|
121
|
+
console.log(` [heal] AI devreye giriyor (effort: ${effort}, deneme: ${healAttempt}/3)...`);
|
|
122
|
+
|
|
123
|
+
if (healAttempt > 3) {
|
|
124
|
+
console.log(` [heal] 3 denemede çözülemedi, atlanıyor.`);
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Screenshot + DOM al
|
|
129
|
+
const healScreenshot = join(PATHS.screenshots, `${platform}_heal_${stepIdx}.png`);
|
|
130
|
+
await page.screenshot({ path: healScreenshot });
|
|
131
|
+
|
|
132
|
+
const domSnap = await page.evaluate(`(function() {
|
|
133
|
+
var SKIP = {"script":1,"style":1,"noscript":1,"link":1,"meta":1};
|
|
134
|
+
function ser(el, d, b) {
|
|
135
|
+
if (d > 15 || b.c <= 0) return "";
|
|
136
|
+
var t = el.tagName.toLowerCase();
|
|
137
|
+
if (SKIP[t]) return "";
|
|
138
|
+
if (t === "svg") return "<svg/>";
|
|
139
|
+
var st = window.getComputedStyle(el);
|
|
140
|
+
if (st.display === "none" || st.visibility === "hidden") return "";
|
|
141
|
+
var at = [];
|
|
142
|
+
for (var i = 0; i < el.attributes.length; i++) {
|
|
143
|
+
var a = el.attributes[i];
|
|
144
|
+
if (a.name === "style") continue;
|
|
145
|
+
at.push(a.name + '="' + (a.value.length > 150 ? a.value.substring(0,150) : a.value).replace(/"/g,"'") + '"');
|
|
146
|
+
}
|
|
147
|
+
var as = at.length ? " " + at.join(" ") : "";
|
|
148
|
+
var dt = "";
|
|
149
|
+
for (var j = 0; j < el.childNodes.length; j++) {
|
|
150
|
+
if (el.childNodes[j].nodeType === 3) { var tx = (el.childNodes[j].textContent||"").trim(); if (tx) dt += tx.substring(0,100); }
|
|
151
|
+
}
|
|
152
|
+
var ch = "";
|
|
153
|
+
for (var k = 0; k < el.children.length; k++) ch += ser(el.children[k], d+1, b);
|
|
154
|
+
if (!as && !dt && !ch) return "";
|
|
155
|
+
var r = "<"+t+as+">"+dt+ch+"</"+t+">";
|
|
156
|
+
b.c -= r.length;
|
|
157
|
+
return r;
|
|
158
|
+
}
|
|
159
|
+
return ser(document.body, 0, {c:500000});
|
|
160
|
+
})()`);
|
|
161
|
+
|
|
162
|
+
// AI kaldığı yerden devam etsin (her denemede effort artar)
|
|
163
|
+
const newSteps = await healAndContinue(
|
|
164
|
+
platform,
|
|
165
|
+
stepIdx,
|
|
166
|
+
steps,
|
|
167
|
+
healScreenshot,
|
|
168
|
+
domSnap as string,
|
|
169
|
+
err.message,
|
|
170
|
+
post.content || step.description,
|
|
171
|
+
effort
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
steps = [...steps.slice(0, stepIdx), ...newSteps];
|
|
175
|
+
// stepIdx artırma - yeni planı deneyecek
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log(` ✓ Tamamlandı`);
|
|
180
|
+
saveToHistory({
|
|
181
|
+
platform,
|
|
182
|
+
content: post.content || step.description,
|
|
183
|
+
action: mapAction.includes("post") ? "post" : mapAction,
|
|
184
|
+
success: true,
|
|
185
|
+
});
|
|
186
|
+
} catch (err: any) {
|
|
187
|
+
console.log(` ✗ Hata: ${err.message}`);
|
|
188
|
+
saveToHistory({
|
|
189
|
+
platform,
|
|
190
|
+
content: post.content || step.description,
|
|
191
|
+
action: mapAction.includes("post") ? "post" : mapAction,
|
|
192
|
+
success: false,
|
|
193
|
+
error: err.message,
|
|
194
|
+
});
|
|
195
|
+
} finally {
|
|
196
|
+
await driver.close();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Tekrarlar arası rastgele bekleme
|
|
200
|
+
if (r < repeatCount - 1) {
|
|
201
|
+
const delay = 2000 + Math.random() * 3000;
|
|
202
|
+
console.log(` (${(delay / 1000).toFixed(1)}s bekleniyor...)`);
|
|
203
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log("");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log("[runner] Plan tamamlandı.");
|
|
211
|
+
process.exit(0);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Parametreleri çözümle - boş olanları repeat index'e göre doldur
|
|
216
|
+
*/
|
|
217
|
+
function resolveParams(params: Record<string, string>, repeatIndex: number): Record<string, string> {
|
|
218
|
+
const resolved: Record<string, string> = {};
|
|
219
|
+
for (const [key, val] of Object.entries(params)) {
|
|
220
|
+
resolved[key] = val || "";
|
|
221
|
+
}
|
|
222
|
+
// Repeat index'i ekle (nth-child selector'ları için)
|
|
223
|
+
resolved["{{INDEX}}"] = String(repeatIndex + 1);
|
|
224
|
+
resolved["INDEX"] = String(repeatIndex + 1);
|
|
225
|
+
return resolved;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Placeholder'ları değiştir
|
|
230
|
+
*/
|
|
231
|
+
function replacePlaceholders(text: string, post: Post, params: Record<string, string>): string {
|
|
232
|
+
if (!text) return text;
|
|
233
|
+
let result = text;
|
|
234
|
+
result = result.replace("{{CONTENT}}", post.content || "");
|
|
235
|
+
for (const [key, val] of Object.entries(params)) {
|
|
236
|
+
const placeholder = key.startsWith("{{") ? key : `{{${key}}}`;
|
|
237
|
+
result = result.replace(placeholder, val);
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Tek bir map step'ini parametrelerle çalıştır
|
|
244
|
+
*/
|
|
245
|
+
async function executeMapStep(
|
|
246
|
+
page: any,
|
|
247
|
+
step: any,
|
|
248
|
+
post: Post,
|
|
249
|
+
params: Record<string, string>
|
|
250
|
+
): Promise<void> {
|
|
251
|
+
function rp(text: string): string {
|
|
252
|
+
return replacePlaceholders(text, post, params);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const selectors = [step.selector, ...(step.fallbackSelectors || [])]
|
|
256
|
+
.filter(Boolean)
|
|
257
|
+
.map((s: string) => rp(s));
|
|
258
|
+
|
|
259
|
+
switch (step.action) {
|
|
260
|
+
case "goto":
|
|
261
|
+
await page.goto(rp(step.url), { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
262
|
+
if (step.waitMs) await page.waitForTimeout(step.waitMs);
|
|
263
|
+
break;
|
|
264
|
+
|
|
265
|
+
case "wait":
|
|
266
|
+
await page.waitForTimeout(step.waitMs || 1000);
|
|
267
|
+
break;
|
|
268
|
+
|
|
269
|
+
case "click": {
|
|
270
|
+
let clicked = false;
|
|
271
|
+
|
|
272
|
+
// Önce normal selector'ları dene
|
|
273
|
+
for (const sel of selectors) {
|
|
274
|
+
// Hâlâ çözümlenmemiş placeholder varsa atla
|
|
275
|
+
if (sel.includes("{{")) continue;
|
|
276
|
+
try {
|
|
277
|
+
await page.waitForSelector(sel, { timeout: 5000 });
|
|
278
|
+
await page.click(sel);
|
|
279
|
+
clicked = true;
|
|
280
|
+
break;
|
|
281
|
+
} catch {}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Hiçbiri çalışmadıysa hata fırlat → heal mekanizması devreye girecek
|
|
285
|
+
// AI ekranı görüp doğru butonu bulacak
|
|
286
|
+
if (!clicked) throw new Error(`Click failed: ${selectors.join(", ")}`);
|
|
287
|
+
if (step.waitMs) await page.waitForTimeout(step.waitMs);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
case "type": {
|
|
292
|
+
const text = rp(step.value || "");
|
|
293
|
+
let typed = false;
|
|
294
|
+
for (const sel of selectors) {
|
|
295
|
+
if (sel.includes("{{")) continue;
|
|
296
|
+
try {
|
|
297
|
+
await page.waitForSelector(sel, { timeout: 5000 });
|
|
298
|
+
const isContentEditable = await page.$eval(sel, (el: Element) =>
|
|
299
|
+
el.getAttribute("contenteditable") === "true" ||
|
|
300
|
+
el.getAttribute("role") === "textbox"
|
|
301
|
+
).catch(() => false);
|
|
302
|
+
|
|
303
|
+
if (isContentEditable) {
|
|
304
|
+
await page.click(sel);
|
|
305
|
+
await page.keyboard.type(text, { delay: 30 });
|
|
306
|
+
} else {
|
|
307
|
+
await page.fill(sel, text);
|
|
308
|
+
}
|
|
309
|
+
typed = true;
|
|
310
|
+
break;
|
|
311
|
+
} catch {}
|
|
312
|
+
}
|
|
313
|
+
if (!typed) throw new Error(`Type failed: ${selectors.join(", ")}`);
|
|
314
|
+
if (step.waitMs) await page.waitForTimeout(step.waitMs);
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
case "keypress":
|
|
319
|
+
await page.keyboard.press(step.key || "Enter");
|
|
320
|
+
if (step.waitMs) await page.waitForTimeout(step.waitMs);
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}
|
package/cli.ts
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { MastodonDriver } from "./platforms/mastodon.js";
|
|
2
|
+
import { BlueskyDriver } from "./platforms/bluesky.js";
|
|
3
|
+
import { BrowserDriver } from "./platforms/browser/driver.js";
|
|
4
|
+
import { loadConfig } from "./core/config.js";
|
|
5
|
+
import { runCommand } from "./ai/runner.js";
|
|
6
|
+
import { printHistory } from "./core/history.js";
|
|
7
|
+
import type { Post, PostResult, PlatformDriver } from "./core/types.js";
|
|
8
|
+
|
|
9
|
+
// ── Platform registry ────────────────────────────────────────
|
|
10
|
+
const BROWSER_PLATFORMS: Record<string, string> = {
|
|
11
|
+
x: "https://x.com",
|
|
12
|
+
linkedin: "https://www.linkedin.com",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function getAllDrivers(): PlatformDriver[] {
|
|
16
|
+
const config = loadConfig();
|
|
17
|
+
const drivers: PlatformDriver[] = [];
|
|
18
|
+
|
|
19
|
+
// API-based (ücretsiz)
|
|
20
|
+
if (config.mastodon) drivers.push(new MastodonDriver());
|
|
21
|
+
if (config.bluesky) drivers.push(new BlueskyDriver());
|
|
22
|
+
|
|
23
|
+
// Browser-based (ücretli platformlar)
|
|
24
|
+
for (const [name, url] of Object.entries(BROWSER_PLATFORMS)) {
|
|
25
|
+
drivers.push({
|
|
26
|
+
name,
|
|
27
|
+
type: "browser",
|
|
28
|
+
post: (post: Post) => new BrowserDriver(name, url).post(post),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return drivers;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ── Komutlar ─────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
async function cmdLogin(platform: string) {
|
|
38
|
+
const url = BROWSER_PLATFORMS[platform];
|
|
39
|
+
if (!url) {
|
|
40
|
+
console.log(`"${platform}" browser platformu değil. Browser platformları: ${Object.keys(BROWSER_PLATFORMS).join(", ")}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const driver = new BrowserDriver(platform, url);
|
|
44
|
+
await driver.login();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function cmdLearn(taskDescription: string, platform: string) {
|
|
48
|
+
const url = BROWSER_PLATFORMS[platform];
|
|
49
|
+
if (!url) {
|
|
50
|
+
console.log(`"${platform}" browser platformu değil. Platformlar: ${Object.keys(BROWSER_PLATFORMS).join(", ")}`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Görev açıklamasından aksiyon adı türet
|
|
55
|
+
const actionName = taskDescription
|
|
56
|
+
.toLowerCase()
|
|
57
|
+
.replace(/[^a-z0-9çğıöşü\s]/g, "")
|
|
58
|
+
.trim()
|
|
59
|
+
.split(/\s+/)
|
|
60
|
+
.slice(0, 3)
|
|
61
|
+
.join("_") || "custom";
|
|
62
|
+
|
|
63
|
+
const driver = new BrowserDriver(platform, url);
|
|
64
|
+
const map = await driver.learn(taskDescription, actionName);
|
|
65
|
+
console.log(`\n"${actionName}" map (${map.steps.length} adım):`);
|
|
66
|
+
for (const [i, step] of map.steps.entries()) {
|
|
67
|
+
console.log(` ${i + 1}. [${step.action}] ${step.description} → ${step.selector || step.url || ""}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function cmdPost(content: string, platforms?: string[], imagePaths?: string[]) {
|
|
72
|
+
const drivers = getAllDrivers();
|
|
73
|
+
|
|
74
|
+
// Platform filtresi
|
|
75
|
+
const activeDrivers = platforms?.length
|
|
76
|
+
? drivers.filter((d) => platforms.includes(d.name))
|
|
77
|
+
: drivers;
|
|
78
|
+
|
|
79
|
+
if (!activeDrivers.length) {
|
|
80
|
+
console.log("Aktif platform yok. config.json'u kontrol et veya 'login' yap.");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const post: Post = {
|
|
85
|
+
content,
|
|
86
|
+
images: imagePaths,
|
|
87
|
+
platform: "all",
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
console.log(`\nPost gönderiliyor (${activeDrivers.length} platform)...\n`);
|
|
91
|
+
|
|
92
|
+
const results: PostResult[] = [];
|
|
93
|
+
for (const driver of activeDrivers) {
|
|
94
|
+
process.stdout.write(` [${driver.name}] `);
|
|
95
|
+
const result = await driver.post(post);
|
|
96
|
+
results.push(result);
|
|
97
|
+
|
|
98
|
+
if (result.success) {
|
|
99
|
+
console.log(`✓ ${result.url || "gönderildi"}`);
|
|
100
|
+
} else {
|
|
101
|
+
console.log(`✗ ${result.error}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const successCount = results.filter((r) => r.success).length;
|
|
106
|
+
console.log(`\n${successCount}/${results.length} platform başarılı.`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function cmdTest(platform: string) {
|
|
110
|
+
console.log(`[${platform}] Test postu gönderiliyor...`);
|
|
111
|
+
const testContent = `Test post - ${new Date().toLocaleString("tr-TR")} 🧪`;
|
|
112
|
+
|
|
113
|
+
const url = BROWSER_PLATFORMS[platform];
|
|
114
|
+
if (url) {
|
|
115
|
+
const driver = new BrowserDriver(platform, url);
|
|
116
|
+
const result = await driver.post({ content: testContent, platform });
|
|
117
|
+
console.log(result.success ? "✓ Başarılı!" : `✗ Hata: ${result.error}`);
|
|
118
|
+
} else {
|
|
119
|
+
const drivers = getAllDrivers();
|
|
120
|
+
const driver = drivers.find((d) => d.name === platform);
|
|
121
|
+
if (!driver) {
|
|
122
|
+
console.log(`"${platform}" platformu bulunamadı.`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const result = await driver.post({ content: testContent, platform });
|
|
126
|
+
console.log(result.success ? `✓ ${result.url}` : `✗ ${result.error}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function cmdStatus() {
|
|
131
|
+
const config = loadConfig();
|
|
132
|
+
const drivers = getAllDrivers();
|
|
133
|
+
|
|
134
|
+
console.log("\n Platform Durumu\n ───────────────");
|
|
135
|
+
for (const d of drivers) {
|
|
136
|
+
const icon = d.type === "api" ? "🔗" : "🌐";
|
|
137
|
+
console.log(` ${icon} ${d.name} (${d.type})`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!drivers.length) {
|
|
141
|
+
console.log(" Hiçbir platform yapılandırılmamış.");
|
|
142
|
+
console.log(" config.json düzenle veya 'login <platform>' çalıştır.");
|
|
143
|
+
}
|
|
144
|
+
console.log();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Main ─────────────────────────────────────────────────────
|
|
148
|
+
const [, , command, ...args] = process.argv;
|
|
149
|
+
|
|
150
|
+
switch (command) {
|
|
151
|
+
case "login":
|
|
152
|
+
await cmdLogin(args[0] || "x");
|
|
153
|
+
break;
|
|
154
|
+
|
|
155
|
+
case "learn": {
|
|
156
|
+
// npx tsx cli.ts learn "kişileri takip etmeyi öğren" x
|
|
157
|
+
// npx tsx cli.ts learn x → varsayılan: "post at"
|
|
158
|
+
const lastArg = args[args.length - 1];
|
|
159
|
+
const platform = BROWSER_PLATFORMS[lastArg] ? lastArg : "x";
|
|
160
|
+
const task = args.length > 1
|
|
161
|
+
? args.slice(0, -1).join(" ")
|
|
162
|
+
: (BROWSER_PLATFORMS[args[0]] ? "yeni bir metin postu at" : args[0] || "yeni bir metin postu at");
|
|
163
|
+
await cmdLearn(task, platform);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
case "post": {
|
|
168
|
+
const content = args[0];
|
|
169
|
+
if (!content) {
|
|
170
|
+
console.log('Kullanım: tsx cli.ts post "Post metni" [--platforms x,mastodon] [--image foto.png]');
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
const platformIdx = args.indexOf("--platforms");
|
|
174
|
+
const platforms = platformIdx > -1 ? args[platformIdx + 1]?.split(",") : undefined;
|
|
175
|
+
const imageIdx = args.indexOf("--image");
|
|
176
|
+
const images = imageIdx > -1 ? [args[imageIdx + 1]] : undefined;
|
|
177
|
+
await cmdPost(content, platforms, images);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
case "run": {
|
|
182
|
+
// npx tsx cli.ts run x "son 3 tweeti beğen ve takip et"
|
|
183
|
+
// npx tsx cli.ts run linkedin "bağlantı isteği gönder"
|
|
184
|
+
const runPlatform = BROWSER_PLATFORMS[args[0]] ? args[0] : "x";
|
|
185
|
+
const runCmd = BROWSER_PLATFORMS[args[0]]
|
|
186
|
+
? args.slice(1).join(" ")
|
|
187
|
+
: args.join(" ");
|
|
188
|
+
if (!runCmd) {
|
|
189
|
+
console.log('Kullanım: tsx cli.ts run <platform> "doğal dil komutu"');
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
await runCommand(runPlatform, runCmd);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
case "test":
|
|
197
|
+
await cmdTest(args[0] || "mastodon");
|
|
198
|
+
break;
|
|
199
|
+
|
|
200
|
+
case "history":
|
|
201
|
+
printHistory(parseInt(args[0]) || 20);
|
|
202
|
+
break;
|
|
203
|
+
|
|
204
|
+
case "status":
|
|
205
|
+
cmdStatus();
|
|
206
|
+
break;
|
|
207
|
+
|
|
208
|
+
default:
|
|
209
|
+
console.log(`
|
|
210
|
+
social-agent - AI destekli sosyal medya otomasyon aracı
|
|
211
|
+
|
|
212
|
+
Komutlar:
|
|
213
|
+
login <platform> Chrome profil seç ve giriş yap
|
|
214
|
+
learn "<görev açıklaması>" <platform> AI ile herhangi bir aksiyonu öğret
|
|
215
|
+
run <platform> "doğal dil komutu" AI planla + çalıştır (map'leri kombine eder)
|
|
216
|
+
post "metin" Post at
|
|
217
|
+
--platforms x,mastodon Belirli platformlara
|
|
218
|
+
--image foto.png Görsel ekle
|
|
219
|
+
test <platform> Test postu gönder
|
|
220
|
+
status Bağlı platformları göster
|
|
221
|
+
|
|
222
|
+
Örnekler:
|
|
223
|
+
tsx cli.ts login x
|
|
224
|
+
tsx cli.ts learn x → post atma öğren
|
|
225
|
+
tsx cli.ts learn "kişileri takip etmeyi öğren" x → takip akışı
|
|
226
|
+
tsx cli.ts learn "tweet beğenmeyi öğren" x → beğeni akışı
|
|
227
|
+
tsx cli.ts run x "uiuxgorkem'in son 3 tweetini beğen" → AI planla + çalıştır
|
|
228
|
+
tsx cli.ts run x "şu tweeti beğen ve rt yap: https://..." → kombine aksiyon
|
|
229
|
+
tsx cli.ts run linkedin "bağlantı isteği gönder: Ahmet Yılmaz" → eksik map'i öğren + çalıştır
|
|
230
|
+
tsx cli.ts post "Yeni özellik!" --platforms x,mastodon
|
|
231
|
+
tsx cli.ts test mastodon
|
|
232
|
+
`);
|
|
233
|
+
}
|
package/core/config.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
|
|
5
|
+
// Data dizini: SOCIAL_AGENT_DATA env var'ı veya ~/.social-agent/
|
|
6
|
+
const DATA_DIR = process.env.SOCIAL_AGENT_DATA || join(homedir(), ".social-agent");
|
|
7
|
+
|
|
8
|
+
export const PATHS = {
|
|
9
|
+
root: DATA_DIR,
|
|
10
|
+
maps: join(DATA_DIR, "maps"),
|
|
11
|
+
profiles: join(DATA_DIR, "profiles"),
|
|
12
|
+
screenshots: join(DATA_DIR, "screenshots"),
|
|
13
|
+
knowledge: join(DATA_DIR, "knowledge"),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export interface Config {
|
|
17
|
+
mastodon?: {
|
|
18
|
+
instanceUrl: string;
|
|
19
|
+
accessToken: string;
|
|
20
|
+
};
|
|
21
|
+
bluesky?: {
|
|
22
|
+
identifier: string;
|
|
23
|
+
password: string;
|
|
24
|
+
};
|
|
25
|
+
telegram?: {
|
|
26
|
+
botToken: string;
|
|
27
|
+
chatId: string;
|
|
28
|
+
};
|
|
29
|
+
browser?: {
|
|
30
|
+
chromePath?: string;
|
|
31
|
+
headless?: boolean;
|
|
32
|
+
};
|
|
33
|
+
claude?: {
|
|
34
|
+
model: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function loadConfig(): Config {
|
|
39
|
+
const configPath = join(DATA_DIR, "config.json");
|
|
40
|
+
if (!existsSync(configPath)) {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
return JSON.parse(readFileSync(configPath, "utf-8"));
|
|
44
|
+
}
|
package/core/history.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { PATHS } from "./config.js";
|
|
4
|
+
|
|
5
|
+
const HISTORY_PATH = join(PATHS.root, "history.json");
|
|
6
|
+
|
|
7
|
+
export interface PostRecord {
|
|
8
|
+
id: string;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
platform: string;
|
|
11
|
+
content: string;
|
|
12
|
+
image?: string;
|
|
13
|
+
url?: string;
|
|
14
|
+
success: boolean;
|
|
15
|
+
error?: string;
|
|
16
|
+
action: string; // "post", "like", "retweet", "follow", etc.
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function loadHistory(): PostRecord[] {
|
|
20
|
+
if (!existsSync(HISTORY_PATH)) return [];
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(readFileSync(HISTORY_PATH, "utf-8"));
|
|
23
|
+
} catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function saveToHistory(record: Omit<PostRecord, "id" | "timestamp">): PostRecord {
|
|
29
|
+
const history = loadHistory();
|
|
30
|
+
const entry: PostRecord = {
|
|
31
|
+
id: `${Date.now()}-${Math.random().toString(36).substring(2, 6)}`,
|
|
32
|
+
timestamp: new Date().toISOString(),
|
|
33
|
+
...record,
|
|
34
|
+
};
|
|
35
|
+
history.push(entry);
|
|
36
|
+
writeFileSync(HISTORY_PATH, JSON.stringify(history, null, 2));
|
|
37
|
+
return entry;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Son N postu getir (sadece başarılı olanlar, sadece post aksiyonu)
|
|
42
|
+
*/
|
|
43
|
+
export function getRecentPosts(count = 20): PostRecord[] {
|
|
44
|
+
return loadHistory()
|
|
45
|
+
.filter((r) => r.action === "post" && r.success)
|
|
46
|
+
.slice(-count);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Platform bazlı son postlar
|
|
51
|
+
*/
|
|
52
|
+
export function getRecentPostsByPlatform(platform: string, count = 10): PostRecord[] {
|
|
53
|
+
return loadHistory()
|
|
54
|
+
.filter((r) => r.platform === platform && r.action === "post" && r.success)
|
|
55
|
+
.slice(-count);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Tüm geçmişi insan-okunur formatta döndür (AI'ın üslup analizi için)
|
|
60
|
+
*/
|
|
61
|
+
export function getHistoryForAI(count = 15): string {
|
|
62
|
+
const posts = getRecentPosts(count);
|
|
63
|
+
if (!posts.length) return "Henüz post geçmişi yok.";
|
|
64
|
+
|
|
65
|
+
return posts
|
|
66
|
+
.map((p) => `[${p.timestamp.split("T")[0]}] [${p.platform}] ${p.content.substring(0, 200)}`)
|
|
67
|
+
.join("\n");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Geçmişi konsola yazdır
|
|
72
|
+
*/
|
|
73
|
+
export function printHistory(count = 20): void {
|
|
74
|
+
const history = loadHistory().slice(-count);
|
|
75
|
+
|
|
76
|
+
if (!history.length) {
|
|
77
|
+
console.log(" Henüz geçmiş yok.");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log(`\n Son ${Math.min(count, history.length)} işlem:\n ─────────────────`);
|
|
82
|
+
|
|
83
|
+
for (const record of history) {
|
|
84
|
+
const date = new Date(record.timestamp).toLocaleString("tr-TR", {
|
|
85
|
+
day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit",
|
|
86
|
+
});
|
|
87
|
+
const icon = record.success ? "✓" : "✗";
|
|
88
|
+
const actionIcon = record.action === "post" ? "📝" : record.action === "like" ? "❤" : "▶";
|
|
89
|
+
const content = record.content ? record.content.substring(0, 60) + (record.content.length > 60 ? "..." : "") : record.action;
|
|
90
|
+
|
|
91
|
+
console.log(` ${icon} ${actionIcon} [${date}] [${record.platform}] ${content}`);
|
|
92
|
+
if (record.url) console.log(` → ${record.url}`);
|
|
93
|
+
if (record.error) console.log(` ✗ ${record.error}`);
|
|
94
|
+
}
|
|
95
|
+
console.log();
|
|
96
|
+
}
|