permerix 0.1.0__tar.gz

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.
permerix-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daffa Farid
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.
@@ -0,0 +1,19 @@
1
+ # Yang IKUT ke dalam paket sumber (sdist).
2
+ include README.md
3
+ include LICENSE
4
+
5
+ # Yang TIDAK BOLEH ikut, apa pun keadaannya.
6
+ # Paket ini akan terbit ke PyPI dan bisa diunduh siapa saja - sekali sebuah
7
+ # kunci ikut terkirim, ia harus dianggap bocor selamanya dan wajib dicabut.
8
+ exclude .env
9
+ exclude .env.*
10
+ global-exclude .env
11
+ global-exclude .env.*
12
+ global-exclude *.key
13
+ global-exclude *.pem
14
+ global-exclude secrets.*
15
+ global-exclude *.pyc
16
+ global-exclude __pycache__/*
17
+ prune contoh
18
+ prune dist
19
+ prune build
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: permerix
3
+ Version: 0.1.0
4
+ Summary: Pustaka Python resmi untuk layanan Permerix (model Nusa).
5
+ Author: Daffa Farid
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Daffa7382466/permerix
8
+ Project-URL: Repository, https://github.com/Daffa7382466/permerix
9
+ Project-URL: Issues, https://github.com/Daffa7382466/permerix/issues
10
+ Keywords: permerix,nusa,ai,bahasa-indonesia,nlp,sdk
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Natural Language :: Indonesian
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Dynamic: license-file
28
+
29
+ # Permerix
30
+
31
+ Pustaka Python resmi untuk layanan **Permerix**.
32
+
33
+ **Permerix** adalah nama layanannya; **Nusa** adalah nama model di dalamnya —
34
+ hubungannya seperti Claude dengan Opus.
35
+
36
+ Nusa adalah AI berbahasa Indonesia yang dibangun dari nol: mesin n-gram dan
37
+ peleburan graf kata, tanpa jaringan saraf. Ia menyusun kalimatnya sendiri dan
38
+ bisa mencari tahu ke internet ketika tidak tahu.
39
+
40
+ **Tanpa dependensi.** `pip install permerix` tidak menarik paket lain apa pun —
41
+ pustaka ini hanya memakai modul bawaan Python.
42
+
43
+ ---
44
+
45
+ ## Pasang
46
+
47
+ ```bash
48
+ pip install permerix
49
+ ```
50
+
51
+ ## Pakai
52
+
53
+ ```python
54
+ from permerix import Permerix
55
+
56
+ klien = Permerix(kunci="prx-live-xxxx")
57
+
58
+ print(klien.tanya("apa itu kalimat efektif"))
59
+ ```
60
+
61
+ Alamat layanan sudah tertanam di pustaka, jadi kamu tidak perlu mengaturnya.
62
+
63
+ ### Jawaban lengkap
64
+
65
+ ```python
66
+ balasan = klien.percakapan.kirim("apa itu pantun")
67
+
68
+ print(balasan.isi)
69
+ print(balasan.kredit) # kredit yang terpakai
70
+ print(balasan.sisa_kredit) # sisa; None berarti tanpa batas
71
+ ```
72
+
73
+ ### Mengalir
74
+
75
+ ```python
76
+ for potong in klien.percakapan.alirkan("ceritakan tentang pantun"):
77
+ print(potong.isi, end="", flush=True)
78
+ ```
79
+
80
+ ### Cek kredit dan model
81
+
82
+ ```python
83
+ klien.kredit() # {'kredit': 98, 'peran': 'user', 'pesan': 2, ...}
84
+ klien.model() # [{'id': 'nusa-0.2', 'nama': 'Nusa 0.2', ...}]
85
+ ```
86
+
87
+ ---
88
+
89
+ ## Dari baris perintah
90
+
91
+ ```bash
92
+ export PERMERIX_API_KEY=prx-live-xxxx
93
+
94
+ permerix "apa itu kalimat efektif"
95
+ permerix --alir "ceritakan tentang pantun"
96
+ permerix --kredit
97
+ permerix --daftar-model
98
+ ```
99
+
100
+ Atau tanpa memasang perintahnya: `python -m permerix "halo"`
101
+
102
+ ---
103
+
104
+ ## Mengambil kunci
105
+
106
+ Kunci API diambil lewat **bot Telegram** Permerix:
107
+
108
+ 1. Buka botnya, tekan `/start`
109
+ 2. Tekan tombol **Key** → **Buat Kunci**
110
+ 3. Salin kunci `prx-live-...`
111
+
112
+ Pengguna baru mendapat **100 kredit gratis**. Satu pesan biasanya 1 kredit,
113
+ naik sampai 5 bila Nusa perlu mencari ke internet.
114
+
115
+ ---
116
+
117
+ ## Pengaturan
118
+
119
+ | Peubah lingkungan | Guna |
120
+ |---|---|
121
+ | `PERMERIX_API_KEY` | kunci API, supaya tidak perlu ditulis di kode |
122
+ | `PERMERIX_API_URL` | alamat layanan; menimpa alamat bawaan pustaka |
123
+
124
+ ```python
125
+ # Semua pengaturan bisa juga diberikan langsung
126
+ klien = Permerix(
127
+ kunci="prx-live-xxxx",
128
+ alamat="https://api.contoh.com", # opsional
129
+ tenggang=180, # detik
130
+ coba_ulang=2, # ulangi bila jaringan gagal
131
+ )
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Menangani kesalahan
137
+
138
+ ```python
139
+ from permerix import Permerix, KreditHabis, TerlaluCepat, GalatPermerix
140
+
141
+ try:
142
+ print(klien.tanya("halo"))
143
+ except KreditHabis:
144
+ print("Kredit habis, isi ulang lewat bot Telegram.")
145
+ except TerlaluCepat as e:
146
+ print(f"Terlalu cepat, tunggu sebentar. {e.pesan}")
147
+ except GalatPermerix as e:
148
+ print(f"Gagal ({e.kode}): {e.pesan}")
149
+ ```
150
+
151
+ Semua kesalahan turunan `GalatPermerix`, jadi satu blok `except` sudah cukup.
152
+
153
+ | Kesalahan | Kapan |
154
+ |---|---|
155
+ | `GalatKunci` | kunci salah, tidak ada, atau sudah dicabut |
156
+ | `KreditHabis` | kredit sudah nol |
157
+ | `TerlaluCepat` | melewati batas laju |
158
+ | `ModelDitolak` | model belum dirilis atau perlu peran lebih tinggi |
159
+ | `GalatJaringan` | server tidak bisa dihubungi |
160
+ | `GalatLayanan` | server bermasalah di sisinya |
161
+
162
+ ---
163
+
164
+ ## Model
165
+
166
+ | Model | Keterangan |
167
+ |---|---|
168
+ | `nusa-0.2` | model bawaan, sudah dirilis |
169
+
170
+ Model berikutnya akan dibuka lebih dulu untuk pengguna Premium.
171
+
172
+ ---
173
+
174
+ ## Batas jujur
175
+
176
+ Nusa **bukan model bahasa besar**. Ia menyusun ulang kalimat yang pernah
177
+ dibacanya memakai n-gram dan peleburan graf kata, tanpa jaringan saraf dan
178
+ tanpa penalaran. Kalimatnya kadang masih pincang, terutama untuk pertanyaan
179
+ yang menuntut hafalan atau perhitungan berantai.
180
+
181
+ Yang dijaga: ia tidak menyalin mentah hasil pencarian, tidak membalik makna
182
+ kalimat, dan mengaku tidak tahu daripada mengarang.
183
+
184
+ ---
185
+
186
+ ## Lisensi
187
+
188
+ MIT
@@ -0,0 +1,160 @@
1
+ # Permerix
2
+
3
+ Pustaka Python resmi untuk layanan **Permerix**.
4
+
5
+ **Permerix** adalah nama layanannya; **Nusa** adalah nama model di dalamnya —
6
+ hubungannya seperti Claude dengan Opus.
7
+
8
+ Nusa adalah AI berbahasa Indonesia yang dibangun dari nol: mesin n-gram dan
9
+ peleburan graf kata, tanpa jaringan saraf. Ia menyusun kalimatnya sendiri dan
10
+ bisa mencari tahu ke internet ketika tidak tahu.
11
+
12
+ **Tanpa dependensi.** `pip install permerix` tidak menarik paket lain apa pun —
13
+ pustaka ini hanya memakai modul bawaan Python.
14
+
15
+ ---
16
+
17
+ ## Pasang
18
+
19
+ ```bash
20
+ pip install permerix
21
+ ```
22
+
23
+ ## Pakai
24
+
25
+ ```python
26
+ from permerix import Permerix
27
+
28
+ klien = Permerix(kunci="prx-live-xxxx")
29
+
30
+ print(klien.tanya("apa itu kalimat efektif"))
31
+ ```
32
+
33
+ Alamat layanan sudah tertanam di pustaka, jadi kamu tidak perlu mengaturnya.
34
+
35
+ ### Jawaban lengkap
36
+
37
+ ```python
38
+ balasan = klien.percakapan.kirim("apa itu pantun")
39
+
40
+ print(balasan.isi)
41
+ print(balasan.kredit) # kredit yang terpakai
42
+ print(balasan.sisa_kredit) # sisa; None berarti tanpa batas
43
+ ```
44
+
45
+ ### Mengalir
46
+
47
+ ```python
48
+ for potong in klien.percakapan.alirkan("ceritakan tentang pantun"):
49
+ print(potong.isi, end="", flush=True)
50
+ ```
51
+
52
+ ### Cek kredit dan model
53
+
54
+ ```python
55
+ klien.kredit() # {'kredit': 98, 'peran': 'user', 'pesan': 2, ...}
56
+ klien.model() # [{'id': 'nusa-0.2', 'nama': 'Nusa 0.2', ...}]
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Dari baris perintah
62
+
63
+ ```bash
64
+ export PERMERIX_API_KEY=prx-live-xxxx
65
+
66
+ permerix "apa itu kalimat efektif"
67
+ permerix --alir "ceritakan tentang pantun"
68
+ permerix --kredit
69
+ permerix --daftar-model
70
+ ```
71
+
72
+ Atau tanpa memasang perintahnya: `python -m permerix "halo"`
73
+
74
+ ---
75
+
76
+ ## Mengambil kunci
77
+
78
+ Kunci API diambil lewat **bot Telegram** Permerix:
79
+
80
+ 1. Buka botnya, tekan `/start`
81
+ 2. Tekan tombol **Key** → **Buat Kunci**
82
+ 3. Salin kunci `prx-live-...`
83
+
84
+ Pengguna baru mendapat **100 kredit gratis**. Satu pesan biasanya 1 kredit,
85
+ naik sampai 5 bila Nusa perlu mencari ke internet.
86
+
87
+ ---
88
+
89
+ ## Pengaturan
90
+
91
+ | Peubah lingkungan | Guna |
92
+ |---|---|
93
+ | `PERMERIX_API_KEY` | kunci API, supaya tidak perlu ditulis di kode |
94
+ | `PERMERIX_API_URL` | alamat layanan; menimpa alamat bawaan pustaka |
95
+
96
+ ```python
97
+ # Semua pengaturan bisa juga diberikan langsung
98
+ klien = Permerix(
99
+ kunci="prx-live-xxxx",
100
+ alamat="https://api.contoh.com", # opsional
101
+ tenggang=180, # detik
102
+ coba_ulang=2, # ulangi bila jaringan gagal
103
+ )
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Menangani kesalahan
109
+
110
+ ```python
111
+ from permerix import Permerix, KreditHabis, TerlaluCepat, GalatPermerix
112
+
113
+ try:
114
+ print(klien.tanya("halo"))
115
+ except KreditHabis:
116
+ print("Kredit habis, isi ulang lewat bot Telegram.")
117
+ except TerlaluCepat as e:
118
+ print(f"Terlalu cepat, tunggu sebentar. {e.pesan}")
119
+ except GalatPermerix as e:
120
+ print(f"Gagal ({e.kode}): {e.pesan}")
121
+ ```
122
+
123
+ Semua kesalahan turunan `GalatPermerix`, jadi satu blok `except` sudah cukup.
124
+
125
+ | Kesalahan | Kapan |
126
+ |---|---|
127
+ | `GalatKunci` | kunci salah, tidak ada, atau sudah dicabut |
128
+ | `KreditHabis` | kredit sudah nol |
129
+ | `TerlaluCepat` | melewati batas laju |
130
+ | `ModelDitolak` | model belum dirilis atau perlu peran lebih tinggi |
131
+ | `GalatJaringan` | server tidak bisa dihubungi |
132
+ | `GalatLayanan` | server bermasalah di sisinya |
133
+
134
+ ---
135
+
136
+ ## Model
137
+
138
+ | Model | Keterangan |
139
+ |---|---|
140
+ | `nusa-0.2` | model bawaan, sudah dirilis |
141
+
142
+ Model berikutnya akan dibuka lebih dulu untuk pengguna Premium.
143
+
144
+ ---
145
+
146
+ ## Batas jujur
147
+
148
+ Nusa **bukan model bahasa besar**. Ia menyusun ulang kalimat yang pernah
149
+ dibacanya memakai n-gram dan peleburan graf kata, tanpa jaringan saraf dan
150
+ tanpa penalaran. Kalimatnya kadang masih pincang, terutama untuk pertanyaan
151
+ yang menuntut hafalan atau perhitungan berantai.
152
+
153
+ Yang dijaga: ia tidak menyalin mentah hasil pencarian, tidak membalik makna
154
+ kalimat, dan mengaku tidak tahu daripada mengarang.
155
+
156
+ ---
157
+
158
+ ## Lisensi
159
+
160
+ MIT
@@ -0,0 +1,48 @@
1
+ """
2
+ Permerix - pustaka Python resmi.
3
+
4
+ Permerix adalah nama layanannya; Nusa adalah nama model di dalamnya -
5
+ hubungannya seperti Claude dengan Opus.
6
+
7
+ from permerix import Permerix
8
+
9
+ klien = Permerix(kunci="prx-live-xxxx")
10
+ print(klien.tanya("apa itu kalimat efektif"))
11
+
12
+ Mengalir:
13
+
14
+ for potong in klien.percakapan.alirkan("halo"):
15
+ print(potong.isi, end="", flush=True)
16
+
17
+ Tanpa dependensi apa pun - hanya pustaka bawaan Python.
18
+ """
19
+
20
+ from ._versi import __version__
21
+ from .galat import (
22
+ GalatJaringan,
23
+ GalatKunci,
24
+ GalatLayanan,
25
+ GalatPermerix,
26
+ KreditHabis,
27
+ ModelDitolak,
28
+ TerlaluCepat,
29
+ )
30
+ from .jenis import Balasan, Pemakaian, Potong
31
+ from .klien import ALAMAT_RESMI, MODEL_BAWAAN, Permerix
32
+
33
+ __all__ = [
34
+ "Permerix",
35
+ "Balasan",
36
+ "Potong",
37
+ "Pemakaian",
38
+ "GalatPermerix",
39
+ "GalatKunci",
40
+ "KreditHabis",
41
+ "TerlaluCepat",
42
+ "ModelDitolak",
43
+ "GalatJaringan",
44
+ "GalatLayanan",
45
+ "ALAMAT_RESMI",
46
+ "MODEL_BAWAAN",
47
+ "__version__",
48
+ ]
@@ -0,0 +1,92 @@
1
+ """
2
+ Antarmuka baris perintah: python -m permerix
3
+
4
+ python -m permerix "apa itu kalimat efektif"
5
+ python -m permerix --alir "ceritakan tentang pantun"
6
+ python -m permerix --kredit
7
+ python -m permerix --model
8
+
9
+ Kunci diambil dari PERMERIX_API_KEY, atau lewat --kunci.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import sys
16
+
17
+ from ._versi import __version__
18
+ from .galat import GalatPermerix, KreditHabis, TerlaluCepat
19
+ from .klien import MODEL_BAWAAN, Permerix
20
+
21
+
22
+ def main(argv: list[str] | None = None) -> int:
23
+ urai = argparse.ArgumentParser(
24
+ prog="permerix",
25
+ description="Berbicara dengan Permerix dari baris perintah.")
26
+ urai.add_argument("pesan", nargs="*", help="pesan yang ingin dikirim")
27
+ urai.add_argument("--kunci", help="kunci API (bawaan: PERMERIX_API_KEY)")
28
+ urai.add_argument("--alamat", help="alamat layanan (bawaan: bawaan pustaka)")
29
+ urai.add_argument("--model", dest="model_dipakai", default=MODEL_BAWAAN,
30
+ help=f"model yang dipakai (bawaan: {MODEL_BAWAAN})")
31
+ urai.add_argument("--alir", action="store_true",
32
+ help="tampilkan jawaban sepotong demi sepotong")
33
+ urai.add_argument("--kredit", action="store_true",
34
+ help="tampilkan sisa kredit lalu keluar")
35
+ urai.add_argument("--daftar-model", action="store_true",
36
+ help="tampilkan model yang tersedia lalu keluar")
37
+ urai.add_argument("--versi", action="version",
38
+ version=f"permerix {__version__}")
39
+ pilihan = urai.parse_args(argv)
40
+
41
+ try:
42
+ klien = Permerix(kunci=pilihan.kunci, alamat=pilihan.alamat)
43
+
44
+ if pilihan.kredit:
45
+ data = klien.kredit()
46
+ sisa = "tanpa batas" if data.get("tanpa_batas") else data.get("kredit")
47
+ print(f"Peran : {data.get('peran')}")
48
+ print(f"Kredit : {sisa}")
49
+ print(f"Pesan : {data.get('pesan')}")
50
+ return 0
51
+
52
+ if pilihan.daftar_model:
53
+ for m in klien.model():
54
+ print(f"{m['id']:<12} {m.get('nama', '')} [{m.get('status', '')}]")
55
+ return 0
56
+
57
+ pesan = " ".join(pilihan.pesan).strip()
58
+ if not pesan:
59
+ urai.print_help()
60
+ return 2
61
+
62
+ if pilihan.alir:
63
+ for potong in klien.percakapan.alirkan(pesan, model=pilihan.model_dipakai):
64
+ if potong.selesai:
65
+ print()
66
+ if potong.pemakaian:
67
+ print(f"[{potong.pemakaian}]", file=sys.stderr)
68
+ else:
69
+ print(potong.isi, end="", flush=True)
70
+ return 0
71
+
72
+ balasan = klien.percakapan.kirim(pesan, model=pilihan.model_dipakai)
73
+ print(balasan.isi)
74
+ print(f"[{balasan.pemakaian}]", file=sys.stderr)
75
+ return 0
76
+
77
+ except KreditHabis as e:
78
+ print(f"Kredit habis: {e.pesan}", file=sys.stderr)
79
+ return 3
80
+ except TerlaluCepat as e:
81
+ print(f"Terlalu cepat: {e.pesan}", file=sys.stderr)
82
+ return 4
83
+ except GalatPermerix as e:
84
+ print(f"Gagal: {e}", file=sys.stderr)
85
+ return 1
86
+ except KeyboardInterrupt:
87
+ print(file=sys.stderr)
88
+ return 130
89
+
90
+
91
+ if __name__ == "__main__":
92
+ sys.exit(main())
@@ -0,0 +1,3 @@
1
+ """Versi paket. Satu-satunya tempat nomor versi ditulis."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,68 @@
1
+ """
2
+ Kesalahan yang bisa dilempar pustaka Permerix.
3
+
4
+ Semuanya turunan GalatPermerix, jadi satu blok `except GalatPermerix`
5
+ sudah cukup untuk menangkap apa pun. Yang ingin membedakan penyebabnya
6
+ bisa menangkap turunan yang lebih khusus.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+
12
+ class GalatPermerix(Exception):
13
+ """Induk semua kesalahan Permerix."""
14
+
15
+ def __init__(self, pesan: str, kode: int = 0, jenis: str = "") -> None:
16
+ super().__init__(pesan)
17
+ self.pesan = pesan
18
+ self.kode = kode # status HTTP, 0 bila belum sampai server
19
+ self.jenis = jenis # penanda dari server, mis. "galat_kunci"
20
+
21
+ def __str__(self) -> str:
22
+ return f"[{self.kode}] {self.pesan}" if self.kode else self.pesan
23
+
24
+
25
+ class GalatKunci(GalatPermerix):
26
+ """Kunci API tidak ada, salah, atau sudah dicabut. (401/403)"""
27
+
28
+
29
+ class KreditHabis(GalatPermerix):
30
+ """Kredit pengguna sudah nol. Isi ulang lewat bot Telegram. (402)"""
31
+
32
+
33
+ class TerlaluCepat(GalatPermerix):
34
+ """Melewati batas laju. Tunggu sebentar lalu coba lagi. (429)"""
35
+
36
+ def __init__(self, pesan: str, kode: int = 429, jenis: str = "",
37
+ tunggu: int = 0) -> None:
38
+ super().__init__(pesan, kode, jenis)
39
+ self.tunggu = tunggu # perkiraan detik sebelum boleh mencoba lagi
40
+
41
+
42
+ class ModelDitolak(GalatPermerix):
43
+ """Model tidak dikenal, belum dirilis, atau perlu peran lebih tinggi. (403)"""
44
+
45
+
46
+ class GalatJaringan(GalatPermerix):
47
+ """Server tidak bisa dihubungi sama sekali."""
48
+
49
+
50
+ class GalatLayanan(GalatPermerix):
51
+ """Server menjawab dengan kesalahan di sisinya. (5xx)"""
52
+
53
+
54
+ def dari_tanggapan(kode: int, pesan: str, jenis: str = "") -> GalatPermerix:
55
+ """Pilih jenis kesalahan yang paling tepat dari status HTTP."""
56
+ if kode == 402:
57
+ return KreditHabis(pesan, kode, jenis)
58
+ if kode == 429:
59
+ return TerlaluCepat(pesan, kode, jenis)
60
+ if kode in (401,) or jenis == "galat_kunci":
61
+ return GalatKunci(pesan, kode, jenis)
62
+ if kode == 403:
63
+ # 403 bisa berarti kunci admin dicabut ATAU model tidak boleh dipakai.
64
+ return (ModelDitolak(pesan, kode, jenis) if jenis == "model_ditolak"
65
+ else GalatKunci(pesan, kode, jenis))
66
+ if kode >= 500:
67
+ return GalatLayanan(pesan, kode, jenis)
68
+ return GalatPermerix(pesan, kode, jenis)
@@ -0,0 +1,93 @@
1
+ """Bentuk data yang dikembalikan pustaka: Balasan, Potong, Pemakaian."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any, Optional
7
+
8
+
9
+ @dataclass
10
+ class Pemakaian:
11
+ """Berapa kredit yang terpakai untuk satu permintaan."""
12
+
13
+ kredit: int = 0
14
+ rincian: str = ""
15
+ sisa_kredit: Optional[int] = None # None berarti tanpa batas (owner)
16
+
17
+ @property
18
+ def tanpa_batas(self) -> bool:
19
+ return self.sisa_kredit is None
20
+
21
+ @classmethod
22
+ def dari(cls, data: dict[str, Any] | None) -> "Pemakaian":
23
+ data = data or {}
24
+ return cls(
25
+ kredit=int(data.get("kredit") or 0),
26
+ rincian=str(data.get("rincian") or ""),
27
+ sisa_kredit=data.get("sisa_kredit"),
28
+ )
29
+
30
+ def __str__(self) -> str:
31
+ sisa = "tanpa batas" if self.tanpa_batas else str(self.sisa_kredit)
32
+ return f"{self.kredit} kredit (sisa {sisa})"
33
+
34
+
35
+ @dataclass
36
+ class Balasan:
37
+ """Jawaban utuh dari satu percakapan."""
38
+
39
+ isi: str = ""
40
+ model: str = ""
41
+ bertanya_balik: bool = False
42
+ pemakaian: Pemakaian = field(default_factory=Pemakaian)
43
+ mentah: dict[str, Any] = field(default_factory=dict)
44
+
45
+ @classmethod
46
+ def dari(cls, data: dict[str, Any]) -> "Balasan":
47
+ # Terima dua bentuk: gaya Permerix dan gaya serupa OpenAI, supaya
48
+ # pustaka ini tetap bekerja bila pemanggil memakai jalur /completions.
49
+ balasan = data.get("balasan")
50
+ if isinstance(balasan, dict):
51
+ isi = str(balasan.get("isi") or "")
52
+ else:
53
+ pilihan = (data.get("choices") or [{}])[0]
54
+ isi = str((pilihan.get("message") or {}).get("content") or "")
55
+
56
+ return cls(
57
+ isi=isi,
58
+ model=str(data.get("model") or ""),
59
+ bertanya_balik=bool(data.get("bertanya_balik")),
60
+ pemakaian=Pemakaian.dari(data.get("pemakaian") or data.get("usage")),
61
+ mentah=data,
62
+ )
63
+
64
+ @property
65
+ def kredit(self) -> int:
66
+ return self.pemakaian.kredit
67
+
68
+ @property
69
+ def sisa_kredit(self) -> Optional[int]:
70
+ return self.pemakaian.sisa_kredit
71
+
72
+ def __str__(self) -> str:
73
+ return self.isi
74
+
75
+ def __repr__(self) -> str:
76
+ cuplik = self.isi[:48] + ("..." if len(self.isi) > 48 else "")
77
+ return f"<Balasan {self.pemakaian}: {cuplik!r}>"
78
+
79
+
80
+ @dataclass
81
+ class Potong:
82
+ """Satu kepingan jawaban saat memakai alir=True."""
83
+
84
+ isi: str = ""
85
+ selesai: bool = False
86
+ pemakaian: Optional[Pemakaian] = None
87
+
88
+ def __str__(self) -> str:
89
+ return self.isi
90
+
91
+ def __bool__(self) -> bool:
92
+ # Supaya `if potong:` berarti "ada isinya", bukan sekadar "objek ada".
93
+ return bool(self.isi)
@@ -0,0 +1,223 @@
1
+ """
2
+ Klien Permerix.
3
+
4
+ from permerix import Permerix
5
+
6
+ klien = Permerix(kunci="prx-live-xxxx")
7
+ print(klien.percakapan.kirim("apa itu kalimat efektif").isi)
8
+
9
+ Hanya memakai pustaka bawaan Python - tidak ada dependensi sama sekali,
10
+ jadi `pip install permerix` tidak menarik paket lain apa pun.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import os
17
+ import time
18
+ import urllib.error
19
+ import urllib.request
20
+ from typing import Any, Iterator, Optional, Sequence, Union
21
+
22
+ from ._versi import __version__
23
+ from .galat import GalatJaringan, GalatPermerix, dari_tanggapan
24
+ from .jenis import Balasan, Pemakaian, Potong
25
+
26
+ # ===========================================================================
27
+ # ALAMAT LAYANAN
28
+ #
29
+ # Nilai di bawah ini yang dipakai bila pemanggil tidak menyebutkan alamat.
30
+ # Beginilah pustaka seperti Groq bekerja: alamatnya tertanam di pustaka,
31
+ # sehingga pemakainya cukup menulis Permerix(kunci="...").
32
+ #
33
+ # SEBELUM MENERBITKAN KE PyPI, ganti dengan alamat layananmu yang sebenarnya
34
+ # (sebaiknya domain ber-HTTPS, bukan IP mentah).
35
+ # ===========================================================================
36
+ ALAMAT_RESMI = "http://localhost:8080"
37
+
38
+ MODEL_BAWAAN = "nusa-0.2"
39
+ TENGGANG_BAWAAN = 180.0
40
+ COBA_ULANG = 2 # berapa kali mencoba lagi bila jaringan gagal
41
+
42
+
43
+ def _alamat_bawaan() -> str:
44
+ """
45
+ Alamat yang dipakai bila tidak disebutkan.
46
+
47
+ Dibaca saat klien DIBUAT, bukan saat modul diimpor: kalau dibaca sekali di
48
+ awal, PERMERIX_API_URL yang dipasang belakangan - misalnya oleh skrip yang
49
+ memuat .env sesudah import - tidak akan pernah terpakai.
50
+ """
51
+ return os.environ.get("PERMERIX_API_URL") or ALAMAT_RESMI
52
+
53
+
54
+ Pesan = Union[str, Sequence[dict]]
55
+
56
+
57
+ def _bakukan_pesan(pesan: Pesan) -> list[dict]:
58
+ """Terima string biasa maupun daftar pesan bergaya percakapan."""
59
+ if isinstance(pesan, str):
60
+ return [{"peran": "pengguna", "isi": pesan}]
61
+ if isinstance(pesan, dict):
62
+ return [pesan]
63
+ return list(pesan)
64
+
65
+
66
+ class _Percakapan:
67
+ """Bagian percakapan dari klien: klien.percakapan.kirim(...)."""
68
+
69
+ def __init__(self, klien: "Permerix") -> None:
70
+ self._klien = klien
71
+
72
+ def kirim(self, pesan: Pesan, model: str = MODEL_BAWAAN,
73
+ **tambahan: Any) -> Balasan:
74
+ """Kirim percakapan lalu tunggu jawaban utuh."""
75
+ badan = {"model": model, "pesan": _bakukan_pesan(pesan), "alir": False}
76
+ badan.update(tambahan)
77
+ return Balasan.dari(self._klien._minta("POST", "/v1/chat", badan))
78
+
79
+ def alirkan(self, pesan: Pesan, model: str = MODEL_BAWAAN,
80
+ **tambahan: Any) -> Iterator[Potong]:
81
+ """Kirim percakapan lalu terima jawaban sepotong demi sepotong."""
82
+ badan = {"model": model, "pesan": _bakukan_pesan(pesan), "alir": True}
83
+ badan.update(tambahan)
84
+ return self._klien._alir("/v1/chat", badan)
85
+
86
+
87
+ class Permerix:
88
+ """
89
+ Klien layanan Permerix.
90
+
91
+ kunci : kunci API dari bot Telegram. Bila kosong, diambil dari peubah
92
+ lingkungan PERMERIX_API_KEY.
93
+ alamat : alamat layanan. Bila kosong, memakai PERMERIX_API_URL atau
94
+ alamat yang tertanam di pustaka.
95
+ tenggang: batas waktu menunggu jawaban, dalam detik.
96
+ """
97
+
98
+ def __init__(self, kunci: Optional[str] = None,
99
+ alamat: Optional[str] = None,
100
+ tenggang: float = TENGGANG_BAWAAN,
101
+ coba_ulang: int = COBA_ULANG) -> None:
102
+ self.kunci = (kunci or os.environ.get("PERMERIX_API_KEY") or "").strip()
103
+ if not self.kunci:
104
+ raise GalatPermerix(
105
+ "kunci API belum diisi. Ambil lewat bot Telegram, lalu "
106
+ "berikan dengan Permerix(kunci=\"prx-live-...\") atau pasang "
107
+ "peubah lingkungan PERMERIX_API_KEY.")
108
+
109
+ self.alamat = (alamat or _alamat_bawaan()).rstrip("/")
110
+ self.tenggang = tenggang
111
+ self.coba_ulang = max(0, int(coba_ulang))
112
+ self.percakapan = _Percakapan(self)
113
+
114
+ # ------------------------------------------------------------- jaringan
115
+ def _permintaan(self, metode: str, jalur: str,
116
+ badan: Optional[dict] = None) -> urllib.request.Request:
117
+ data = json.dumps(badan).encode("utf-8") if badan is not None else None
118
+ return urllib.request.Request(
119
+ self.alamat + jalur, data=data, method=metode,
120
+ headers={
121
+ "Authorization": f"Bearer {self.kunci}",
122
+ "Content-Type": "application/json",
123
+ "Accept": "application/json",
124
+ "User-Agent": f"permerix-python/{__version__}",
125
+ })
126
+
127
+ def _buka(self, metode: str, jalur: str, badan: Optional[dict] = None):
128
+ """Buka sambungan, ubah kegagalan HTTP jadi kesalahan yang jelas."""
129
+ galat_akhir: Optional[Exception] = None
130
+
131
+ for percobaan in range(self.coba_ulang + 1):
132
+ try:
133
+ return urllib.request.urlopen(
134
+ self._permintaan(metode, jalur, badan), timeout=self.tenggang)
135
+
136
+ except urllib.error.HTTPError as e:
137
+ pesan, jenis = f"HTTP {e.code}", ""
138
+ try:
139
+ isi = json.loads(e.read().decode("utf-8"))
140
+ keterangan = isi.get("galat") or {}
141
+ pesan = keterangan.get("pesan", pesan)
142
+ jenis = keterangan.get("jenis", "")
143
+ except Exception:
144
+ pass
145
+ # Kesalahan HTTP tidak diulang: server sudah menjawab dengan
146
+ # tegas, mengulanginya hanya membuang kredit dan waktu.
147
+ raise dari_tanggapan(e.code, pesan, jenis) from None
148
+
149
+ except urllib.error.URLError as e:
150
+ galat_akhir = e
151
+ except (TimeoutError, OSError) as e:
152
+ galat_akhir = e
153
+
154
+ # Hanya kegagalan jaringan yang diulang, dengan jeda menaik.
155
+ if percobaan < self.coba_ulang:
156
+ time.sleep(0.5 * (percobaan + 1))
157
+
158
+ alasan = getattr(galat_akhir, "reason", galat_akhir)
159
+ raise GalatJaringan(
160
+ f"tidak bisa menghubungi {self.alamat}: {alasan}") from None
161
+
162
+ def _minta(self, metode: str, jalur: str,
163
+ badan: Optional[dict] = None) -> dict:
164
+ with self._buka(metode, jalur, badan) as tanggapan:
165
+ teks = tanggapan.read().decode("utf-8")
166
+ try:
167
+ return json.loads(teks)
168
+ except json.JSONDecodeError:
169
+ raise GalatPermerix(
170
+ f"tanggapan tidak terbaca: {teks[:120]}") from None
171
+
172
+ def _alir(self, jalur: str, badan: dict) -> Iterator[Potong]:
173
+ # Aliran tidak diulang: sebagian jawaban mungkin sudah terkirim, dan
174
+ # mengulangnya akan menghasilkan jawaban ganda.
175
+ klien_alir = Permerix.__new__(Permerix)
176
+ klien_alir.__dict__.update(self.__dict__)
177
+ klien_alir.coba_ulang = 0
178
+
179
+ with klien_alir._buka("POST", jalur, badan) as tanggapan:
180
+ for baris_bita in tanggapan:
181
+ baris = baris_bita.decode("utf-8").strip()
182
+ if not baris.startswith("data:"):
183
+ continue
184
+ muatan = baris[5:].strip()
185
+ if muatan == "[DONE]":
186
+ return
187
+ try:
188
+ data = json.loads(muatan)
189
+ except json.JSONDecodeError:
190
+ continue
191
+
192
+ if data.get("selesai"):
193
+ yield Potong(selesai=True,
194
+ pemakaian=Pemakaian.dari(data.get("pemakaian")))
195
+ return
196
+ # Dua bentuk: gaya Permerix dan gaya serupa OpenAI.
197
+ isi = (data.get("potong") or {}).get("isi")
198
+ if isi is None:
199
+ pilihan = (data.get("choices") or [{}])[0]
200
+ isi = (pilihan.get("delta") or {}).get("content")
201
+ if isi:
202
+ yield Potong(isi=isi)
203
+
204
+ # --------------------------------------------------------------- guna
205
+ def model(self) -> list[dict]:
206
+ """Daftar model yang boleh dipakai kunci ini."""
207
+ return self._minta("GET", "/v1/model").get("data", [])
208
+
209
+ def kredit(self) -> dict:
210
+ """Sisa kredit dan ringkasan pemakaian."""
211
+ return self._minta("GET", "/v1/kredit")
212
+
213
+ def sehat(self) -> dict:
214
+ """Keadaan layanan. Tidak memerlukan kunci yang sah."""
215
+ return self._minta("GET", "/sehat")
216
+
217
+ # ------------------------------------------------------------- ringkas
218
+ def tanya(self, pesan: Pesan, model: str = MODEL_BAWAAN, **tambahan) -> str:
219
+ """Jalan pintas: kirim satu pesan, kembalikan teks jawabannya saja."""
220
+ return self.percakapan.kirim(pesan, model=model, **tambahan).isi
221
+
222
+ def __repr__(self) -> str:
223
+ return f"<Permerix {self.alamat} kunci=...{self.kunci[-6:]}>"
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: permerix
3
+ Version: 0.1.0
4
+ Summary: Pustaka Python resmi untuk layanan Permerix (model Nusa).
5
+ Author: Daffa Farid
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Daffa7382466/permerix
8
+ Project-URL: Repository, https://github.com/Daffa7382466/permerix
9
+ Project-URL: Issues, https://github.com/Daffa7382466/permerix/issues
10
+ Keywords: permerix,nusa,ai,bahasa-indonesia,nlp,sdk
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Natural Language :: Indonesian
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Dynamic: license-file
28
+
29
+ # Permerix
30
+
31
+ Pustaka Python resmi untuk layanan **Permerix**.
32
+
33
+ **Permerix** adalah nama layanannya; **Nusa** adalah nama model di dalamnya —
34
+ hubungannya seperti Claude dengan Opus.
35
+
36
+ Nusa adalah AI berbahasa Indonesia yang dibangun dari nol: mesin n-gram dan
37
+ peleburan graf kata, tanpa jaringan saraf. Ia menyusun kalimatnya sendiri dan
38
+ bisa mencari tahu ke internet ketika tidak tahu.
39
+
40
+ **Tanpa dependensi.** `pip install permerix` tidak menarik paket lain apa pun —
41
+ pustaka ini hanya memakai modul bawaan Python.
42
+
43
+ ---
44
+
45
+ ## Pasang
46
+
47
+ ```bash
48
+ pip install permerix
49
+ ```
50
+
51
+ ## Pakai
52
+
53
+ ```python
54
+ from permerix import Permerix
55
+
56
+ klien = Permerix(kunci="prx-live-xxxx")
57
+
58
+ print(klien.tanya("apa itu kalimat efektif"))
59
+ ```
60
+
61
+ Alamat layanan sudah tertanam di pustaka, jadi kamu tidak perlu mengaturnya.
62
+
63
+ ### Jawaban lengkap
64
+
65
+ ```python
66
+ balasan = klien.percakapan.kirim("apa itu pantun")
67
+
68
+ print(balasan.isi)
69
+ print(balasan.kredit) # kredit yang terpakai
70
+ print(balasan.sisa_kredit) # sisa; None berarti tanpa batas
71
+ ```
72
+
73
+ ### Mengalir
74
+
75
+ ```python
76
+ for potong in klien.percakapan.alirkan("ceritakan tentang pantun"):
77
+ print(potong.isi, end="", flush=True)
78
+ ```
79
+
80
+ ### Cek kredit dan model
81
+
82
+ ```python
83
+ klien.kredit() # {'kredit': 98, 'peran': 'user', 'pesan': 2, ...}
84
+ klien.model() # [{'id': 'nusa-0.2', 'nama': 'Nusa 0.2', ...}]
85
+ ```
86
+
87
+ ---
88
+
89
+ ## Dari baris perintah
90
+
91
+ ```bash
92
+ export PERMERIX_API_KEY=prx-live-xxxx
93
+
94
+ permerix "apa itu kalimat efektif"
95
+ permerix --alir "ceritakan tentang pantun"
96
+ permerix --kredit
97
+ permerix --daftar-model
98
+ ```
99
+
100
+ Atau tanpa memasang perintahnya: `python -m permerix "halo"`
101
+
102
+ ---
103
+
104
+ ## Mengambil kunci
105
+
106
+ Kunci API diambil lewat **bot Telegram** Permerix:
107
+
108
+ 1. Buka botnya, tekan `/start`
109
+ 2. Tekan tombol **Key** → **Buat Kunci**
110
+ 3. Salin kunci `prx-live-...`
111
+
112
+ Pengguna baru mendapat **100 kredit gratis**. Satu pesan biasanya 1 kredit,
113
+ naik sampai 5 bila Nusa perlu mencari ke internet.
114
+
115
+ ---
116
+
117
+ ## Pengaturan
118
+
119
+ | Peubah lingkungan | Guna |
120
+ |---|---|
121
+ | `PERMERIX_API_KEY` | kunci API, supaya tidak perlu ditulis di kode |
122
+ | `PERMERIX_API_URL` | alamat layanan; menimpa alamat bawaan pustaka |
123
+
124
+ ```python
125
+ # Semua pengaturan bisa juga diberikan langsung
126
+ klien = Permerix(
127
+ kunci="prx-live-xxxx",
128
+ alamat="https://api.contoh.com", # opsional
129
+ tenggang=180, # detik
130
+ coba_ulang=2, # ulangi bila jaringan gagal
131
+ )
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Menangani kesalahan
137
+
138
+ ```python
139
+ from permerix import Permerix, KreditHabis, TerlaluCepat, GalatPermerix
140
+
141
+ try:
142
+ print(klien.tanya("halo"))
143
+ except KreditHabis:
144
+ print("Kredit habis, isi ulang lewat bot Telegram.")
145
+ except TerlaluCepat as e:
146
+ print(f"Terlalu cepat, tunggu sebentar. {e.pesan}")
147
+ except GalatPermerix as e:
148
+ print(f"Gagal ({e.kode}): {e.pesan}")
149
+ ```
150
+
151
+ Semua kesalahan turunan `GalatPermerix`, jadi satu blok `except` sudah cukup.
152
+
153
+ | Kesalahan | Kapan |
154
+ |---|---|
155
+ | `GalatKunci` | kunci salah, tidak ada, atau sudah dicabut |
156
+ | `KreditHabis` | kredit sudah nol |
157
+ | `TerlaluCepat` | melewati batas laju |
158
+ | `ModelDitolak` | model belum dirilis atau perlu peran lebih tinggi |
159
+ | `GalatJaringan` | server tidak bisa dihubungi |
160
+ | `GalatLayanan` | server bermasalah di sisinya |
161
+
162
+ ---
163
+
164
+ ## Model
165
+
166
+ | Model | Keterangan |
167
+ |---|---|
168
+ | `nusa-0.2` | model bawaan, sudah dirilis |
169
+
170
+ Model berikutnya akan dibuka lebih dulu untuk pengguna Premium.
171
+
172
+ ---
173
+
174
+ ## Batas jujur
175
+
176
+ Nusa **bukan model bahasa besar**. Ia menyusun ulang kalimat yang pernah
177
+ dibacanya memakai n-gram dan peleburan graf kata, tanpa jaringan saraf dan
178
+ tanpa penalaran. Kalimatnya kadang masih pincang, terutama untuk pertanyaan
179
+ yang menuntut hafalan atau perhitungan berantai.
180
+
181
+ Yang dijaga: ia tidak menyalin mentah hasil pencarian, tidak membalik makna
182
+ kalimat, dan mengaku tidak tahu daripada mengarang.
183
+
184
+ ---
185
+
186
+ ## Lisensi
187
+
188
+ MIT
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ permerix/__init__.py
6
+ permerix/__main__.py
7
+ permerix/_versi.py
8
+ permerix/galat.py
9
+ permerix/jenis.py
10
+ permerix/klien.py
11
+ permerix.egg-info/PKG-INFO
12
+ permerix.egg-info/SOURCES.txt
13
+ permerix.egg-info/dependency_links.txt
14
+ permerix.egg-info/entry_points.txt
15
+ permerix.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ permerix = permerix.__main__:main
@@ -0,0 +1 @@
1
+ permerix
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "permerix"
7
+ dynamic = ["version"]
8
+ description = "Pustaka Python resmi untuk layanan Permerix (model Nusa)."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Daffa Farid" }]
13
+ keywords = ["permerix", "nusa", "ai", "bahasa-indonesia", "nlp", "sdk"]
14
+
15
+ # Tanpa dependencies sama sekali: pustaka ini hanya memakai modul bawaan
16
+ # Python. `pip install permerix` tidak akan menarik paket lain apa pun.
17
+ dependencies = []
18
+
19
+ classifiers = [
20
+ "Development Status :: 3 - Alpha",
21
+ "Intended Audience :: Developers",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Natural Language :: Indonesian",
24
+ "Operating System :: OS Independent",
25
+ "Programming Language :: Python :: 3",
26
+ "Programming Language :: Python :: 3.8",
27
+ "Programming Language :: Python :: 3.9",
28
+ "Programming Language :: Python :: 3.10",
29
+ "Programming Language :: Python :: 3.11",
30
+ "Programming Language :: Python :: 3.12",
31
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
32
+ "Topic :: Software Development :: Libraries :: Python Modules",
33
+ ]
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/Daffa7382466/permerix"
37
+ Repository = "https://github.com/Daffa7382466/permerix"
38
+ Issues = "https://github.com/Daffa7382466/permerix/issues"
39
+
40
+ [project.scripts]
41
+ permerix = "permerix.__main__:main"
42
+
43
+ [tool.setuptools]
44
+ # Hanya paket "permerix" yang ikut. Ditulis tegas, bukan otomatis, supaya
45
+ # folder lain di sekitarnya tidak pernah ikut terbawa ke paket.
46
+ packages = ["permerix"]
47
+
48
+ [tool.setuptools.dynamic]
49
+ version = { attr = "permerix._versi.__version__" }
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+