biapp-mcp 0.0.0-stage → 0.3.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/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # Değişiklik günlüğü
2
+
3
+ ## 0.3.0
4
+
5
+ - E-posta pazarlama: 11 yeni araç (`eposta_kisiler`, `eposta_gruplari`, `eposta_kampanyalari`, `eposta_kisi_ekle`, `eposta_grup_ac`, `eposta_gruba_ekle`, `eposta_kampanya_hazirla`, `eposta_kampanya_guncelle`, `eposta_deneme_gonder`, `eposta_kampanya_gonder`, `eposta_kampanya_durdur`). 38 araç.
6
+ - Yeni API anahtarı kapsamları: `email:read`, `email:write`, `email:send` (gönderim ayrı kapsam).
7
+ - Menü şablonları: `haftalikMenu` (yapıştırılan gün/yemek metninden) ve `gunlukMenu` (yemek listesi otomatik kategorilenir, firma iletişim düğmeleri). Backend `POST /v1/email/campaigns` artık `templateType` + `content` kabul eder (WeeklyMenu, DailyMenu, Newsletter, Promotion); renk, https adresi, WhatsApp/Instagram/telefon alanları doğrulanır.
8
+ - `eposta_kampanya_gonder` her zaman önizleme + onay ister (`BIAPP_ONAY=dogrudan` dahil); alıcı sayısı değişirse gönderilmez.
9
+
10
+ ## 0.2.0
11
+
12
+ - Yeni: `teklif_pdf` (yerel dosyaya PDF yazar; `GET /v1/quotes/{id}/pdf`, `quotes:read`) ve `teklif_guncelle` (taslak teklif; `PUT /v1/quotes/{id}`). 27 araç.
13
+ - `teklif_hazirla`: `odemeKosullari`, `opsiyonGun`, `teslimGun` alanları; `gecerlilikGun` (son geçerlilik) ile `opsiyonGun` (belge yazısı) ayrımı açıklandı.
14
+ - `cari_ac`: `vergiDairesi`; aynı ad/telefon/VKN'li kayıt için önizleme uyarısı. `cari_ara` / `urun_ara` Türkçe harf (i/İ/ı/I) duyarsız (backend).
15
+ - Önizleme artık girdinin tüm dolu alanlarını gösterir (`fis_elle`, `z_raporu_elle`, `teklif_hazirla`, `cari_ac`); `gecerliSaat`; süre dolunca hata araç ve girdiyi söyler.
16
+ - `z_raporu_elle`: aynı gün Z varsa uyarı, ikinci kayıt için `ikinciKayitOnayi: true`.
17
+ - `teklif_paylas`: localhost adresinde uyarı. `kasa_hareketi_ekle`: cariye bağlı genel tahsilatın faturayı kapatmadığı uyarısı.
18
+ - Fatura PDF'i kapsam dışı (backend'de fatura PDF ucu yok).
19
+
20
+ ## 0.1.0
21
+
22
+ İlk sürüm.
23
+
24
+ - 25 Türkçe araç: 10 okuma, 15 yazma (kasa hareketi, cari, ürün/stok, teklif, fatura/tahsilat, ödeme linki, fiş, Z raporu).
25
+ - Yazma araçları önce önizleme döner; kayıt yalnız aynı girdi ve önizleme kodu (`islemKodu`) ile onaylanınca yapılır (`BIAPP_ONAY=sor`, varsayılan).
26
+ - Açılışta `GET /v1/me` ile anahtarın yetkisine ve kurulu uygulamalara göre araç süzme.
27
+ - Fiş ve Z raporu görseli yükleme: dosya baytlarından JPEG/PNG/WebP/PDF doğrulaması, 15 MB sınırı, özel IP reddi.
28
+ - Silme aracı yok.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BiApp
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,183 @@
1
- # Temporary Holding Version
1
+ # biapp-mcp
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [BiApp](https://biapp.com.tr)'i bir yapay zekâ asistanıyla sohbet ederek yönetin: "500 TL geldi Ahmet beyden", fiş fotoğrafı yükleme,
4
+ Z raporu girme, cari ve ürün açma, teklif hazırlama, fatura ve tahsilat.
5
+
6
+ Bu paket bir [MCP](https://modelcontextprotocol.io) sunucusudur (stdio). BiApp'e yalnızca `https://api.biapp.com.tr/api/v1` üzerinden,
7
+ sizin oluşturduğunuz **firma API anahtarıyla** konuşur. Hesap, KDV, bakiye, kredi ve mükerrer kontrolü BiApp'tedir; paket bunları yeniden
8
+ hesaplamaz.
9
+
10
+ *English summary: an MCP server (stdio) that lets an AI assistant manage a BiApp business account through the company API key: ledger
11
+ entries, receipts, Z reports, contacts, products, quotes, invoices. Turkish only. Writes require a preview-then-confirm step by default.*
12
+
13
+ ## Kurulum
14
+
15
+ 1. BiApp'te **Ayarlar › Entegrasyonlar › API Anahtarları**'ndan bir anahtar oluşturun (`bik_…`). Kapsamları aşağıdaki tablodan seçin;
16
+ anahtarı yalnızca işletme yöneticisi açabilir ve bir kez gösterilir.
17
+ 2. Asistanınıza ekleyin:
18
+
19
+ **Claude Code**
20
+
21
+ ```bash
22
+ claude mcp add biapp -e BIAPP_API_KEY=bik_... -- npx -y biapp-mcp
23
+ ```
24
+
25
+ **Claude Desktop** (`claude_desktop_config.json`)
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "biapp": {
31
+ "command": "npx",
32
+ "args": ["-y", "biapp-mcp"],
33
+ "env": { "BIAPP_API_KEY": "bik_..." }
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ **Paperclip / diğer MCP istemcileri** (`mcp.json`)
40
+
41
+ ```json
42
+ {
43
+ "mcpServers": {
44
+ "biapp": {
45
+ "command": "npx",
46
+ "args": ["-y", "biapp-mcp"],
47
+ "env": { "BIAPP_API_KEY": "bik_...", "BIAPP_ONAY": "dogrudan" }
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ Otomasyonda (insan onayı olmayan ajan) `BIAPP_ONAY=dogrudan` açıkça seçilir; aşağıya bakın. Node 20 veya üstü gerekir.
54
+
55
+ ## Ortam değişkenleri
56
+
57
+ | Değişken | Varsayılan | Anlamı |
58
+ |---|---|---|
59
+ | `BIAPP_API_KEY` | — (zorunlu) | `bik_…` anahtarı. Günlüğe, hataya ya da araç çıktısına yazılmaz. |
60
+ | `BIAPP_API_URL` | `https://api.biapp.com.tr/api` | API adresi. `http://` yalnızca `localhost` için kabul edilir. |
61
+ | `BIAPP_ONAY` | `sor` | `sor`: yazma araçları önce önizleme döner. `dogrudan`: onay adımı yok. |
62
+ | `BIAPP_DOSYA_KOKU` | — | Doluysa fiş/Z yükleme yalnız bu klasörün altındaki dosyaları okur. |
63
+ | `BIAPP_PDF_KLASORU` | `~/Downloads` | `teklif_pdf` dosyayı buraya yazar. |
64
+ | `BIAPP_ZAMAN_ASIMI_SN` | `30` | İstek zaman aşımı (yüklemede en az 90). |
65
+
66
+ ## Araçlar ve gereken kapsam
67
+
68
+ Araçlar açılışta anahtarınızın kapsamına ve işletmenizde kurulu uygulamalara göre süzülür; yetkisi olmayan araç asistana hiç görünmez.
69
+
70
+ | Araç | Ne yapar | Kapsam |
71
+ |---|---|---|
72
+ | `isletme_bilgisi` | Firma, para birimi, kurulu uygulamalar, anahtar yetkileri | — |
73
+ | `ozet` | Bugün/hafta/ay patron özeti | `reports:read` |
74
+ | `hesaplar` | Kasa ve banka hesapları | `ledger:read` |
75
+ | `kasa_hareketleri` | Gelir/gider listesi ve toplamı | `ledger:read` |
76
+ | `kasa_hareketi_ekle` | Gelir ya da gider yazar (cariye bağlanabilir) | `ledger:write` |
77
+ | `kasa_hareketi_duzelt` | Elle açılmış hareketi düzeltir | `ledger:write` |
78
+ | `cari_ara` / `cari_ac` | Cari arar (alacaklar dahil) / yeni cari açar | `contacts:read` / `contacts:write` |
79
+ | `urun_ara` / `urun_ac` | Ürün ve stok arar / yeni ürün açar | `products:read` / `products:write` |
80
+ | `stok_hareketi` | Stok girişi, çıkışı, sayımı | `products:write` |
81
+ | `teklifler` | Teklifleri listeler | `quotes:read` |
82
+ | `teklif_pdf` | Teklifin PDF'ini yerel dosyaya yazar, yolunu döner (onay istemez, kayıt değiştirmez) | `quotes:read` |
83
+ | `teklif_hazirla` / `teklif_guncelle` / `teklif_durumu` / `teklif_paylas` | Teklif açar / taslağı günceller / durumunu değiştirir (taslağı iptal: `Rejected`) / paylaşım bağlantısı üretir | `quotes:write` |
84
+ | `faturalar` | Faturalar ve kalan tutar | `invoices:read` |
85
+ | `fatura_kes` | Taslak satış faturası açar | `invoices:write` |
86
+ | `tahsilat_isle` | Faturaya tahsilat yazar | `invoices:write` |
87
+ | `odeme_linki` | Kartla ödeme bağlantısı üretir | `payment-links:write` |
88
+ | `eposta_kisiler` / `eposta_gruplari` / `eposta_kampanyalari` | E-posta kişileri, grupları ve kampanyaları (istatistikli) okur | `email:read` |
89
+ | `eposta_kisi_ekle` / `eposta_grup_ac` / `eposta_gruba_ekle` | Kişi (izin beyanıyla) / grup ekler, kişileri gruba koyar | `email:write` |
90
+ | `eposta_kampanya_hazirla` / `eposta_kampanya_guncelle` | Taslak kampanya açar / günceller (göndermez) | `email:write` |
91
+ | `eposta_deneme_gonder` | Kampanyanın `[TEST]` kopyasını tek adrese yollar (kredi düşmez) | `email:send` |
92
+ | `eposta_kampanya_gonder` | Taslağı hedef kitleye **gönderir** ya da zamanlar (geri alınamaz, kredi düşer) | `email:send` |
93
+ | `eposta_kampanya_durdur` | Giden kampanyayı durdurur / zamanlamayı iptal eder | `email:send` |
94
+ | `fis_yukle` / `fis_elle` / `fisler` | Fiş görseli yükler / elle gider fişi girer / fişleri okur | `receipts:write` / `receipts:write` / `receipts:read` |
95
+ | `z_raporu_yukle` / `z_raporu_elle` / `z_raporlari` | Z görseli yükler / elle Z girer / Z raporlarını okur | `pos-reports:write` / `pos-reports:write` / `pos-reports:read` |
96
+
97
+ Bilerek **yoktur**: silme, iade, e-Fatura gönderimi, fatura PDF'i (BiApp'te fatura PDF ucu yok; yalnız e-Fatura akışı var), teklifin e-postayla gönderimi, sipariş ve Hızlı Satış, çek/senet. Paylaşım
98
+ bağlantılarını ve ödeme linklerini paket göndermez; bağlantıyı siz iletirsiniz.
99
+
100
+ ## Yazmadan önce onay
101
+
102
+ `BIAPP_ONAY=sor` (varsayılan) iken her yazma aracı ilk çağrıda **BiApp'e hiçbir şey yazmaz**; bir önizleme ve imzalı bir `islemKodu` döner.
103
+ Asistan önizlemeyi size gösterir, "evet" derseniz aynı girdi ve kodla yeniden çağırır. Girdi değişirse (örneğin tutar) kod geçersiz olur ve
104
+ yeni önizleme gerekir. Kod 30 dakika geçerlidir ve BiApp'e `Idempotency-Key` olarak gider: ağ hatasından sonra yeniden deneme çift kayıt açmaz.
105
+
106
+ Bu bir güvenlik sınırı değil, bir emniyet kemeridir: onayı asistanın kendisi verebilir. Asıl güvence anahtarın kapsamı ve silme aracının
107
+ olmamasıdır. Yalnız okumasını istiyorsanız anahtara yazma kapsamlarını vermeyin.
108
+
109
+ ## Fiş ve Z raporu
110
+
111
+ - Sohbete eklediğiniz fotoğraf araca dosya olarak **geçmez**. Asistan fotoğrafı kendisi okuyup `fis_elle` / `z_raporu_elle` ile değerleri
112
+ yazarsa fotoğraf BiApp'e kaydedilmez (arşivde ve muhasebeci paylaşımında görünmez). Fotoğrafın kendisi için dosya yolu ya da `https`
113
+ adresi verin (`fis_yukle` / `z_raporu_yukle`) ya da BiApp mobil/Telegram yüklemesini kullanın.
114
+ - Yüklenen görsel Z raporuysa BiApp bunu kendisi ayırır: fiş `Failed` + `Z_REPORT` olur ve Z raporu kaydı açılır (`fisler` aracı `posReportId` gösterir).
115
+ - Yükleme araçları dosyayı baytlarından doğrular (yalnız JPEG, PNG, WebP, PDF; Z raporunda PDF yok), 15 MB'ı geçeni, metin/kod dosyasını ve
116
+ iç ağ adreslerini reddeder. `BIAPP_DOSYA_KOKU` ile okunabilecek klasörü daraltabilirsiniz.
117
+ - Z raporu yükleme 1 kredi düşer; fiş için önce ücretsiz hak, sonra kredi. Krediniz yoksa kayıt bekler.
118
+
119
+ ## Güvenlik
120
+
121
+ - Anahtar yalnızca ortam değişkenindendir; hiçbir çıktıya yazılmaz. Telemetri, kullanım toplama ve kendini güncelleme yoktur.
122
+ - Anahtar işletmenizin tüm verisini görür (alt kullanıcı kapsamı anahtarla işlemez). Yalnızca güvendiğiniz bilgisayara ve uygulamaya koyun,
123
+ gerekirse BiApp'te iptal edin.
124
+ - Araç çıktısındaki metinler (cari notu, ürün açıklaması) veridir; asistan talimatları buna göre verilmiştir, ama bir model yine de
125
+ yanılabilir. Önemli kayıtları onay adımında kontrol edin.
126
+
127
+ ## Sorun giderme
128
+
129
+ | Belirti | Neden |
130
+ |---|---|
131
+ | Yalnızca `isletme_bilgisi` var | Anahtar geçersiz/iptal ya da BiApp'e ulaşılamadı; aracı çağırın, nedenini söyler. Düzelttikten sonra bağlantıyı yeniden başlatın. |
132
+ | Kasa araçları görünmüyor | İşletmede Kasa uygulaması kurulu değil ya da anahtarda `ledger:*` kapsamı yok. |
133
+ | "Bu hareket … kaydından geliyor" | Fiş/fatura/Z'den gelen hareketin tutarı kaynak kaydında düzeltilir. |
134
+ | "Bu dosya zaten yüklenmiş" | Aynı görsel daha önce yüklenmiş (mükerrer koruması). |
135
+
136
+ ## Geliştirme
137
+
138
+ ```bash
139
+ npm install
140
+ npm run typecheck && npm test
141
+ BIAPP_E2E=1 BIAPP_API_KEY=bik_... BIAPP_API_URL=http://localhost:5173/api npm test # yerel BiApp stack'i + test firması gerekir
142
+ ```
143
+
144
+ Uçtan uca senaryolar `[MCP TEST]` önekli veri yazar; yalnızca test firmasında çalıştırın. Görselli senaryo için `BIAPP_E2E_IMAGES=<jpeg yolu>`
145
+ verilir ve yerel backend'in geçici olarak yerel MinIO'ya bağlanması gerekir (canlı depoya yazmasın diye).
146
+
147
+ ## Lisans
148
+
149
+ MIT
150
+
151
+ ## Teklif alanları
152
+
153
+ - `gecerlilikGun`: teklifin **son geçerlilik tarihi** (bugün + N gün). Dolunca BiApp teklifi `Expired` yapar.
154
+ - `opsiyonGun`: belgede **"Opsiyon"** olarak yazan metin ("7 gün"); süre dolumunu tetiklemez. İkisi ayrı alandır.
155
+ - `odemeKosullari` ("Peşin", "30 gün vade"…) belgede **Ödeme Koşulları**, `teslimGun` **Teslim Süresi**. Bu bilgiyi `not`'a yazmayın.
156
+ - Gönderilmemiş (Draft) teklif `teklif_guncelle` ile düzeltilir; vazgeçilen taslak `teklif_durumu` ile `Rejected` yapılır (silme yoktur).
157
+
158
+ ## Önizleme ve uyarılar
159
+
160
+ Önizleme, imzalanan girdinin dolu tüm alanlarını listeler; yanıtta ayrıca `gecerliSaat` ("HH:mm'e kadar", İstanbul) ve varsa `uyarilar` gelir:
161
+ aynı gün Z raporu varsa (`z_raporu_elle`, ikinci kayıt için `ikinciKayitOnayi: true`), aynı ad/telefon/VKN'li cari varsa (`cari_ac`),
162
+ cariye bağlı genel tahsilatın faturaları kapatmayacağı (`kasa_hareketi_ekle`). Süre dolarsa hata, hangi aracın hangi girdiyle yeniden çağrılacağını söyler.
163
+ `teklif_paylas` localhost adresi döndürürse "yalnız yerelde açılır" uyarısı verir ve hazır mesaj üretmez (müşteri adresi sunucuda `App__BaseUrl` ile ayarlanır).
164
+
165
+ ## E-posta pazarlama
166
+
167
+ E-posta Pazarlama uygulaması kuruluysa 11 araç eklenir. Gönderim ayrı kapsamdadır: `email:send` vermediğiniz anahtarla asistan taslak hazırlayabilir ama gönderemez.
168
+
169
+ - **Hedef açıkça seçilir:** taslakta `grupIds` ya da `tumKisiler: true` gerekir; "herkes" varsayılmaz.
170
+ - **Gönderim önizlemesi** alıcı sayısını, düşecek krediyi (her 100 alıcı için 10) ve aylık kotayı gösterir. Alıcı sayısı önizleme ile onay arasında değişirse gönderilmez (BiApp de `expectedRecipients` ile 409 döner, kredi düşmez).
171
+ - `eposta_kampanya_gonder` `BIAPP_ONAY=dogrudan` iken bile **her zaman** önizleme ve onay ister.
172
+ - Abonelikten çıkma bağlantısı içerikte yoksa BiApp sona ekler. `{{FIRST_NAME}}`, `{{LAST_NAME}}`, `{{FULL_NAME}}`, `{{COMPANY}}` kişiselleştirir.
173
+ - `eposta_kisi_ekle` kişinin e-posta almayı kabul ettiğini kullanıcının söylemesini ister (`izinOnayi`). Abonelikten çıkmış kişiyi yeniden abone yapan araç yoktur.
174
+ - Gönderim sağlayıcısı (SMTP / Brevo / Resend) BiApp panelinde ayarlıdır; MCP ayar yazmaz. Silme, kişi içe aktarma ve şablon editörü yoktur.
175
+
176
+ ### Menü şablonları (haftalık ve günlük)
177
+
178
+ `eposta_kampanya_hazirla` / `eposta_kampanya_guncelle`, düz metin ve HTML'in yanında BiApp'in hazır menü şablonlarını doldurur:
179
+
180
+ - **`haftalikMenu`** (Haftalık Menü): yapıştırılan menü `menuMetni`ne olduğu gibi konur. Günler boş satırla ayrılır, her bloğun ilk satırı gün adı, altındakiler yemektir (Pazartesi… Cuma). `baslik`, `altBaslik` ("3 – 7 Kasım") ve `altYazi` isteğe bağlı.
181
+ - **`gunlukMenu`** (Günlük Menü, kategorili kartlar): kategorisiz yemek listesi `yemekMetni`ne konur (satır başına bir yemek). Çorbalar, Ana Yemekler, Pilav & Makarna, Salata & Yanlar, İçecekler, Tatlılar olarak otomatik gruplanır; gruplama önizlemede görünür ve beğenilmezse `kategoriler` ile elle verilir. `tarih` verilmezse bugün (İstanbul). `firma` ile telefon, WhatsApp (905551234567), Instagram kullanıcı adı, adres, harita, logo ve web adresi eklenir.
182
+
183
+ Menüyü asistan düzenlemez; olduğu gibi şablona koyar. Her hafta/gün tekrar eden **otomatik** gönderim yoktur: menüyü her seferinde yapıştırırsınız, asistan taslağı hazırlar, deneme postasını yollar ve (isterseniz `zaman` ile ileri tarihe zamanlayarak) gönderir.
package/dist/client.js ADDED
@@ -0,0 +1,115 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { BiappError, describeHttpError, networkError } from "./errors.js";
3
+ const MAX_RETRY_AFTER_MS = 10_000;
4
+ /**
5
+ * BiApp `api/v1` istemcisi. Yalnız `Authorization: Bearer` gönderir; anahtar hata metnine girmez.
6
+ * Yeniden deneme: 429'da `Retry-After` kadar bekleyip bir kez; 5xx ve ağ hatasında bir kez (yazmada aynı
7
+ * `Idempotency-Key` ile, yani çift kayıt açmaz).
8
+ */
9
+ export class BiappClient {
10
+ config;
11
+ fetchImpl;
12
+ sleep;
13
+ userAgent;
14
+ constructor(config, fetchImpl = fetch, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), userAgent = "biapp-mcp") {
15
+ this.config = config;
16
+ this.fetchImpl = fetchImpl;
17
+ this.sleep = sleep;
18
+ this.userAgent = userAgent;
19
+ }
20
+ get(path, query) {
21
+ return this.request("GET", path, { query });
22
+ }
23
+ /** İkili yanıt (ör. PDF). */
24
+ getBytes(path) {
25
+ return this.request("GET", path, { binary: true });
26
+ }
27
+ post(path, opts = {}) {
28
+ return this.request("POST", path, opts);
29
+ }
30
+ put(path, opts = {}) {
31
+ return this.request("PUT", path, opts);
32
+ }
33
+ patch(path, opts = {}) {
34
+ return this.request("PATCH", path, opts);
35
+ }
36
+ url(path, query) {
37
+ const u = new URL(`${this.config.apiUrl}/v1${path}`);
38
+ for (const [k, v] of Object.entries(query ?? {})) {
39
+ if (v !== undefined && v !== null && v !== "")
40
+ u.searchParams.set(k, String(v));
41
+ }
42
+ return u.toString();
43
+ }
44
+ async request(method, path, opts = {}) {
45
+ const isWrite = method !== "GET";
46
+ const key = isWrite && !opts.form ? (opts.idempotencyKey ?? randomUUID()) : undefined;
47
+ const timeout = opts.timeoutMs ?? (opts.form ? this.config.uploadTimeoutMs : this.config.timeoutMs);
48
+ const url = this.url(path, opts.query);
49
+ const attempt = async () => {
50
+ const headers = {
51
+ Authorization: `Bearer ${this.config.apiKey}`,
52
+ Accept: opts.binary ? "application/pdf, application/json" : "application/json",
53
+ "User-Agent": this.userAgent,
54
+ };
55
+ if (key)
56
+ headers["Idempotency-Key"] = key;
57
+ let body;
58
+ if (opts.form)
59
+ body = opts.form;
60
+ else if (opts.body !== undefined) {
61
+ headers["Content-Type"] = "application/json";
62
+ body = JSON.stringify(opts.body);
63
+ }
64
+ return this.fetchImpl(url, { method, headers, body, redirect: "error", signal: AbortSignal.timeout(timeout) });
65
+ };
66
+ let res;
67
+ try {
68
+ res = await attempt();
69
+ }
70
+ catch {
71
+ await this.sleep(500);
72
+ try {
73
+ res = await attempt();
74
+ }
75
+ catch {
76
+ throw networkError();
77
+ }
78
+ }
79
+ if (res.status === 429) {
80
+ const wait = Math.min(Number(res.headers.get("retry-after")) * 1000 || 2000, MAX_RETRY_AFTER_MS);
81
+ await this.sleep(wait);
82
+ try {
83
+ res = await attempt();
84
+ }
85
+ catch {
86
+ throw networkError();
87
+ }
88
+ }
89
+ else if (res.status >= 500) {
90
+ await this.sleep(500);
91
+ try {
92
+ res = await attempt();
93
+ }
94
+ catch {
95
+ throw networkError();
96
+ }
97
+ }
98
+ if (opts.binary && res.ok)
99
+ return { status: res.status, data: new Uint8Array(await res.arrayBuffer()) };
100
+ const text = await res.text();
101
+ let data = undefined;
102
+ if (text) {
103
+ try {
104
+ data = JSON.parse(text);
105
+ }
106
+ catch {
107
+ data = undefined;
108
+ }
109
+ }
110
+ if (!res.ok)
111
+ throw describeHttpError(res.status, data);
112
+ return { status: res.status, data: data };
113
+ }
114
+ }
115
+ export { BiappError };
package/dist/config.js ADDED
@@ -0,0 +1,43 @@
1
+ export const VARSAYILAN_API_URL = "https://api.biapp.com.tr/api";
2
+ export class ConfigError extends Error {
3
+ }
4
+ function isLocalHost(host) {
5
+ return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host.endsWith(".localhost");
6
+ }
7
+ export function loadConfig(env = process.env) {
8
+ const apiKey = (env.BIAPP_API_KEY ?? "").trim();
9
+ if (!apiKey) {
10
+ throw new ConfigError("BIAPP_API_KEY tanımlı değil. BiApp > Ayarlar > Entegrasyonlar'dan bir API anahtarı oluşturup ortam değişkeni olarak verin.");
11
+ }
12
+ if (!apiKey.startsWith("bik_")) {
13
+ throw new ConfigError("BIAPP_API_KEY 'bik_' ile başlamalı; BiApp > Entegrasyonlar'daki anahtarı kopyaladığınızdan emin olun.");
14
+ }
15
+ const rawUrl = (env.BIAPP_API_URL ?? "").trim() || VARSAYILAN_API_URL;
16
+ let parsed;
17
+ try {
18
+ parsed = new URL(rawUrl);
19
+ }
20
+ catch {
21
+ throw new ConfigError("BIAPP_API_URL geçerli bir adres değil.");
22
+ }
23
+ if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && isLocalHost(parsed.hostname))) {
24
+ throw new ConfigError("BIAPP_API_URL https olmalı (http yalnız localhost için kabul edilir).");
25
+ }
26
+ const apiUrl = rawUrl.replace(/\/+$/, "");
27
+ const onayRaw = (env.BIAPP_ONAY ?? "sor").trim().toLowerCase();
28
+ if (onayRaw !== "sor" && onayRaw !== "dogrudan") {
29
+ throw new ConfigError("BIAPP_ONAY 'sor' ya da 'dogrudan' olmalı.");
30
+ }
31
+ const timeoutSn = Number(env.BIAPP_ZAMAN_ASIMI_SN ?? "");
32
+ const timeoutMs = Number.isFinite(timeoutSn) && timeoutSn > 0 ? timeoutSn * 1000 : 30_000;
33
+ const fileRoot = (env.BIAPP_DOSYA_KOKU ?? "").trim();
34
+ return {
35
+ apiKey,
36
+ apiUrl,
37
+ onay: onayRaw,
38
+ fileRoot: fileRoot || undefined,
39
+ pdfDir: (env.BIAPP_PDF_KLASORU ?? "").trim() || undefined,
40
+ timeoutMs,
41
+ uploadTimeoutMs: Math.max(timeoutMs, 90_000),
42
+ };
43
+ }
@@ -0,0 +1,50 @@
1
+ import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
2
+ /** Önizleme kodunun geçerlilik süresi. */
3
+ export const GECERLILIK_DK = 30;
4
+ /** Anahtarları sıralı, `undefined` atılmış JSON: aynı girdi her zaman aynı metni verir. */
5
+ export function canonical(value) {
6
+ if (value === undefined)
7
+ return "null";
8
+ if (value === null || typeof value !== "object")
9
+ return JSON.stringify(value) ?? "null";
10
+ if (Array.isArray(value))
11
+ return `[${value.map((v) => canonical(v)).join(",")}]`;
12
+ const entries = Object.entries(value)
13
+ .filter(([, v]) => v !== undefined)
14
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
15
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(",")}}`;
16
+ }
17
+ function mac(apiKey, tool, payload, nonce, exp) {
18
+ const secret = createHash("sha256").update(`biapp-mcp:${apiKey}`).digest();
19
+ return createHmac("sha256", secret).update(`${tool}|${canonical(payload)}|${nonce}|${exp}`).digest("hex");
20
+ }
21
+ /**
22
+ * Durumsuz önizleme kodu: `nonce.bitisSn.hmac`. Süreç yeniden başlasa da doğrulanır; aynı anahtar, araç ve girdi ister.
23
+ * Kod aynı zamanda `Idempotency-Key` olarak gider (200 karakter sınırının çok altında).
24
+ */
25
+ export function signIslemKodu(apiKey, tool, payload, now = Date.now()) {
26
+ const nonce = randomBytes(8).toString("hex");
27
+ const exp = Math.floor(now / 1000) + GECERLILIK_DK * 60;
28
+ return `${nonce}.${exp}.${mac(apiKey, tool, payload, nonce, exp)}`;
29
+ }
30
+ export function verifyIslemKodu(apiKey, tool, payload, kod, now = Date.now()) {
31
+ if (!kod)
32
+ return { ok: false, neden: "islemKodu yok. Önce onay vermeden çağırıp önizleme alın, kullanıcıya gösterin." };
33
+ const parts = kod.split(".");
34
+ if (parts.length !== 3)
35
+ return { ok: false, neden: "islemKodu geçersiz. Önizlemeyi yeniden alın." };
36
+ const [nonce, expRaw, given] = parts;
37
+ const exp = Number(expRaw);
38
+ if (!Number.isInteger(exp))
39
+ return { ok: false, neden: "islemKodu geçersiz. Önizlemeyi yeniden alın." };
40
+ const expected = mac(apiKey, tool, payload, nonce, exp);
41
+ const a = Buffer.from(given, "hex");
42
+ const b = Buffer.from(expected, "hex");
43
+ if (a.length !== b.length || !timingSafeEqual(a, b)) {
44
+ return { ok: false, neden: "Girdi önizlemedekiyle aynı değil (ya da kod başka araca ait). Yeni önizleme alıp kullanıcıya gösterin." };
45
+ }
46
+ if (Math.floor(now / 1000) > exp) {
47
+ return { ok: false, neden: "Önizlemenin süresi doldu. Yeni önizleme alıp kullanıcıya yeniden onaylatın." };
48
+ }
49
+ return { ok: true };
50
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,63 @@
1
+ /** Asistana anlatılabilir hata; `message` Türkçe ve eyleme dönüktür, anahtar içermez. */
2
+ export class BiappError extends Error {
3
+ status;
4
+ code;
5
+ constructor(message, status, code) {
6
+ super(message);
7
+ this.status = status;
8
+ this.code = code;
9
+ this.name = "BiappError";
10
+ }
11
+ }
12
+ function asBody(raw) {
13
+ return raw && typeof raw === "object" ? raw : {};
14
+ }
15
+ export function describeHttpError(status, raw) {
16
+ const body = asBody(raw);
17
+ const text = typeof body.error === "string" ? body.error : undefined;
18
+ const code = typeof body.code === "string" ? body.code : undefined;
19
+ if (status === 401) {
20
+ return new BiappError("BiApp anahtarı geçersiz ya da iptal edilmiş. BiApp > Ayarlar > Entegrasyonlar'dan yenileyin.", status, code);
21
+ }
22
+ if (status === 403) {
23
+ if (typeof body.scope === "string") {
24
+ return new BiappError(`Bu anahtarın '${body.scope}' yetkisi yok; BiApp > Entegrasyonlar'da anahtara ekleyin.`, status, "SCOPE");
25
+ }
26
+ if (typeof body.app === "string") {
27
+ return new BiappError(`'${body.app}' uygulaması kurulu değil; BiApp mağazasından kurun.`, status, "APP");
28
+ }
29
+ return new BiappError(text ?? "Bu işlem için yetki yok.", status, code);
30
+ }
31
+ if (status === 404) {
32
+ return new BiappError("Kayıt bulunamadı (başka işletmeye ait olabilir).", status, code);
33
+ }
34
+ if (status === 409) {
35
+ if (code === "LEDGER_LOCKED") {
36
+ return new BiappError(`${text ?? "Bu hareket başka bir kayıttan geliyor."} Tutarı kaynak kaydında düzeltin; açıklama değiştirilebilir.`, status, code);
37
+ }
38
+ if (code === "NO_ACTING_USER") {
39
+ return new BiappError("Bu anahtar adına kayıt açacak aktif kullanıcı yok. Anahtarı bir yönetici açmalı.", status, code);
40
+ }
41
+ return new BiappError(text ?? "Kayıt çakışması.", status, code);
42
+ }
43
+ if (status === 400 && (code === "DUPLICATE_RECEIPT" || code === "DUPLICATE_POS_REPORT")) {
44
+ const dup = body.duplicateOfReceiptId ?? body.duplicateOfReportId;
45
+ return new BiappError(`Bu dosya zaten yüklenmiş${dup ? ` (kayıt ${dup})` : ""}.`, status, code);
46
+ }
47
+ if (status === 402) {
48
+ return new BiappError("Krediniz yok; kayıt bekliyor. Kredi yüklenince işlenir.", status, code);
49
+ }
50
+ if (status === 400 || status === 422) {
51
+ return new BiappError(text ?? "İstek geçersiz.", status, code);
52
+ }
53
+ if (status === 429) {
54
+ return new BiappError("Çok sık istek gönderildi; biraz bekleyip yeniden deneyin.", status, code);
55
+ }
56
+ if (status >= 500) {
57
+ return new BiappError("BiApp şu an yanıt vermiyor; birazdan yeniden deneyin.", status, code);
58
+ }
59
+ return new BiappError(text ?? `Beklenmeyen yanıt (${status}).`, status, code);
60
+ }
61
+ export function networkError() {
62
+ return new BiappError("BiApp'e ulaşılamadı. İnternet bağlantısını ve BIAPP_API_URL'yi kontrol edin.", 0, "NETWORK");
63
+ }