@chispear/vibekit 0.4.0 → 0.6.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/README.md +195 -8
- package/dist/agent.js +183 -8
- package/dist/brief.js +276 -0
- package/dist/cli.js +361 -23
- package/dist/config.js +47 -1
- package/dist/mcp.js +389 -0
- package/dist/prompt.js +4 -4
- package/dist/router.js +186 -12
- package/dist/sesi.js +77 -0
- package/dist/state.js +104 -0
- package/examples/vibekit.config.json +1 -1
- package/package.json +1 -1
package/dist/sesi.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function folderSesi(dirConfig) {
|
|
4
|
+
return path.join(dirConfig, "sesi");
|
|
5
|
+
}
|
|
6
|
+
function lokasiSesi(dirSesi, id) {
|
|
7
|
+
return path.join(dirSesi, `${id}.json`);
|
|
8
|
+
}
|
|
9
|
+
function lokasiPenanda(dirSesi) {
|
|
10
|
+
return path.join(dirSesi, "terakhir.json");
|
|
11
|
+
}
|
|
12
|
+
/** Buat id sesi baru dari waktu saat ini, mis. "sesi-2026-10-26T00-10-22". */
|
|
13
|
+
export function buatIdSesi(sekarang = new Date()) {
|
|
14
|
+
const dasar = "sesi-" + sekarang.toISOString().replace(/[:.]/g, "-").replace(/-\d+Z$/, "");
|
|
15
|
+
return dasar;
|
|
16
|
+
}
|
|
17
|
+
/** Simpan sesi ke disk (atomik via file sementara) dan perbarui penanda sesi terakhir. */
|
|
18
|
+
export function simpanSesi(dirSesi, sesi) {
|
|
19
|
+
fs.mkdirSync(dirSesi, { recursive: true });
|
|
20
|
+
const lokasi = lokasiSesi(dirSesi, sesi.id);
|
|
21
|
+
const sementara = lokasi + ".tmp";
|
|
22
|
+
fs.writeFileSync(sementara, JSON.stringify(sesi, null, 2) + "\n", "utf8");
|
|
23
|
+
fs.renameSync(sementara, lokasi);
|
|
24
|
+
fs.writeFileSync(lokasiPenanda(dirSesi), JSON.stringify({ id: sesi.id, diperbarui: sesi.diperbarui }, null, 2) + "\n", "utf8");
|
|
25
|
+
}
|
|
26
|
+
function bacaSesiMentah(lokasi) {
|
|
27
|
+
try {
|
|
28
|
+
const data = JSON.parse(fs.readFileSync(lokasi, "utf8"));
|
|
29
|
+
if (data && typeof data.id === "string" && Array.isArray(data.pesan))
|
|
30
|
+
return data;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
/* file rusak/tidak ada → dianggap tidak ada sesi */
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/** Muat sesi yang ditunjuk penanda terakhir; null bila belum ada / rusak. */
|
|
38
|
+
export function muatSesiTerakhir(dirSesi) {
|
|
39
|
+
try {
|
|
40
|
+
const penanda = JSON.parse(fs.readFileSync(lokasiPenanda(dirSesi), "utf8"));
|
|
41
|
+
if (typeof penanda.id !== "string")
|
|
42
|
+
return null;
|
|
43
|
+
return bacaSesiMentah(lokasiSesi(dirSesi, penanda.id));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Daftar semua sesi tersimpan, terbaru (menurut `diperbarui`) lebih dulu. */
|
|
50
|
+
export function daftarSesi(dirSesi) {
|
|
51
|
+
let namaFile;
|
|
52
|
+
try {
|
|
53
|
+
namaFile = fs.readdirSync(dirSesi);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
const hasil = [];
|
|
59
|
+
for (const nama of namaFile) {
|
|
60
|
+
if (!nama.endsWith(".json") || nama === "terakhir.json")
|
|
61
|
+
continue;
|
|
62
|
+
const sesi = bacaSesiMentah(path.join(dirSesi, nama));
|
|
63
|
+
if (!sesi)
|
|
64
|
+
continue;
|
|
65
|
+
const pesanUserPertama = sesi.pesan.find((m) => m.role === "user" && typeof m.content === "string" && m.content.trim() !== "");
|
|
66
|
+
const mentah = (pesanUserPertama?.content ?? "").replace(/\s+/g, " ").trim();
|
|
67
|
+
hasil.push({
|
|
68
|
+
id: sesi.id,
|
|
69
|
+
dibuat: sesi.dibuat,
|
|
70
|
+
diperbarui: sesi.diperbarui,
|
|
71
|
+
jumlahPesan: sesi.pesan.length,
|
|
72
|
+
cuplikan: mentah.length > 60 ? mentah.slice(0, 60) + "…" : mentah,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
hasil.sort((a, b) => (a.diperbarui < b.diperbarui ? 1 : a.diperbarui > b.diperbarui ? -1 : 0));
|
|
76
|
+
return hasil;
|
|
77
|
+
}
|
package/dist/state.js
CHANGED
|
@@ -45,11 +45,115 @@ export function statePenyedia(state, nama, sekarang = new Date()) {
|
|
|
45
45
|
}
|
|
46
46
|
return entri;
|
|
47
47
|
}
|
|
48
|
+
/** Jumlah hari riwayat pemakaian/hemat yang dipertahankan di state. */
|
|
49
|
+
const HARI_RIWAYAT = 30;
|
|
50
|
+
function pangkasRiwayatTanggal(peta, sekarang) {
|
|
51
|
+
const tanggal = Object.keys(peta).sort();
|
|
52
|
+
const batas = tanggalLokal(new Date(sekarang.getTime() - (HARI_RIWAYAT - 1) * 86_400_000));
|
|
53
|
+
for (const t of tanggal) {
|
|
54
|
+
if (t < batas)
|
|
55
|
+
delete peta[t];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Catat pemakaian token satu penyedia ke peta harian (dipakai laporan `vibekit hemat`). */
|
|
59
|
+
export function catatPemakaianHarian(state, nama, token, sekarang = new Date()) {
|
|
60
|
+
if (token <= 0)
|
|
61
|
+
return;
|
|
62
|
+
if (!state.pemakaianHarian)
|
|
63
|
+
state.pemakaianHarian = {};
|
|
64
|
+
const tgl = tanggalLokal(sekarang);
|
|
65
|
+
const perProvider = (state.pemakaianHarian[tgl] ??= {});
|
|
66
|
+
perProvider[nama] = (perProvider[nama] ?? 0) + token;
|
|
67
|
+
pangkasRiwayatTanggal(state.pemakaianHarian, sekarang);
|
|
68
|
+
}
|
|
69
|
+
/** Tambahkan token yang dibuang kompaksi riwayat ke penghitung hemat hari ini, lalu simpan state. */
|
|
70
|
+
export function catatHematKompaksi(lokasi, token, sekarang = new Date()) {
|
|
71
|
+
if (token <= 0)
|
|
72
|
+
return;
|
|
73
|
+
const state = muatState(lokasi);
|
|
74
|
+
if (!state.hematKompaksi)
|
|
75
|
+
state.hematKompaksi = {};
|
|
76
|
+
const tgl = tanggalLokal(sekarang);
|
|
77
|
+
state.hematKompaksi[tgl] = (state.hematKompaksi[tgl] ?? 0) + token;
|
|
78
|
+
pangkasRiwayatTanggal(state.hematKompaksi, sekarang);
|
|
79
|
+
simpanState(lokasi, state);
|
|
80
|
+
}
|
|
81
|
+
/** Daftar `n` tanggal lokal terakhir, terbaru lebih dulu (hari ini pertama). */
|
|
82
|
+
export function tanggalTerakhir(n, sekarang = new Date()) {
|
|
83
|
+
const hasil = [];
|
|
84
|
+
for (let i = 0; i < n; i++) {
|
|
85
|
+
hasil.push(tanggalLokal(new Date(sekarang.getTime() - i * 86_400_000)));
|
|
86
|
+
}
|
|
87
|
+
return hasil;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Pemakaian token pada satu tanggal, per penyedia. Sumber utama adalah
|
|
91
|
+
* `pemakaianHarian`; untuk tanggal yang belum tercatat di sana (state lama),
|
|
92
|
+
* dipakai totalToken harian dari entri `providers` sebagai cadangan.
|
|
93
|
+
*/
|
|
94
|
+
export function pemakaianPada(state, tanggal) {
|
|
95
|
+
const tercatat = state.pemakaianHarian?.[tanggal];
|
|
96
|
+
if (tercatat)
|
|
97
|
+
return { ...tercatat };
|
|
98
|
+
const cadangan = {};
|
|
99
|
+
for (const [nama, entri] of Object.entries(state.providers)) {
|
|
100
|
+
if (entri.tanggal === tanggal && entri.totalToken > 0)
|
|
101
|
+
cadangan[nama] = entri.totalToken;
|
|
102
|
+
}
|
|
103
|
+
return cadangan;
|
|
104
|
+
}
|
|
48
105
|
export function sedangCooldown(entri, sekarang = new Date()) {
|
|
49
106
|
if (!entri.cooldownUntil)
|
|
50
107
|
return false;
|
|
51
108
|
return new Date(entri.cooldownUntil).getTime() > sekarang.getTime();
|
|
52
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Susun teks laporan `vibekit hemat` dari state: pemakaian hari ini dan 7
|
|
112
|
+
* hari terakhir per penyedia, plus token yang DIUKUR terbuang oleh kompaksi
|
|
113
|
+
* riwayat. Angka penghematan lain (gaya jawaban, konteks terseleksi)
|
|
114
|
+
* sengaja tidak diangkakan — dinyatakan sebagai catatan kejujuran.
|
|
115
|
+
*/
|
|
116
|
+
export function bangunLaporanHemat(state, sekarang = new Date()) {
|
|
117
|
+
const fmt = (n) => n.toLocaleString("id-ID");
|
|
118
|
+
const hari = tanggalTerakhir(7, sekarang);
|
|
119
|
+
const hariIni = hari[0];
|
|
120
|
+
const baris = ["Laporan pemakaian & penghematan token", ""];
|
|
121
|
+
const pakaiHariIni = pemakaianPada(state, hariIni);
|
|
122
|
+
const namaHariIni = Object.keys(pakaiHariIni).sort();
|
|
123
|
+
baris.push(`Hari ini (${hariIni}):`);
|
|
124
|
+
if (namaHariIni.length === 0) {
|
|
125
|
+
baris.push(" (belum ada pemakaian tercatat hari ini)");
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
let total = 0;
|
|
129
|
+
for (const nama of namaHariIni) {
|
|
130
|
+
baris.push(` ${nama}: ${fmt(pakaiHariIni[nama])} token`);
|
|
131
|
+
total += pakaiHariIni[nama];
|
|
132
|
+
}
|
|
133
|
+
baris.push(` Total hari ini: ${fmt(total)} token`);
|
|
134
|
+
}
|
|
135
|
+
baris.push("", "7 hari terakhir:");
|
|
136
|
+
const gabungan = new Map();
|
|
137
|
+
let total7 = 0;
|
|
138
|
+
for (const tgl of hari) {
|
|
139
|
+
const pakai = pemakaianPada(state, tgl);
|
|
140
|
+
const totalHari = Object.values(pakai).reduce((a, b) => a + b, 0);
|
|
141
|
+
baris.push(` ${tgl}: ${fmt(totalHari)} token`);
|
|
142
|
+
total7 += totalHari;
|
|
143
|
+
for (const [nama, token] of Object.entries(pakai)) {
|
|
144
|
+
gabungan.set(nama, (gabungan.get(nama) ?? 0) + token);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (const nama of [...gabungan.keys()].sort()) {
|
|
148
|
+
baris.push(` ${nama} (7 hari): ${fmt(gabungan.get(nama))} token`);
|
|
149
|
+
}
|
|
150
|
+
baris.push(` Total 7 hari terakhir: ${fmt(total7)} token`);
|
|
151
|
+
const hemat = state.hematKompaksi ?? {};
|
|
152
|
+
const hematHariIni = hemat[hariIni] ?? 0;
|
|
153
|
+
const hematTotal = Object.values(hemat).reduce((a, b) => a + b, 0);
|
|
154
|
+
baris.push("", "Token yang dihemat kompaksi riwayat chat (terukur dari isi yang dibuang):", ` Hari ini: ${fmt(hematHariIni)} token`, ` Total tercatat: ${fmt(hematTotal)} token`, "", "Catatan: penghematan dari gaya jawaban ringkas dan konteks terseleksi bersifat desain (mencegah token terpakai sejak awal) dan tidak dihitung sebagai angka di laporan ini.");
|
|
155
|
+
return baris.join("\n");
|
|
156
|
+
}
|
|
53
157
|
export function formatJam(iso) {
|
|
54
158
|
const d = new Date(iso);
|
|
55
159
|
const hh = String(d.getHours()).padStart(2, "0");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"gaya":{"jawaban":"poin-penting","maksTokenJawaban":800},"providers":[{"anggaranHarian":500000,"apiKey":"","baseUrl":"https://api.openai.com/v1","jedaCooldownMenit":15,"model":"gpt-4o-mini","nama":"gpt"},{"apiKey":"env:GEMINI_API_KEY","baseUrl":"https://generativelanguage.googleapis.com/v1beta/openai/","jedaCooldownMenit":15,"model":"gemini-3.8-flash","nama":"gemini"},{"apiKey":"env:DEEPSEEK_API_KEY","baseUrl":"https://api.deepseek.com/v1","extraBody":{},"jedaCooldownMenit":15,"model":"deepseek-chat","nama":"deepseek"},{"apiKey":"env:OPENROUTER_API_KEY","baseUrl":"https://openrouter.ai/api/v1","model":"openai/gpt-4o-mini","nama":"openrouter"},{"apiKey":"ollama","baseUrl":"http://localhost:11434/v1","model":"llama3.1","nama":"ollama-lokal"}]}
|
|
1
|
+
{"gaya":{"jawaban":"poin-penting","maksTokenJawaban":800},"mcpServers":[{"argumen":["-y","@modelcontextprotocol/server-filesystem","/path/ke/proyek"],"nama":"filesystem","perintah":"npx"},{"headers":{"X-Goog-Api-Key":"ISI_API_KEY_STITCH_MU"},"nama":"stitch","url":"https://stitch.googleapis.com/mcp"}],"providers":[{"anggaranHarian":500000,"apiKey":"","baseUrl":"https://api.openai.com/v1","jedaCooldownMenit":15,"model":"gpt-4o-mini","nama":"gpt"},{"apiKey":"env:GEMINI_API_KEY","baseUrl":"https://generativelanguage.googleapis.com/v1beta/openai/","jedaCooldownMenit":15,"model":"gemini-3.8-flash","nama":"gemini"},{"apiKey":"env:DEEPSEEK_API_KEY","baseUrl":"https://api.deepseek.com/v1","extraBody":{},"jedaCooldownMenit":15,"model":"deepseek-chat","nama":"deepseek"},{"apiKey":"env:OPENROUTER_API_KEY","baseUrl":"https://openrouter.ai/api/v1","model":"openai/gpt-4o-mini","nama":"openrouter"},{"apiKey":"ollama","baseUrl":"http://localhost:11434/v1","model":"llama3.1","nama":"ollama-lokal"}]}
|