@myk794/adly 1.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.
Files changed (2) hide show
  1. package/adly.js +321 -0
  2. package/package.json +34 -0
package/adly.js ADDED
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env node
2
+ // Adly CLI — Claude Code (terminal) entegrasyonu. Faz 1, MOCK ödeme.
3
+ //
4
+ // Komutlar:
5
+ // adly login --key <anahtar> Web panelindeki API anahtarıyla bağlanır (~/.adly/config.json)
6
+ // adly config --api <url> Backend URL'sini ayarlar (varsayılan: prod)
7
+ // adly status Bakiye + istatistik
8
+ // adly open Son gösterilen reklamı tarayıcıda aç (tıklama → 50× kazanç)
9
+ // adly watch Terminalde canlı sponsorlu satır + kazanç (demo)
10
+ // adly line Tek satır basar (Claude Code statusLine için)
11
+ // adly install-statusline ~/.claude/settings.json'a statusLine olarak ekler
12
+ // adly uninstall-statusline statusLine ayarını geri alır
13
+ //
14
+ // "line" sık çağrılır (statusLine ~300ms). State dosyasıyla throttle eder:
15
+ // gösterimi yalnızca 5sn'de bir sayar (gerçek impression penceresi).
16
+
17
+ const fs = require("node:fs");
18
+ const path = require("node:path");
19
+ const os = require("node:os");
20
+
21
+ const HOME = os.homedir();
22
+ const ADLY_DIR = path.join(HOME, ".adly");
23
+ const CONFIG_PATH = path.join(ADLY_DIR, "config.json");
24
+ const STATE_PATH = path.join(ADLY_DIR, "state.json");
25
+ const CLAUDE_SETTINGS = path.join(HOME, ".claude", "settings.json");
26
+
27
+ const IMPRESSION_MS = 5000;
28
+
29
+ function ensureDir() { fs.mkdirSync(ADLY_DIR, { recursive: true }); }
30
+ function readJSON(p, def) { try { return JSON.parse(fs.readFileSync(p, "utf8")); } catch { return def; } }
31
+ function writeJSON(p, o) { ensureDir(); fs.writeFileSync(p, JSON.stringify(o, null, 2)); }
32
+
33
+ function loadConfig() {
34
+ const c = readJSON(CONFIG_PATH, {});
35
+ return { api: c.api || process.env.ADLY_API || "https://web-eight-zeta-10.vercel.app", token: c.token || null };
36
+ }
37
+
38
+ // Emoji bazı Windows terminallerinde glyph olmadığı için "?" çıkar. ASCII modunda
39
+ // emoji yerine sade işaretler basılır. Öncelik: ADLY_ASCII env → config.ascii → platform.
40
+ // (Windows varsayılan ASCII; ADLY_ASCII=0 veya `adly config --ascii off` ile emoji açılır.)
41
+ function useAscii() {
42
+ const env = process.env.ADLY_ASCII;
43
+ if (env === "1" || env === "true") return true;
44
+ if (env === "0" || env === "false") return false;
45
+ const c = readJSON(CONFIG_PATH, {});
46
+ if (typeof c.ascii === "boolean") return c.ascii;
47
+ return process.platform === "win32";
48
+ }
49
+ function sym() {
50
+ return useAscii()
51
+ ? { money: "$", bolt: "*", dev: "#", ok: "[OK]", err: "x", sep: "-", lira: "TRY " }
52
+ : { money: "💸", bolt: "⚡", dev: "👨‍💻", ok: "✅", err: "✗", sep: "·", lira: "₺" };
53
+ }
54
+
55
+ async function api(p, { method = "GET", body, token } = {}) {
56
+ const cfg = loadConfig();
57
+ const ctrl = new AbortController();
58
+ const t = setTimeout(() => ctrl.abort(), 2500);
59
+ try {
60
+ const r = await fetch(cfg.api + p, {
61
+ method,
62
+ headers: { "Content-Type": "application/json", ...(token ? { Authorization: "Bearer " + token } : {}) },
63
+ body: body ? JSON.stringify(body) : undefined,
64
+ signal: ctrl.signal,
65
+ });
66
+ return await r.json();
67
+ } finally {
68
+ clearTimeout(t);
69
+ }
70
+ }
71
+
72
+ // ---------- Komutlar ----------
73
+
74
+ async function cmdLogin(args) {
75
+ const cfg = loadConfig();
76
+ const i = args.indexOf("--key");
77
+ const key = i !== -1 ? args[i + 1] : args[0];
78
+ if (!key) {
79
+ return fail("kullanım: adly login --key <api-anahtarı>\n (Anahtarı web panelindeki 'API anahtarı' kartından kopyala.)");
80
+ }
81
+ // Anahtarı doğrula: kaydetmeden önce /me'ye sor.
82
+ let me;
83
+ try {
84
+ me = await api("/api/developer/me", { token: key });
85
+ } catch {
86
+ return fail("backend'e ulaşılamadı: " + cfg.api + " (adly config --api <url> ile değiştir)");
87
+ }
88
+ if (!me || me.error) return fail("anahtar doğrulanamadı: " + (me && me.error ? me.error : "bilinmiyor"));
89
+ writeJSON(CONFIG_PATH, { ...readJSON(CONFIG_PATH, {}), api: cfg.api, token: key, email: me.email });
90
+ console.log(`${sym().ok} Bağlandı (${me.email || "geliştirici"}). API anahtarı kaydedildi.`);
91
+ console.log(`Sonraki: adly install-statusline (Claude Code'a bağla) veya adly status`);
92
+ }
93
+
94
+ function cmdConfig(args) {
95
+ const i = args.indexOf("--api");
96
+ const a = args.indexOf("--ascii");
97
+ if (i !== -1 && args[i + 1]) {
98
+ writeJSON(CONFIG_PATH, { ...readJSON(CONFIG_PATH, {}), api: args[i + 1] });
99
+ console.log(`${sym().ok} API ayarlandı: ` + args[i + 1]);
100
+ } else if (a !== -1 && args[a + 1]) {
101
+ const v = args[a + 1].toLowerCase();
102
+ const next = { ...readJSON(CONFIG_PATH, {}) };
103
+ if (v === "on" || v === "1" || v === "true") next.ascii = true;
104
+ else if (v === "off" || v === "0" || v === "false") next.ascii = false;
105
+ else if (v === "auto") delete next.ascii; // platforma göre (Windows=ASCII)
106
+ else return fail("kullanım: adly config --ascii <on|off|auto>");
107
+ writeJSON(CONFIG_PATH, next);
108
+ console.log(`${sym().ok} ASCII modu: ` + (v === "auto" ? "auto (platform)" : v) + ` → emoji ${useAscii() ? "KAPALI" : "AÇIK"}`);
109
+ } else if (args.indexOf("--links") !== -1 && args[args.indexOf("--links") + 1]) {
110
+ const v = args[args.indexOf("--links") + 1].toLowerCase();
111
+ const next = { ...readJSON(CONFIG_PATH, {}) };
112
+ if (v === "on" || v === "1" || v === "true") next.links = true;
113
+ else if (v === "off" || v === "0" || v === "false") next.links = false;
114
+ else return fail("kullanım: adly config --links <on|off>");
115
+ writeJSON(CONFIG_PATH, next);
116
+ console.log(`${sym().ok} Tıklanabilir link (OSC 8): ${useLinks() ? "AÇIK" : "KAPALI"}`);
117
+ } else {
118
+ console.log(JSON.stringify({ ...loadConfig(), ascii: useAscii(), links: useLinks() }, null, 2));
119
+ }
120
+ }
121
+
122
+ async function cmdStatus() {
123
+ const { token } = loadConfig();
124
+ if (!token) return fail("önce: adly login <email>");
125
+ const me = await api("/api/developer/me", { token });
126
+ console.log(`${sym().dev} ${me.email}`);
127
+ console.log(` Bakiye: ${me.balance_try} TRY`);
128
+ console.log(` Gösterim: ${me.impressions}`);
129
+ console.log(` Tıklama: ${me.clicks}`);
130
+ console.log(` Payout eşiği: ${me.payout_threshold_try} TRY`);
131
+ }
132
+
133
+ // Claude Code'un statusLine'a stdin'den verdiği JSON'u oku.
134
+ // Manuel terminalde (TTY) çağrılırsa beklemeden boş döner.
135
+ function readStdinJSON() {
136
+ return new Promise((resolve) => {
137
+ if (process.stdin.isTTY) return resolve({});
138
+ let data = "";
139
+ let done = false;
140
+ const finish = () => { if (done) return; done = true; try { resolve(JSON.parse(data || "{}")); } catch { resolve({}); } };
141
+ const to = setTimeout(finish, 400);
142
+ process.stdin.setEncoding("utf8");
143
+ process.stdin.on("data", (c) => (data += c));
144
+ process.stdin.on("end", () => { clearTimeout(to); finish(); });
145
+ });
146
+ }
147
+
148
+ // statusLine için tek satır. TOKEN-BAZLI: cost.total_cost_usd değişince
149
+ // (= yeni tur işlendi) o turda tüketilen token kadar faturalandırır.
150
+ async function cmdLine() {
151
+ const S = sym();
152
+ const { token } = loadConfig();
153
+ if (!token) { process.stdout.write(`${S.bolt} Adly: giriş yok (adly login)`); return; }
154
+
155
+ const payload = await readStdinJSON();
156
+ const sessionId = payload.session_id || "default";
157
+ const cu = payload.context_window && payload.context_window.current_usage;
158
+ const costUsd =
159
+ payload.cost && typeof payload.cost.total_cost_usd === "number" ? payload.cost.total_cost_usd : null;
160
+
161
+ const all = readJSON(STATE_PATH, {});
162
+ const st = all[sessionId] || {};
163
+ const sig = costUsd != null ? costUsd.toFixed(6) : null;
164
+ const newTurn = sig != null && sig !== st.lastSig; // cost arttı = yeni tur
165
+
166
+ try {
167
+ if (newTurn) {
168
+ // Bu turda yakılan token (taze input + cache yazımı + üretilen çıktı).
169
+ let tokens = 0;
170
+ if (cu) tokens = (cu.output_tokens || 0) + (cu.input_tokens || 0) + (cu.cache_creation_input_tokens || 0);
171
+ // Önceki reklamı bu turun token'ıyla faturala.
172
+ if (st.impressionToken && tokens > 0) {
173
+ const imp = await api("/api/ads/impression", { method: "POST", token, body: { impressionToken: st.impressionToken, tokens } });
174
+ if (imp && imp.ok) { st.balance = imp.balance_try; st.lastTokens = tokens; }
175
+ }
176
+ // Sonraki tur için yeni reklam al.
177
+ const s = await api("/api/ads/serve", { token });
178
+ if (s && !s.empty && s.campaign) {
179
+ st.line = s.campaign.text; st.url = s.campaign.url; st.impressionToken = s.impressionToken; st.clickPath = s.click_path || null;
180
+ // Gösterilen reklamın tıklama linki — `adly open` ve OSC 8 link bunu kullanır.
181
+ if (s.click_path) all._lastClick = { path: s.click_path, text: st.line, url: st.url };
182
+ }
183
+ else { st.line = `${S.bolt} Adly: aktif reklam yok`; st.impressionToken = null; st.clickPath = null; }
184
+ st.lastSig = sig;
185
+ all[sessionId] = st; writeJSON(STATE_PATH, all);
186
+ } else if (!st.impressionToken) {
187
+ // İlk çalıştırma: bir reklam al (henüz faturalama yok).
188
+ const s = await api("/api/ads/serve", { token });
189
+ if (s && !s.empty && s.campaign) {
190
+ st.line = s.campaign.text; st.url = s.campaign.url; st.impressionToken = s.impressionToken; st.clickPath = s.click_path || null;
191
+ if (s.click_path) all._lastClick = { path: s.click_path, text: st.line, url: st.url };
192
+ }
193
+ if (sig != null) st.lastSig = sig;
194
+ all[sessionId] = st; writeJSON(STATE_PATH, all);
195
+ }
196
+ } catch {
197
+ // Ağ hatası: son bilinen satırı bas, çökme yok.
198
+ }
199
+
200
+ const bal = st.balance ? ` ${S.sep} ${S.lira}${st.balance}` : "";
201
+ // İstenirse reklam metnini OSC 8 ile tıklanabilir yap (Ctrl/Cmd+click → tıklama 50× + reklamveren).
202
+ let line = st.line || S.bolt + " Adly";
203
+ if (useLinks() && st.clickPath && st.line) {
204
+ const url = loadConfig().api + st.clickPath;
205
+ line = `\x1b]8;;${url}\x07${line}\x1b]8;;\x07`; // OSC 8: ESC ]8;;URL BEL metin ESC ]8;;BEL
206
+ }
207
+ process.stdout.write(`${S.money} ${line}${bal}`);
208
+ }
209
+
210
+ // OSC 8 tıklanabilir link: env ADLY_LINKS → config.links → varsayılan KAPALI.
211
+ // (Terminal/Claude Code desteklemiyorsa düz metin görünür; Windows Terminal'de FORCE_HYPERLINK=1 gerekebilir.)
212
+ function useLinks() {
213
+ const env = process.env.ADLY_LINKS;
214
+ if (env === "1" || env === "true") return true;
215
+ if (env === "0" || env === "false") return false;
216
+ const c = readJSON(CONFIG_PATH, {});
217
+ return c.links === true;
218
+ }
219
+
220
+ // En son gösterilen reklamı tarayıcıda aç → tıklamayı kaydeder (50× kazanç), reklamverene yönlendirir.
221
+ async function cmdOpen() {
222
+ const cfg = loadConfig();
223
+ if (!cfg.token) return fail("önce: adly login --key <anahtar>");
224
+ const all = readJSON(STATE_PATH, {});
225
+ const lc = all._lastClick;
226
+ if (!lc || !lc.path) {
227
+ return fail("açılacak reklam yok — Claude Code'da bir tur işlensin (reklam faturalanınca aktif olur).");
228
+ }
229
+ const url = cfg.api + lc.path;
230
+ console.log(`${sym().money} Açılıyor: ${lc.text || "reklam"}`);
231
+ openUrl(url);
232
+ }
233
+
234
+ function openUrl(url) {
235
+ const { spawn } = require("node:child_process");
236
+ let cmd, args;
237
+ if (process.platform === "win32") { cmd = "cmd"; args = ["/c", "start", "", url]; }
238
+ else if (process.platform === "darwin") { cmd = "open"; args = [url]; }
239
+ else { cmd = "xdg-open"; args = [url]; }
240
+ try { spawn(cmd, args, { detached: true, stdio: "ignore" }).unref(); }
241
+ catch { console.log("Tarayıcıda aç: " + url); }
242
+ }
243
+
244
+ // Terminalde canlı demo (Claude Code'a bağlamadan görmek için).
245
+ async function cmdWatch() {
246
+ const S = sym();
247
+ const { token } = loadConfig();
248
+ if (!token) return fail("önce: adly login <email>");
249
+ console.log("Adly izleniyor… (Ctrl+C ile çık)\n");
250
+ for (;;) {
251
+ let s;
252
+ try { s = await api("/api/ads/serve", { token }); } catch { s = null; }
253
+ if (!s || s.empty || !s.campaign) {
254
+ process.stdout.write(`\r${S.bolt} Adly: aktif reklam yok `);
255
+ await sleep(IMPRESSION_MS);
256
+ continue;
257
+ }
258
+ process.stdout.write(`\r${S.money} ${s.campaign.text} `);
259
+ await sleep(IMPRESSION_MS);
260
+ try {
261
+ const imp = await api("/api/ads/impression", { method: "POST", token, body: { impressionToken: s.impressionToken } });
262
+ if (imp && imp.ok) process.stdout.write(`\r${S.money} ${s.campaign.text} → +${imp.earned_try}${S.lira} (bakiye ${imp.balance_try}${S.lira})\n`);
263
+ } catch {}
264
+ }
265
+ }
266
+
267
+ function cmdInstallStatusline() {
268
+ const selfPath = path.resolve(__filename);
269
+ const settings = readJSON(CLAUDE_SETTINGS, {});
270
+ if (fs.existsSync(CLAUDE_SETTINGS)) {
271
+ fs.copyFileSync(CLAUDE_SETTINGS, CLAUDE_SETTINGS + ".adly-bak");
272
+ }
273
+ settings.statusLine = {
274
+ type: "command",
275
+ command: `node "${selfPath}" line`,
276
+ padding: 0,
277
+ };
278
+ fs.mkdirSync(path.dirname(CLAUDE_SETTINGS), { recursive: true });
279
+ fs.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2));
280
+ console.log(`${sym().ok} Claude Code statusLine'a Adly eklendi.`);
281
+ console.log(" Ayar: " + CLAUDE_SETTINGS + " (yedek: .adly-bak)");
282
+ console.log(" Yeni bir Claude Code oturumu başlat — sponsorlu satır en altta görünecek.");
283
+ }
284
+
285
+ function cmdUninstallStatusline() {
286
+ const settings = readJSON(CLAUDE_SETTINGS, null);
287
+ if (!settings) return fail("settings.json yok");
288
+ delete settings.statusLine;
289
+ fs.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2));
290
+ console.log(`${sym().ok} statusLine kaldırıldı.`);
291
+ }
292
+
293
+ // ---------- yardımcılar ----------
294
+ function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
295
+ function fail(msg) { console.error(sym().err + " " + msg); process.exitCode = 1; }
296
+
297
+ const [cmd, ...rest] = process.argv.slice(2);
298
+ (async () => {
299
+ switch (cmd) {
300
+ case "login": return cmdLogin(rest);
301
+ case "config": return cmdConfig(rest);
302
+ case "status": return cmdStatus();
303
+ case "line": return cmdLine();
304
+ case "open": return cmdOpen();
305
+ case "watch": return cmdWatch();
306
+ case "install-statusline": return cmdInstallStatusline();
307
+ case "uninstall-statusline": return cmdUninstallStatusline();
308
+ default:
309
+ console.log("Adly CLI — komutlar:");
310
+ console.log(" adly login --key <anahtar> API anahtarıyla bağlan (web panelinden kopyala)");
311
+ console.log(" adly config --api <url> backend URL (varsayılan: prod)");
312
+ console.log(" adly config --ascii on|off|auto emoji yerine ASCII (Windows varsayılan: on)");
313
+ console.log(" adly config --links on|off reklamı tıklanabilir yap (OSC 8, varsayılan: off)");
314
+ console.log(" adly status bakiye/istatistik");
315
+ console.log(" adly open son reklamı tarayıcıda aç (tıklama = 50× kazanç)");
316
+ console.log(" adly watch canlı demo (terminal)");
317
+ console.log(" adly line tek satır (statusLine)");
318
+ console.log(" adly install-statusline Claude Code'a bağla");
319
+ console.log(" adly uninstall-statusline geri al");
320
+ }
321
+ })();
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@myk794/adly",
3
+ "version": "1.0.0",
4
+ "description": "Adly CLI — Claude Code statusLine'ını sponsorlu satıra çevirir; izledikçe kazanırsın.",
5
+ "bin": {
6
+ "adly": "adly.js"
7
+ },
8
+ "files": [
9
+ "adly.js"
10
+ ],
11
+ "keywords": [
12
+ "adly",
13
+ "claude-code",
14
+ "statusline",
15
+ "cli",
16
+ "ads"
17
+ ],
18
+ "engines": {
19
+ "node": ">=20"
20
+ },
21
+ "license": "MIT",
22
+ "homepage": "https://github.com/myk794/Adly#readme",
23
+ "bugs": {
24
+ "url": "https://github.com/myk794/Adly/issues"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/myk794/Adly.git",
29
+ "directory": "cli"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
34
+ }