bibest-code 2.0.0 → 2.0.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.
Files changed (2) hide show
  1. package/bibest-code.js +325 -245
  2. package/package.json +13 -4
package/bibest-code.js CHANGED
@@ -1,10 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  // ============================================================
3
- // BIBEST CODE v2.0.0 — Agent CLI propulsé par Claude
3
+ // BIBEST CODE v3.0.0 — Agent CLI propulsé par Claude
4
+ // Supabase + Stripe + Quota + Licence
4
5
  // © BIBI ONDOUA — Tous droits réservés
5
6
  // ============================================================
6
7
 
7
8
  import Anthropic from "@anthropic-ai/sdk";
9
+ import { createClient } from "@supabase/supabase-js";
10
+ import ws from "ws";
8
11
  import * as readline from "readline";
9
12
  import * as fs from "fs";
10
13
  import * as path from "path";
@@ -12,102 +15,148 @@ import { exec } from "child_process";
12
15
  import { promisify } from "util";
13
16
 
14
17
  const execAsync = promisify(exec);
15
- const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
16
18
 
17
- // ── Couleurs & styles ──────────────────────────────────────
19
+ // ── Config ─────────────────────────────────────────────────
20
+ const SUPABASE_URL = process.env.SUPABASE_URL || "https://fkkhzvstilmgtvkbbjxz.supabase.co";
21
+ const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY || "";
22
+ const ANTHROPIC_KEY = process.env.ANTHROPIC_API_KEY || "";
23
+
24
+ const PLANS = {
25
+ free: { name: "Free", tokens: 5000, price: 0 },
26
+ starter: { name: "Starter", tokens: 500000, price: 9 },
27
+ pro: { name: "Pro", tokens: 2000000, price: 29 },
28
+ business: { name: "Business", tokens: 10000000, price: 79 },
29
+ enterprise: { name: "Enterprise", tokens: -1, price: 199 },
30
+ founder: { name: "Founder", tokens: -1, price: 0 },
31
+ };
32
+
33
+ // ── Couleurs ────────────────────────────────────────────────
18
34
  const C = {
19
- reset: "\x1b[0m",
20
- bold: "\x1b[1m",
21
- dim: "\x1b[2m",
22
- italic: "\x1b[3m",
23
- underline: "\x1b[4m",
24
- cyan: "\x1b[36m",
25
- cyanBright: "\x1b[96m",
26
- green: "\x1b[32m",
27
- greenBright: "\x1b[92m",
28
- yellow: "\x1b[33m",
29
- yellowBright: "\x1b[93m",
30
- red: "\x1b[31m",
31
- redBright: "\x1b[91m",
32
- magenta: "\x1b[35m",
33
- magentaBright: "\x1b[95m",
34
- blue: "\x1b[34m",
35
- blueBright: "\x1b[94m",
36
- white: "\x1b[37m",
37
- whiteBright: "\x1b[97m",
35
+ reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
36
+ cyan: "\x1b[36m", cyanBright: "\x1b[96m",
37
+ green: "\x1b[32m", greenBright: "\x1b[92m",
38
+ yellow: "\x1b[33m", yellowBright: "\x1b[93m",
39
+ red: "\x1b[31m", redBright: "\x1b[91m",
40
+ magenta: "\x1b[35m", magentaBright: "\x1b[95m",
41
+ blue: "\x1b[34m", blueBright: "\x1b[94m",
42
+ white: "\x1b[37m", whiteBright: "\x1b[97m",
38
43
  gray: "\x1b[90m",
39
- bgCyan: "\x1b[46m",
40
- bgBlue: "\x1b[44m",
41
- bgGreen: "\x1b[42m",
42
- bgRed: "\x1b[41m",
43
- bgMagenta: "\x1b[45m",
44
- bgYellow: "\x1b[43m",
45
44
  };
46
45
 
47
46
  const W = process.stdout.columns || 80;
48
- const line = (char = "─", color = C.gray) => color + char.repeat(W) + C.reset;
49
- const pad = (text, width) => text + " ".repeat(Math.max(0, width - stripAnsi(text).length));
50
- const stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*m/g, "");
51
- const center = (text, color = "") => {
52
- const len = stripAnsi(text).length;
53
- const spaces = Math.max(0, Math.floor((W - len) / 2));
54
- return " ".repeat(spaces) + color + text + C.reset;
55
- };
56
- const box = (lines, borderColor = C.cyanBright) => {
57
- const maxLen = Math.max(...lines.map(l => stripAnsi(l).length));
58
- const w = maxLen + 4;
59
- console.log(borderColor + "" + "═".repeat(w) + "" + C.reset);
60
- lines.forEach(l => {
61
- const padded = pad(l, maxLen);
62
- console.log(borderColor + "║ " + C.reset + padded + " " + borderColor + "║" + C.reset);
63
- });
64
- console.log(borderColor + "╚" + "═".repeat(w) + "╝" + C.reset);
47
+ const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
48
+ const pad = (t, w) => t + " ".repeat(Math.max(0, w - stripAnsi(t).length));
49
+ const center = (t, c = "") => { const l = stripAnsi(t).length; const s = Math.max(0, Math.floor((W - l) / 2)); return " ".repeat(s) + c + t + C.reset; };
50
+ const line = (ch = "─", c = C.gray) => c + ch.repeat(W) + C.reset;
51
+ const println = (c, t) => console.log(c + t + C.reset);
52
+
53
+ const box = (lines, bc = C.cyanBright) => {
54
+ const max = Math.max(...lines.map(l => stripAnsi(l).length));
55
+ const w = max + 4;
56
+ console.log(bc + "╔" + "═".repeat(w) + "╗" + C.reset);
57
+ lines.forEach(l => console.log(bc + "║ " + C.reset + pad(l, max) + " " + bc + "║" + C.reset));
58
+ console.log(bc + "" + "═".repeat(w) + "" + C.reset);
65
59
  };
66
60
 
67
- // ── État global ────────────────────────────────────────────
68
- let sessionStats = { messages: 0, filesCreated: 0, filesRead: 0, commandsRun: 0, tokens: 0 };
61
+ // ── État ────────────────────────────────────────────────────
62
+ let currentUser = null;
69
63
  let currentModel = "claude-sonnet-4-6";
64
+ let sessionTokens = 0;
70
65
  let sessionStart = new Date();
66
+ let sessionMsgs = 0;
71
67
  const HISTORY_FILE = path.join(process.env.USERPROFILE || process.env.HOME || ".", ".bibest-code-history.json");
72
68
 
73
- // ── Header principal ───────────────────────────────────────
69
+ // ── Supabase ────────────────────────────────────────────────
70
+ let supabase = null;
71
+ function getSupabase() {
72
+ if (!supabase && SUPABASE_URL && SUPABASE_ANON_KEY) {
73
+ supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { realtime: { transport: ws } });
74
+ }
75
+ return supabase;
76
+ }
77
+
78
+ async function loadUser(email) {
79
+ const sb = getSupabase();
80
+ if (!sb) return null;
81
+ const { data } = await sb.from("users").select("*, plans(*)").eq("email", email).single();
82
+ return data;
83
+ }
84
+
85
+ async function logUsage(userId, tokens, model) {
86
+ const sb = getSupabase();
87
+ if (!sb || !userId) return;
88
+ await sb.from("usage_logs").insert({ user_id: userId, tokens_used: tokens, model });
89
+ await sb.from("users").update({ tokens_used: supabase.rpc ? undefined : undefined }).eq("id", userId);
90
+ }
91
+
92
+ async function checkQuota(user) {
93
+ if (!user) return { ok: true, remaining: 5000, plan: "free" };
94
+ const planId = user.plan_id || "free";
95
+ const plan = PLANS[planId];
96
+ if (plan.tokens === -1) return { ok: true, remaining: -1, plan: planId };
97
+ const sb = getSupabase();
98
+ if (!sb) return { ok: true, remaining: plan.tokens, plan: planId };
99
+ const startOfMonth = new Date(); startOfMonth.setDate(1); startOfMonth.setHours(0, 0, 0, 0);
100
+ const { data } = await sb.from("usage_logs").select("tokens_used").eq("user_id", user.id).gte("created_at", startOfMonth.toISOString());
101
+ const used = data ? data.reduce((s, r) => s + r.tokens_used, 0) : 0;
102
+ const remaining = plan.tokens - used;
103
+ return { ok: remaining > 0, remaining, plan: planId, used, limit: plan.tokens };
104
+ }
105
+
106
+ // ── Header ──────────────────────────────────────────────────
74
107
  function showHeader() {
75
108
  console.clear();
76
109
  console.log();
77
- console.log(center("██████╗ ██╗██████╗ ███████╗███████╗████████╗", C.cyanBright + C.bold));
78
- console.log(center("██╔══██╗██║██╔══██╗██╔════╝██╔════╝╚══██╔══╝", C.cyanBright + C.bold));
79
- console.log(center("██████╔╝██║██████╔╝█████╗ ███████╗ ██║ ", C.cyan + C.bold));
80
- console.log(center("██╔══██╗██║██╔══██╗██╔══╝ ╚════██║ ██║ ", C.blue + C.bold));
81
- console.log(center("██████╔╝██║██████╔╝███████╗███████║ ██║ ", C.blueBright + C.bold));
82
- console.log(center("╚═════╝ ╚═╝╚═════╝ ╚══════╝╚══════╝ ╚═╝ ", C.gray + C.bold));
83
- console.log(center("██████╗ ██████╗ ██████╗ ███████╗", C.magentaBright + C.bold));
84
- console.log(center("██╔════╝██╔═══██╗██╔══██╗██╔════╝", C.magenta + C.bold));
85
- console.log(center("██║ ██║ ██║██║ ██║█████╗ ", C.magentaBright + C.bold));
86
- console.log(center("██║ ██║ ██║██║ ██║██╔══╝ ", C.magenta + C.bold));
87
- console.log(center("╚██████╗╚██████╔╝██████╔╝███████╗", C.magentaBright + C.bold));
88
- console.log(center(" ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝", C.gray + C.bold));
110
+ println(C.cyanBright + C.bold, center("██████╗ ██╗██████╗ ███████╗███████╗████████╗"));
111
+ println(C.cyanBright + C.bold, center("██╔══██╗██║██╔══██╗██╔════╝██╔════╝╚══██╔══╝"));
112
+ println(C.cyan + C.bold, center("██████╔╝██║██████╔╝█████╗ ███████╗ ██║ "));
113
+ println(C.blue + C.bold, center("██╔══██╗██║██╔══██╗██╔══╝ ╚════██║ ██║ "));
114
+ println(C.blueBright + C.bold, center("██████╔╝██║██████╔╝███████╗███████║ ██║ "));
115
+ println(C.gray + C.bold, center("╚═════╝ ╚═╝╚═════╝ ╚══════╝╚══════╝ ╚═╝ "));
116
+ println(C.magentaBright + C.bold, center("██████╗ ██████╗ ██████╗ ███████╗"));
117
+ println(C.magenta + C.bold, center("██╔════╝██╔═══██╗██╔══██╗██╔════╝"));
118
+ println(C.magentaBright + C.bold, center("██║ ██║ ██║██║ ██║█████╗ "));
119
+ println(C.magenta + C.bold, center("██║ ██║ ██║██║ ██║██╔══╝ "));
120
+ println(C.magentaBright + C.bold, center("╚██████╗╚██████╔╝██████╔╝███████╗"));
121
+ println(C.gray + C.bold, center(" ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝"));
89
122
  console.log();
90
123
  console.log(line("═", C.cyanBright));
91
- console.log(center("🤖 Agent CLI de développement propulsé par Claude AI 🚀", C.whiteBright + C.bold));
92
- console.log(center("© BIBI ONDOUA — Tous droits réservés — v2.0.0", C.gray));
124
+ println(C.whiteBright + C.bold, center("🤖 Agent CLI de développement propulsé par Claude AI 🚀"));
125
+ println(C.gray, center("© BIBI ONDOUA — Tous droits réservés — v3.0.0"));
93
126
  console.log(line("═", C.cyanBright));
94
127
  console.log();
95
128
  }
96
129
 
97
- // ── Barre de statut ────────────────────────────────────────
98
- function showStatus() {
130
+ // ── Barre de statut ─────────────────────────────────────────
131
+ async function showStatus() {
99
132
  const now = new Date();
100
133
  const uptime = Math.floor((now - sessionStart) / 1000);
101
- const mins = Math.floor(uptime / 60).toString().padStart(2, "0");
102
- const secs = (uptime % 60).toString().padStart(2, "0");
103
- const cwd = process.cwd().replace(process.env.USERPROFILE || "", "~");
134
+ const mm = Math.floor(uptime / 60).toString().padStart(2, "0");
135
+ const ss = (uptime % 60).toString().padStart(2, "0");
136
+ const cwd = process.cwd().replace(process.env.USERPROFILE || "", "~").slice(-30);
137
+
138
+ let quotaStr = "";
139
+ if (currentUser) {
140
+ const q = await checkQuota(currentUser);
141
+ const planName = PLANS[currentUser.plan_id || "free"].name;
142
+ if (q.remaining === -1) {
143
+ quotaStr = `${C.greenBright}∞ illimité${C.reset}`;
144
+ } else {
145
+ const pct = Math.round((q.remaining / q.limit) * 100);
146
+ const color = pct > 50 ? C.greenBright : pct > 20 ? C.yellowBright : C.redBright;
147
+ quotaStr = `${color}${q.remaining.toLocaleString()} tokens${C.reset}`;
148
+ }
149
+ } else {
150
+ quotaStr = `${C.yellowBright}5K tokens Free${C.reset}`;
151
+ }
104
152
 
105
153
  const items = [
106
154
  `${C.greenBright}●${C.reset} ${C.gray}Connecté${C.reset}`,
107
- `${C.cyan}⚡${C.reset} ${C.whiteBright}${currentModel}${C.reset}`,
155
+ `${C.cyan}⚡${C.reset} ${C.whiteBright}${currentModel.replace("claude-", "")}${C.reset}`,
108
156
  `${C.yellow}📁${C.reset} ${C.gray}${cwd}${C.reset}`,
109
- `${C.magenta}⏱${C.reset} ${C.gray}${mins}:${secs}${C.reset}`,
110
- `${C.blue}💬${C.reset} ${C.gray}${sessionStats.messages} msgs${C.reset}`,
157
+ `${C.magenta}⏱${C.reset} ${C.gray}${mm}:${ss}${C.reset}`,
158
+ `${C.blue}💬${C.reset} ${C.gray}${sessionMsgs} msgs${C.reset}`,
159
+ `${C.cyan}🔋${C.reset} ${quotaStr}`,
111
160
  ];
112
161
 
113
162
  console.log(line("─", C.gray));
@@ -115,80 +164,165 @@ function showStatus() {
115
164
  console.log(line("─", C.gray));
116
165
  }
117
166
 
118
- // ── Menu d'aide ────────────────────────────────────────────
167
+ // ── Login ───────────────────────────────────────────────────
168
+ async function loginFlow(rl) {
169
+ console.log();
170
+ box([
171
+ `${C.yellowBright + C.bold}🔑 Connexion à BIBEST CODE${C.reset}`,
172
+ "",
173
+ `${C.gray}Entrez votre email pour accéder à votre compte.${C.reset}`,
174
+ `${C.gray}Nouveau ? Un compte Free sera créé automatiquement.${C.reset}`,
175
+ ], C.yellowBright);
176
+
177
+ return new Promise((resolve) => {
178
+ process.stdout.write(`\n${C.cyan}Email : ${C.reset}`);
179
+ rl.once("line", async (email) => {
180
+ email = email.trim().toLowerCase();
181
+ if (!email || !email.includes("@")) {
182
+ println(C.gray, "\n Mode anonyme — plan Free (5K tokens)\n");
183
+ resolve(null);
184
+ return;
185
+ }
186
+
187
+ const sb = getSupabase();
188
+ if (!sb) { resolve(null); return; }
189
+
190
+ let user = await loadUser(email);
191
+ if (!user) {
192
+ // Créer le compte
193
+ const { data, error } = await sb.from("users").insert({
194
+ email, name: email.split("@")[0], plan_id: "free"
195
+ }).select("*, plans(*)").single();
196
+ user = data;
197
+ if (!error) println(C.greenBright, `\n ✅ Compte créé ! Bienvenue sur BIBEST CODE (plan Free)\n`);
198
+ } else {
199
+ const planName = PLANS[user.plan_id || "free"].name;
200
+ println(C.greenBright, `\n ✅ Bonjour ${user.name || email} ! Plan : ${C.bold}${planName}${C.reset}\n`);
201
+ }
202
+ resolve(user);
203
+ });
204
+ });
205
+ }
206
+
207
+ // ── Help ────────────────────────────────────────────────────
119
208
  function showHelp() {
120
209
  console.log();
121
210
  box([
122
- `${C.yellowBright + C.bold}BIBEST CODE — Commandes disponibles${C.reset}`,
211
+ `${C.yellowBright + C.bold}BIBEST CODE — Commandes${C.reset}`,
123
212
  "",
124
- `${C.cyanBright}/help${C.reset} ${C.gray}Afficher cette aide${C.reset}`,
213
+ `${C.cyanBright}/help${C.reset} ${C.gray}Cette aide${C.reset}`,
125
214
  `${C.cyanBright}/clear${C.reset} ${C.gray}Effacer l'écran${C.reset}`,
126
- `${C.cyanBright}/history${C.reset} ${C.gray}Voir l'historique des sessions${C.reset}`,
127
- `${C.cyanBright}/stats${C.reset} ${C.gray}Statistiques de la session${C.reset}`,
215
+ `${C.cyanBright}/stats${C.reset} ${C.gray}Statistiques session${C.reset}`,
216
+ `${C.cyanBright}/quota${C.reset} ${C.gray}Voir votre quota tokens${C.reset}`,
217
+ `${C.cyanBright}/upgrade${C.reset} ${C.gray}Voir les plans et upgrader${C.reset}`,
128
218
  `${C.cyanBright}/model${C.reset} ${C.gray}Changer de modèle Claude${C.reset}`,
129
219
  `${C.cyanBright}/cd <dossier>${C.reset} ${C.gray}Changer de répertoire${C.reset}`,
130
- `${C.cyanBright}/ls${C.reset} ${C.gray}Lister le répertoire courant${C.reset}`,
131
- `${C.cyanBright}/reset${C.reset} ${C.gray}Réinitialiser la conversation${C.reset}`,
132
- `${C.cyanBright}/exit${C.reset} ${C.gray}Quitter BIBEST CODE${C.reset}`,
133
- "",
134
- `${C.gray}Exemples de tâches :${C.reset}`,
135
- `${C.green}▶${C.reset} ${C.italic}crée une API REST en Express${C.reset}`,
136
- `${C.green}▶${C.reset} ${C.italic}analyse et corrige les bugs de mon projet${C.reset}`,
137
- `${C.green}▶${C.reset} ${C.italic}écris des tests unitaires pour app.js${C.reset}`,
138
- `${C.green}▶${C.reset} ${C.italic}optimise les performances de ce code${C.reset}`,
220
+ `${C.cyanBright}/ls${C.reset} ${C.gray}Lister le répertoire${C.reset}`,
221
+ `${C.cyanBright}/reset${C.reset} ${C.gray}Réinitialiser conversation${C.reset}`,
222
+ `${C.cyanBright}/history${C.reset} ${C.gray}Historique sessions${C.reset}`,
223
+ `${C.cyanBright}/exit${C.reset} ${C.gray}Quitter${C.reset}`,
139
224
  ], C.cyanBright);
140
225
  console.log();
141
226
  }
142
227
 
143
- // ── Statistiques ───────────────────────────────────────────
228
+ // ── Quota ────────────────────────────────────────────────────
229
+ async function showQuota() {
230
+ console.log();
231
+ const q = await checkQuota(currentUser);
232
+ const planId = currentUser?.plan_id || "free";
233
+ const plan = PLANS[planId];
234
+
235
+ if (q.remaining === -1) {
236
+ box([
237
+ `${C.greenBright + C.bold}🔋 Quota — Plan ${plan.name}${C.reset}`,
238
+ "",
239
+ `${C.greenBright}✨ Tokens illimités${C.reset}`,
240
+ `${C.gray}Session actuelle : ${sessionTokens.toLocaleString()} tokens utilisés${C.reset}`,
241
+ ], C.greenBright);
242
+ } else {
243
+ const pct = Math.round((q.used / q.limit) * 100);
244
+ const bar = Math.round(pct / 5);
245
+ const barFull = "█".repeat(bar) + "░".repeat(20 - bar);
246
+ const color = pct < 50 ? C.greenBright : pct < 80 ? C.yellowBright : C.redBright;
247
+ box([
248
+ `${C.yellowBright + C.bold}🔋 Quota mensuel — Plan ${plan.name}${C.reset}`,
249
+ "",
250
+ `${color}${barFull} ${pct}%${C.reset}`,
251
+ `${C.gray}Utilisé : ${(q.used || 0).toLocaleString()} tokens${C.reset}`,
252
+ `${C.gray}Restant : ${q.remaining.toLocaleString()} tokens${C.reset}`,
253
+ `${C.gray}Total : ${q.limit.toLocaleString()} tokens/mois${C.reset}`,
254
+ "",
255
+ q.remaining < 1000 ? `${C.redBright}⚠️ Quota presque épuisé — tapez /upgrade${C.reset}` : `${C.greenBright}✅ Quota suffisant${C.reset}`,
256
+ ], color);
257
+ }
258
+ console.log();
259
+ }
260
+
261
+ // ── Upgrade ──────────────────────────────────────────────────
262
+ function showUpgrade() {
263
+ console.log();
264
+ box([
265
+ `${C.yellowBright + C.bold}🚀 Plans BIBEST CODE${C.reset}`,
266
+ "",
267
+ `${C.gray}Plan Prix Tokens/mois${C.reset}`,
268
+ `${C.gray}─────────────────────────────────${C.reset}`,
269
+ `${C.white}Free 0€ 5 000${C.reset} ${C.gray}← actuel${C.reset}`,
270
+ `${C.cyanBright}Starter 9€/mois 500 000${C.reset}`,
271
+ `${C.blueBright}Pro 29€/mois 2 000 000${C.reset}`,
272
+ `${C.magentaBright}Business 79€/mois 10 000 000${C.reset}`,
273
+ `${C.yellowBright}Enterprise 199€/mois Illimité${C.reset}`,
274
+ "",
275
+ `${C.gray}Contactez : b.ondoua00@gmail.com${C.reset}`,
276
+ `${C.gray}ou visitez : npmjs.com/package/bibest-code${C.reset}`,
277
+ ], C.yellowBright);
278
+ console.log();
279
+ }
280
+
281
+ // ── Stats ────────────────────────────────────────────────────
144
282
  function showStats() {
145
283
  console.log();
146
284
  box([
147
- `${C.yellowBright + C.bold}📊 Statistiques de session${C.reset}`,
285
+ `${C.yellowBright + C.bold}📊 Statistiques session${C.reset}`,
148
286
  "",
149
- `${C.cyan}Messages envoyés :${C.reset} ${C.whiteBright}${sessionStats.messages}${C.reset}`,
150
- `${C.cyan}Fichiers créés :${C.reset} ${C.greenBright}${sessionStats.filesCreated}${C.reset}`,
151
- `${C.cyan}Fichiers lus :${C.reset} ${C.blueBright}${sessionStats.filesRead}${C.reset}`,
152
- `${C.cyan}Commandes exécutées:${C.reset} ${C.yellowBright}${sessionStats.commandsRun}${C.reset}`,
153
- `${C.cyan}Modèle actuel :${C.reset} ${C.magentaBright}${currentModel}${C.reset}`,
287
+ `${C.cyan}Messages :${C.reset} ${C.whiteBright}${sessionMsgs}${C.reset}`,
288
+ `${C.cyan}Tokens session :${C.reset} ${C.whiteBright}${sessionTokens.toLocaleString()}${C.reset}`,
289
+ `${C.cyan}Modèle :${C.reset} ${C.magentaBright}${currentModel}${C.reset}`,
290
+ `${C.cyan}Utilisateur :${C.reset} ${C.whiteBright}${currentUser?.email || "Anonyme"}${C.reset}`,
291
+ `${C.cyan}Plan :${C.reset} ${C.greenBright}${PLANS[currentUser?.plan_id || "free"].name}${C.reset}`,
154
292
  ], C.yellowBright);
155
293
  console.log();
156
294
  }
157
295
 
158
- // ── Sélecteur de modèle ────────────────────────────────────
296
+ // ── Sélecteur modèle ─────────────────────────────────────────
159
297
  async function selectModel(rl) {
160
298
  const models = [
161
- { id: "claude-opus-4-6", label: "Claude Opus 4.6", desc: "Le plus puissant — tâches complexes" },
162
- { id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", desc: "Équilibre puissance/vitesse (recommandé)" },
163
- { id: "claude-haiku-4-5", label: "Claude Haiku 4.5", desc: "Ultra rapide — tâches simples" },
299
+ { id: "claude-opus-4-6", label: "Claude Opus 4.6", desc: "Le plus puissant" },
300
+ { id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", desc: "Recommandé " },
301
+ { id: "claude-haiku-4-5", label: "Claude Haiku 4.5", desc: "Ultra rapide" },
164
302
  ];
165
-
166
303
  console.log();
167
304
  box([
168
- `${C.yellowBright + C.bold}🤖 Choisir un modèle Claude${C.reset}`,
305
+ `${C.yellowBright + C.bold}🤖 Choisir un modèle${C.reset}`,
169
306
  "",
170
307
  ...models.map((m, i) => `${C.cyanBright}${i + 1}.${C.reset} ${C.whiteBright}${m.label}${C.reset} ${C.gray}${m.desc}${C.reset}${m.id === currentModel ? ` ${C.greenBright}← actuel${C.reset}` : ""}`),
171
308
  ], C.yellowBright);
172
-
173
309
  return new Promise((resolve) => {
174
310
  process.stdout.write(`\n${C.yellow}Choix (1-3) : ${C.reset}`);
175
311
  rl.once("line", (input) => {
176
312
  const idx = parseInt(input.trim()) - 1;
177
313
  if (idx >= 0 && idx < models.length) {
178
314
  currentModel = models[idx].id;
179
- console.log(`\n${C.greenBright}✅ Modèle changé → ${C.bold}${models[idx].label}${C.reset}\n`);
180
- } else {
181
- console.log(`\n${C.gray}Annulé.${C.reset}\n`);
315
+ println(C.greenBright, `\n ✅ Modèle → ${models[idx].label}\n`);
182
316
  }
183
317
  resolve();
184
318
  });
185
319
  });
186
320
  }
187
321
 
188
- // ── Historique sessions ────────────────────────────────────
322
+ // ── Historique ────────────────────────────────────────────────
189
323
  function saveHistory(history) {
190
324
  try {
191
- const data = { date: new Date().toISOString(), model: currentModel, stats: sessionStats, messages: history.slice(-20) };
325
+ const data = { date: new Date().toISOString(), model: currentModel, tokens: sessionTokens, messages: history.slice(-10) };
192
326
  let all = [];
193
327
  if (fs.existsSync(HISTORY_FILE)) all = JSON.parse(fs.readFileSync(HISTORY_FILE, "utf8"));
194
328
  all.unshift(data);
@@ -200,115 +334,91 @@ function saveHistory(history) {
200
334
  function showHistory() {
201
335
  console.log();
202
336
  try {
203
- if (!fs.existsSync(HISTORY_FILE)) { console.log(`${C.gray} Aucun historique disponible.${C.reset}\n`); return; }
337
+ if (!fs.existsSync(HISTORY_FILE)) { println(C.gray, " Aucun historique."); return; }
204
338
  const all = JSON.parse(fs.readFileSync(HISTORY_FILE, "utf8"));
205
339
  box([
206
- `${C.yellowBright + C.bold}📜 Historique des sessions${C.reset}`,
340
+ `${C.yellowBright + C.bold}📜 Historique${C.reset}`,
207
341
  "",
208
342
  ...all.map((s, i) => {
209
343
  const d = new Date(s.date).toLocaleString("fr-FR");
210
- return `${C.cyanBright}${i + 1}.${C.reset} ${C.whiteBright}${d}${C.reset} ${C.gray}${s.model} ${s.stats.messages} msgs — ${s.stats.filesCreated} fichiers${C.reset}`;
344
+ return `${C.cyanBright}${i + 1}.${C.reset} ${C.whiteBright}${d}${C.reset} ${C.gray}${s.tokens?.toLocaleString() || 0} tokens${C.reset}`;
211
345
  }),
212
346
  ], C.blueBright);
213
- } catch { console.log(`${C.gray} Erreur lecture historique.${C.reset}`); }
347
+ } catch {}
214
348
  console.log();
215
349
  }
216
350
 
217
- // ── Outils ─────────────────────────────────────────────────
351
+ // ── Outils ───────────────────────────────────────────────────
218
352
  const tools = [
219
- { name: "read_file", description: "Lire le contenu d'un fichier", input_schema: { type: "object", properties: { path: { type: "string", description: "Chemin du fichier" } }, required: ["path"] } },
220
- { name: "write_file", description: "Créer ou écrire dans un fichier", input_schema: { type: "object", properties: { path: { type: "string" }, content: { type: "string" } }, required: ["path", "content"] } },
221
- { name: "list_files", description: "Lister les fichiers d'un répertoire", input_schema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] } },
222
- { name: "run_command", description: "Exécuter une commande shell", input_schema: { type: "object", properties: { command: { type: "string" } }, required: ["command"] } },
223
- { name: "delete_file", description: "Supprimer un fichier", input_schema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] } },
224
- { name: "create_directory", description: "Créer un dossier", input_schema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] } },
353
+ { name: "read_file", description: "Lire un fichier", input_schema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] } },
354
+ { name: "write_file", description: "Écrire un fichier", input_schema: { type: "object", properties: { path: { type: "string" }, content: { type: "string" } }, required: ["path", "content"] } },
355
+ { name: "list_files", description: "Lister un répertoire", input_schema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] } },
356
+ { name: "run_command", description: "Exécuter une commande", input_schema: { type: "object", properties: { command: { type: "string" } }, required: ["command"] } },
357
+ { name: "delete_file", description: "Supprimer un fichier", input_schema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] } },
358
+ { name: "create_directory", description: "Créer un dossier", input_schema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] } },
225
359
  ];
226
360
 
227
361
  const toolIcons = { read_file: "📄", write_file: "✏️ ", list_files: "📁", run_command: "⚡", delete_file: "🗑️ ", create_directory: "📂" };
228
362
 
229
363
  async function executeTool(name, input) {
230
364
  try {
231
- switch (name) {
232
- case "read_file": {
233
- const content = fs.readFileSync(input.path, "utf8");
234
- sessionStats.filesRead++;
235
- console.log(` ${C.blueBright}└─${C.reset} ${C.gray}${input.path} (${content.length} chars)${C.reset}`);
236
- return content;
237
- }
238
- case "write_file": {
239
- const dir = path.dirname(input.path);
240
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
241
- fs.writeFileSync(input.path, input.content, "utf8");
242
- sessionStats.filesCreated++;
243
- console.log(` ${C.greenBright}└─${C.reset} ${C.gray}${input.path}${C.reset}`);
244
- return `Fichier écrit: ${input.path}`;
245
- }
246
- case "list_files": {
247
- const files = fs.readdirSync(input.path);
248
- console.log(` ${C.blueBright}└─${C.reset} ${C.gray}${files.length} entrées${C.reset}`);
249
- return files.map(f => {
250
- const full = path.join(input.path, f);
251
- const stat = fs.statSync(full);
252
- return `${stat.isDirectory() ? "[DIR] " : " "}${f}`;
253
- }).join("\n");
254
- }
255
- case "run_command": {
256
- sessionStats.commandsRun++;
257
- console.log(` ${C.yellowBright}└─${C.reset} ${C.gray}${input.command}${C.reset}`);
258
- const { stdout, stderr } = await execAsync(input.command, { timeout: 30000, cwd: process.cwd() });
259
- return (stdout + (stderr ? "\nSTDERR: " + stderr : "")).trim() || "(aucune sortie)";
260
- }
261
- case "delete_file": {
262
- fs.unlinkSync(input.path);
263
- console.log(` ${C.redBright}└─${C.reset} ${C.gray}${input.path}${C.reset}`);
264
- return `Supprimé: ${input.path}`;
265
- }
266
- case "create_directory": {
267
- fs.mkdirSync(input.path, { recursive: true });
268
- console.log(` ${C.greenBright}└─${C.reset} ${C.gray}${input.path}${C.reset}`);
269
- return `Dossier créé: ${input.path}`;
270
- }
271
- default: return `Outil inconnu: ${name}`;
272
- }
365
+ if (name === "read_file") { const c = fs.readFileSync(input.path, "utf8"); println(C.blueBright, ` └─ ${C.gray}${input.path}${C.reset}`); return c; }
366
+ if (name === "write_file") { fs.mkdirSync(path.dirname(input.path), { recursive: true }); fs.writeFileSync(input.path, input.content, "utf8"); println(C.greenBright, ` └─ ${C.gray}${input.path}${C.reset}`); return "OK"; }
367
+ if (name === "list_files") { return fs.readdirSync(input.path).map(f => { const s = fs.statSync(path.join(input.path, f)); return `${s.isDirectory() ? "[DIR] " : " "}${f}`; }).join("\n"); }
368
+ if (name === "run_command") { println(C.yellowBright, ` └─ ${C.gray}${input.command}${C.reset}`); const { stdout, stderr } = await execAsync(input.command, { timeout: 30000, cwd: process.cwd() }); return (stdout + (stderr ? "\n" + stderr : "")).trim() || "(aucune sortie)"; }
369
+ if (name === "delete_file") { fs.unlinkSync(input.path); return `Supprimé: ${input.path}`; }
370
+ if (name === "create_directory") { fs.mkdirSync(input.path, { recursive: true }); return `Créé: ${input.path}`; }
371
+ return "Outil inconnu";
273
372
  } catch (e) { return `ERREUR: ${e.message}`; }
274
373
  }
275
374
 
276
- // ── Animation de chargement ────────────────────────────────
375
+ // ── Spinner ───────────────────────────────────────────────────
277
376
  function startSpinner(text) {
278
- const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
377
+ const frames = ["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"];
279
378
  let i = 0;
280
- const timer = setInterval(() => {
281
- process.stdout.write(`\r ${C.cyanBright}${frames[i % frames.length]}${C.reset} ${C.gray}${text}${C.reset} `);
282
- i++;
283
- }, 80);
284
- return () => { clearInterval(timer); process.stdout.write("\r" + " ".repeat(60) + "\r"); };
379
+ const t = setInterval(() => { process.stdout.write(`\r ${C.cyanBright}${frames[i++ % frames.length]}${C.reset} ${C.gray}${text}${C.reset} `); }, 80);
380
+ return () => { clearInterval(t); process.stdout.write("\r" + " ".repeat(60) + "\r"); };
285
381
  }
286
382
 
287
- // ── Agent principal ────────────────────────────────────────
383
+ // ── Agent ─────────────────────────────────────────────────────
384
+ const client = new Anthropic({ apiKey: ANTHROPIC_KEY });
385
+
288
386
  async function runAgent(userMessage, history) {
289
- sessionStats.messages++;
290
- history.push({ role: "user", content: userMessage });
387
+ // Vérif quota
388
+ const quota = await checkQuota(currentUser);
389
+ if (!quota.ok) {
390
+ console.log();
391
+ box([
392
+ `${C.redBright + C.bold}⛔ Quota épuisé${C.reset}`,
393
+ "",
394
+ `${C.gray}Vous avez atteint la limite de votre plan ${PLANS[currentUser?.plan_id || "free"].name}.${C.reset}`,
395
+ `${C.yellowBright}Tapez /upgrade pour voir les plans disponibles.${C.reset}`,
396
+ ], C.redBright);
397
+ return;
398
+ }
291
399
 
400
+ sessionMsgs++;
401
+ history.push({ role: "user", content: userMessage });
292
402
  const stopSpinner = startSpinner("BIBEST CODE réfléchit...");
293
403
 
294
404
  try {
295
405
  while (true) {
296
406
  const response = await client.messages.create({
297
- model: currentModel,
298
- max_tokens: 8096,
407
+ model: currentModel, max_tokens: 8096,
299
408
  system: `Tu es BIBEST CODE, un agent de développement expert créé par BIBI ONDOUA.
300
- Tu peux lire/écrire des fichiers, exécuter des commandes shell, créer des projets complets.
301
- Répertoire de travail actuel: ${process.cwd()}
302
- Réponds toujours en français sauf demande contraire.
303
- Sois efficace, précis et professionnel. Utilise les outils pour accomplir les tâches concrètement.
304
- Après chaque action, donne un résumé clair de ce qui a été fait.`,
305
- tools,
306
- messages: history,
409
+ Tu peux lire/écrire des fichiers, exécuter des commandes, créer des projets complets.
410
+ Répertoire: ${process.cwd()}. Réponds en français. Sois efficace et précis.`,
411
+ tools, messages: history,
307
412
  });
308
413
 
309
414
  stopSpinner();
310
- history.push({ role: "assistant", content: response.content });
311
415
 
416
+ // Comptage tokens
417
+ const tokensUsed = response.usage?.input_tokens + response.usage?.output_tokens || 0;
418
+ sessionTokens += tokensUsed;
419
+ if (currentUser) await logUsage(currentUser.id, tokensUsed, currentModel);
420
+
421
+ history.push({ role: "assistant", content: response.content });
312
422
  let hasToolUse = false;
313
423
  const toolResults = [];
314
424
 
@@ -316,66 +426,60 @@ Après chaque action, donne un résumé clair de ce qui a été fait.`,
316
426
  if (block.type === "text" && block.text) {
317
427
  console.log();
318
428
  console.log(line("─", C.gray));
319
- const lines = block.text.split("\n");
320
- lines.forEach(l => {
321
- if (l.startsWith("# ")) console.log(` ${C.yellowBright + C.bold}${l.substring(2)}${C.reset}`);
322
- else if (l.startsWith("## ")) console.log(` ${C.cyanBright + C.bold}${l.substring(3)}${C.reset}`);
323
- else if (l.startsWith("- ") || l.startsWith("")) console.log(` ${C.cyan}▸${C.reset} ${C.white}${l.substring(2)}${C.reset}`);
324
- else if (l.startsWith("```")) console.log(` ${C.gray}${l}${C.reset}`);
325
- else if (l.trim() === "") console.log();
326
- else console.log(` ${C.whiteBright}${l}${C.reset}`);
429
+ block.text.split("\n").forEach(l => {
430
+ if (l.startsWith("# ")) println(C.yellowBright + C.bold, " " + l.substring(2));
431
+ else if (l.startsWith("## ")) println(C.cyanBright + C.bold, " " + l.substring(3));
432
+ else if (l.startsWith("- ") || l.startsWith("• ")) println(C.reset, ` ${C.cyan}▸${C.reset} ${C.whiteBright}${l.substring(2)}${C.reset}`);
433
+ else if (l.trim() === "") console.log();
434
+ else println(C.whiteBright, " " + l);
327
435
  });
328
436
  console.log(line("─", C.gray));
329
437
  }
330
438
  if (block.type === "tool_use") {
331
439
  hasToolUse = true;
332
- const icon = toolIcons[block.name] || "🔧";
333
- console.log(`\n ${icon} ${C.magentaBright + C.bold}${block.name}${C.reset} ${C.gray}en cours...${C.reset}`);
440
+ println(C.magentaBright, `\n ${toolIcons[block.name] || "🔧"} ${C.bold}${block.name}${C.reset}`);
334
441
  const result = await executeTool(block.name, block.input);
335
442
  toolResults.push({ type: "tool_result", tool_use_id: block.id, content: result });
336
443
  }
337
444
  }
338
445
 
339
- if (hasToolUse) {
340
- history.push({ role: "user", content: toolResults });
341
- const stop2 = startSpinner("Traitement des résultats...");
342
- setTimeout(stop2, 100);
343
- continue;
344
- }
446
+ if (hasToolUse) { history.push({ role: "user", content: toolResults }); continue; }
345
447
  break;
346
448
  }
347
- } catch (e) {
348
- stopSpinner();
349
- throw e;
350
- }
449
+ } catch (e) { stopSpinner(); throw e; }
351
450
  }
352
451
 
353
- // ── Prompt décoré ──────────────────────────────────────────
452
+ // ── Prompt ────────────────────────────────────────────────────
354
453
  function showPrompt() {
454
+ const planColor = { free: C.gray, starter: C.cyanBright, pro: C.blueBright, business: C.magentaBright, enterprise: C.yellowBright, founder: C.greenBright };
455
+ const planId = currentUser?.plan_id || "free";
456
+ const pc = planColor[planId] || C.gray;
457
+ const planBadge = `${pc}[${PLANS[planId].name}]${C.reset}`;
355
458
  const cwd = path.basename(process.cwd());
356
- process.stdout.write(
357
- `\n ${C.gray}[${C.cyanBright}${cwd}${C.gray}]${C.reset} ${C.yellowBright + C.bold}❯${C.reset} `
358
- );
459
+ process.stdout.write(`\n ${C.gray}[${C.cyanBright}${cwd}${C.gray}]${C.reset} ${planBadge} ${C.yellowBright + C.bold}❯${C.reset} `);
359
460
  }
360
461
 
361
- // ── Main ───────────────────────────────────────────────────
462
+ // ── Main ──────────────────────────────────────────────────────
362
463
  async function main() {
363
464
  showHeader();
364
465
 
365
- if (!process.env.ANTHROPIC_API_KEY) {
366
- box([
367
- `${C.redBright + C.bold}❌ Clé API manquante${C.reset}`,
368
- "",
369
- `Lance cette commande d'abord :`,
370
- `${C.yellowBright}$env:ANTHROPIC_API_KEY="sk-ant-..."${C.reset}`,
371
- ], C.redBright);
466
+ if (!ANTHROPIC_KEY) {
467
+ box([`${C.redBright}❌ ANTHROPIC_API_KEY manquante${C.reset}`, "", `${C.yellow}$env:ANTHROPIC_API_KEY="sk-ant-..."${C.reset}`], C.redBright);
372
468
  process.exit(1);
373
469
  }
374
470
 
375
- showStatus();
471
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
472
+
473
+ // Login
474
+ if (SUPABASE_ANON_KEY) {
475
+ currentUser = await loginFlow(rl);
476
+ } else {
477
+ println(C.yellow, " ⚠️ Mode hors-ligne (SUPABASE_ANON_KEY non configurée)\n");
478
+ }
479
+
480
+ await showStatus();
376
481
  showHelp();
377
482
 
378
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
379
483
  const history = [];
380
484
 
381
485
  const prompt = () => {
@@ -384,49 +488,25 @@ async function main() {
384
488
  const t = input.trim();
385
489
  if (!t) return prompt();
386
490
 
387
- // Commandes internes
388
491
  if (t === "/exit" || t === "exit") {
389
492
  saveHistory(history);
390
493
  console.log();
391
- box([
392
- `${C.cyanBright + C.bold}👋 Merci d'avoir utilisé BIBEST CODE${C.reset}`,
393
- "",
394
- `${C.gray}Session sauvegardée — À bientôt !${C.reset}`,
395
- `${C.gray}© BIBI ONDOUA — Tous droits réservés${C.reset}`,
396
- ], C.cyanBright);
397
- console.log();
494
+ box([`${C.cyanBright + C.bold}👋 Au revoir !${C.reset}`, "", `${C.gray}© BIBI ONDOUA — Tous droits réservés${C.reset}`], C.cyanBright);
398
495
  process.exit(0);
399
496
  }
400
- if (t === "/help") { showHelp(); return prompt(); }
401
- if (t === "/clear") { showHeader(); showStatus(); return prompt(); }
402
- if (t === "/stats") { showStats(); return prompt(); }
497
+ if (t === "/help") { showHelp(); return prompt(); }
498
+ if (t === "/clear") { showHeader(); await showStatus(); return prompt(); }
499
+ if (t === "/stats") { showStats(); return prompt(); }
500
+ if (t === "/quota") { await showQuota(); return prompt(); }
501
+ if (t === "/upgrade") { showUpgrade(); return prompt(); }
403
502
  if (t === "/history") { showHistory(); return prompt(); }
404
- if (t === "/reset") { history.length = 0; console.log(`\n ${C.greenBright}✅ Conversation réinitialisée${C.reset}\n`); return prompt(); }
405
- if (t === "/ls") {
406
- const files = fs.readdirSync(process.cwd());
407
- console.log();
408
- files.forEach(f => {
409
- const stat = fs.statSync(f);
410
- console.log(` ${stat.isDirectory() ? C.blueBright + "📁" : C.gray + "📄"} ${C.whiteBright}${f}${C.reset}`);
411
- });
412
- console.log();
413
- return prompt();
414
- }
415
- if (t.startsWith("/cd ")) {
416
- const dir = t.substring(4).trim();
417
- try { process.chdir(dir); console.log(`\n ${C.greenBright}✅ Répertoire: ${process.cwd()}${C.reset}\n`); }
418
- catch { console.log(`\n ${C.redBright}❌ Dossier introuvable: ${dir}${C.reset}\n`); }
419
- return prompt();
420
- }
503
+ if (t === "/reset") { history.length = 0; println(C.greenBright, "\n ✅ Conversation réinitialisée\n"); return prompt(); }
504
+ if (t === "/ls") { fs.readdirSync(process.cwd()).forEach(f => { const s = fs.statSync(f); println(s.isDirectory() ? C.blueBright : C.gray, ` ${s.isDirectory() ? "📁" : "📄"} ${C.whiteBright}${f}${C.reset}`); }); return prompt(); }
505
+ if (t.startsWith("/cd ")) { try { process.chdir(t.substring(4).trim()); println(C.greenBright, `\n ✅ ${process.cwd()}\n`); } catch { println(C.redBright, "\n ❌ Dossier introuvable\n"); } return prompt(); }
421
506
  if (t.startsWith("/model")) { await selectModel(rl); return prompt(); }
422
507
 
423
- // Message pour l'agent
424
- try {
425
- await runAgent(t, history);
426
- } catch (e) {
427
- console.log();
428
- box([`${C.redBright}❌ Erreur: ${e.message}${C.reset}`], C.redBright);
429
- }
508
+ try { await runAgent(t, history); }
509
+ catch (e) { box([`${C.redBright}❌ ${e.message}${C.reset}`], C.redBright); }
430
510
  prompt();
431
511
  });
432
512
  };
package/package.json CHANGED
@@ -1,17 +1,26 @@
1
1
  {
2
2
  "name": "bibest-code",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Agent CLI de developpement propulse par Claude AI",
5
5
  "main": "bibest-code.js",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "bibest-code": "./bibest-code.js"
9
9
  },
10
- "keywords": ["ai", "cli", "claude", "agent", "developer-tools"],
10
+ "keywords": [
11
+ "ai",
12
+ "cli",
13
+ "claude",
14
+ "agent",
15
+ "developer-tools"
16
+ ],
11
17
  "author": "BIBI ONDOUA <b.ondoua00@gmail.com>",
12
18
  "license": "ISC",
13
19
  "dependencies": {
14
20
  "@anthropic-ai/sdk": "^0.39.0",
15
- "readline-sync": "^1.4.10"
21
+ "@supabase/supabase-js": "^2.108.2",
22
+ "readline-sync": "^1.4.10",
23
+ "stripe": "^22.3.0",
24
+ "ws": "^8.21.0"
16
25
  }
17
- }
26
+ }