tokengratis 0.1.0 → 0.1.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.
- package/README.md +3 -0
- package/package.json +1 -1
- package/src/cli.mjs +42 -13
package/README.md
CHANGED
|
@@ -54,6 +54,9 @@ tokengratis search llama
|
|
|
54
54
|
tokengratis models --provider groq
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
- `--refresh` (alias `--no-cache`) pada perintah apa pun akan mengabaikan cache
|
|
58
|
+
lokal dan mengambil ulang dari server. Output teks selalu menampilkan umur
|
|
59
|
+
datanya, jadi cache basi tidak pernah tersaji diam-diam.
|
|
57
60
|
- Plain text output by default; add `--json` to any command for
|
|
58
61
|
machine-readable output.
|
|
59
62
|
- Color is auto-disabled when stdout isn't a TTY, or when `NO_COLOR` is set.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokengratis",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "CLI + MCP server for tokengratis.id, the aggregator directory of free-tier & free-credit LLM APIs for Indonesian developers. Zero runtime dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tokengratis",
|
package/src/cli.mjs
CHANGED
|
@@ -29,6 +29,10 @@ Usage:
|
|
|
29
29
|
tokengratis --help
|
|
30
30
|
tokengratis --version
|
|
31
31
|
|
|
32
|
+
Flag global:
|
|
33
|
+
--json output machine-readable
|
|
34
|
+
--refresh abaikan cache, ambil ulang dari server (alias: --no-cache)
|
|
35
|
+
|
|
32
36
|
Contoh:
|
|
33
37
|
tokengratis list
|
|
34
38
|
tokengratis list --modality vision
|
|
@@ -38,11 +42,29 @@ Contoh:
|
|
|
38
42
|
tokengratis models --provider groq
|
|
39
43
|
|
|
40
44
|
Data live dari https://tokengratis.id/api/providers
|
|
41
|
-
(fallback otomatis ke GitHub raw kalau API down). Cached lokal ~1 jam
|
|
45
|
+
(fallback otomatis ke GitHub raw kalau API down). Cached lokal ~1 jam —
|
|
46
|
+
umur data selalu ditulis di bawah output; pakai --refresh buat maksa segar.
|
|
42
47
|
|
|
43
48
|
Ini AGGREGATOR, bukan verifier — tiap provider bawa provenance
|
|
44
49
|
(sources[] + syncedAt), transparan apa adanya dari sumber.`;
|
|
45
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Baris umur data. Sengaja SELALU ditampilkan di output teks.
|
|
53
|
+
*
|
|
54
|
+
* Kenapa: data di-cache 1 jam di temp dir. Pas data upstream diperbaiki
|
|
55
|
+
* (INCIDENT 2026-07-25), siapa pun yang sempat jalanin CLI sebelum perbaikan
|
|
56
|
+
* bakal terus ngeliat angka lama sampai satu jam — dan sebelumnya ga ada satu
|
|
57
|
+
* pun petunjuk bahwa yang dia liat itu cache. Diam-diam nyajiin data basi itu
|
|
58
|
+
* pelanggaran prinsip proyek ini (selalu tunjukin KAPAN datanya). Sekarang
|
|
59
|
+
* umurnya kelihatan, dan --refresh kasih jalan keluar.
|
|
60
|
+
*/
|
|
61
|
+
function ageNote(fetchedAt, color) {
|
|
62
|
+
const mins = Math.max(0, Math.round((Date.now() - fetchedAt) / 60000));
|
|
63
|
+
const when = mins < 1 ? "barusan" : mins === 1 ? "1 menit lalu" : `${mins} menit lalu`;
|
|
64
|
+
const hint = mins >= 1 ? " · paksa segarkan: --refresh" : "";
|
|
65
|
+
return color.dim(`Data diambil ${when}${hint}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
46
68
|
function getFlagValue(args, name) {
|
|
47
69
|
const idx = args.indexOf(name);
|
|
48
70
|
if (idx === -1 || idx === args.length - 1) return undefined;
|
|
@@ -65,10 +87,10 @@ function positionals(args, valueFlags) {
|
|
|
65
87
|
return out;
|
|
66
88
|
}
|
|
67
89
|
|
|
68
|
-
async function cmdList(args, { json, color }) {
|
|
90
|
+
async function cmdList(args, { json, color, refresh }) {
|
|
69
91
|
const modality = getFlagValue(args, "--modality");
|
|
70
92
|
const minContext = getFlagValue(args, "--min-context");
|
|
71
|
-
const { providers } = await getProviders();
|
|
93
|
+
const { providers, fetchedAt } = await getProviders({ forceRefresh: refresh });
|
|
72
94
|
|
|
73
95
|
let list = providers;
|
|
74
96
|
list = filterByModality(list, modality);
|
|
@@ -85,16 +107,17 @@ async function cmdList(args, { json, color }) {
|
|
|
85
107
|
console.log(formatProviderListText(list, { color }));
|
|
86
108
|
console.log("");
|
|
87
109
|
console.log(color.dim(`${list.length} provider. Detail: tokengratis show <slug>`));
|
|
110
|
+
console.log(ageNote(fetchedAt, color));
|
|
88
111
|
}
|
|
89
112
|
|
|
90
|
-
async function cmdShow(args, { json, color }) {
|
|
113
|
+
async function cmdShow(args, { json, color, refresh }) {
|
|
91
114
|
const [slug] = positionals(args, ["--modality", "--min-context", "--provider"]);
|
|
92
115
|
if (!slug) {
|
|
93
116
|
console.error("Error: slug wajib diisi. Contoh: tokengratis show openrouter");
|
|
94
117
|
process.exitCode = 1;
|
|
95
118
|
return;
|
|
96
119
|
}
|
|
97
|
-
const { providers } = await getProviders();
|
|
120
|
+
const { providers, fetchedAt } = await getProviders({ forceRefresh: refresh });
|
|
98
121
|
const p = providers.find((x) => x.slug === slug);
|
|
99
122
|
if (!p) {
|
|
100
123
|
console.error(`Error: provider dengan slug "${slug}" tidak ditemukan. Coba: tokengratis list`);
|
|
@@ -106,16 +129,18 @@ async function cmdShow(args, { json, color }) {
|
|
|
106
129
|
return;
|
|
107
130
|
}
|
|
108
131
|
console.log(formatProviderText(p, { color }));
|
|
132
|
+
console.log("");
|
|
133
|
+
console.log(ageNote(fetchedAt, color));
|
|
109
134
|
}
|
|
110
135
|
|
|
111
|
-
async function cmdSearch(args, { json, color }) {
|
|
136
|
+
async function cmdSearch(args, { json, color, refresh }) {
|
|
112
137
|
const query = positionals(args, ["--modality", "--min-context", "--provider"]).join(" ").trim();
|
|
113
138
|
if (!query) {
|
|
114
139
|
console.error('Error: query wajib diisi. Contoh: tokengratis search "llama"');
|
|
115
140
|
process.exitCode = 1;
|
|
116
141
|
return;
|
|
117
142
|
}
|
|
118
|
-
const { providers } = await getProviders();
|
|
143
|
+
const { providers, fetchedAt } = await getProviders({ forceRefresh: refresh });
|
|
119
144
|
const results = searchProviders(providers, query);
|
|
120
145
|
if (json) {
|
|
121
146
|
console.log(
|
|
@@ -133,11 +158,13 @@ async function cmdSearch(args, { json, color }) {
|
|
|
133
158
|
return;
|
|
134
159
|
}
|
|
135
160
|
console.log(formatSearchResultsText(results, query, { color }));
|
|
161
|
+
console.log("");
|
|
162
|
+
console.log(ageNote(fetchedAt, color));
|
|
136
163
|
}
|
|
137
164
|
|
|
138
|
-
async function cmdModels(args, { json, color }) {
|
|
165
|
+
async function cmdModels(args, { json, color, refresh }) {
|
|
139
166
|
const providerSlug = getFlagValue(args, "--provider");
|
|
140
|
-
const { providers } = await getProviders();
|
|
167
|
+
const { providers, fetchedAt } = await getProviders({ forceRefresh: refresh });
|
|
141
168
|
const rows = listModels(providers, providerSlug);
|
|
142
169
|
if (json) {
|
|
143
170
|
console.log(JSON.stringify(rows, null, 2));
|
|
@@ -146,6 +173,7 @@ async function cmdModels(args, { json, color }) {
|
|
|
146
173
|
console.log(formatModelListText(rows, { color }));
|
|
147
174
|
console.log("");
|
|
148
175
|
console.log(color.dim(`${rows.length} model.`));
|
|
176
|
+
console.log(ageNote(fetchedAt, color));
|
|
149
177
|
}
|
|
150
178
|
|
|
151
179
|
async function main(argv) {
|
|
@@ -162,20 +190,21 @@ async function main(argv) {
|
|
|
162
190
|
|
|
163
191
|
const [command, ...rest] = args;
|
|
164
192
|
const json = args.includes("--json");
|
|
193
|
+
const refresh = args.includes("--refresh") || args.includes("--no-cache");
|
|
165
194
|
const color = createColorizer();
|
|
166
195
|
|
|
167
196
|
switch (command) {
|
|
168
197
|
case "list":
|
|
169
|
-
await cmdList(rest, { json, color });
|
|
198
|
+
await cmdList(rest, { json, color, refresh });
|
|
170
199
|
break;
|
|
171
200
|
case "show":
|
|
172
|
-
await cmdShow(rest, { json, color });
|
|
201
|
+
await cmdShow(rest, { json, color, refresh });
|
|
173
202
|
break;
|
|
174
203
|
case "search":
|
|
175
|
-
await cmdSearch(rest, { json, color });
|
|
204
|
+
await cmdSearch(rest, { json, color, refresh });
|
|
176
205
|
break;
|
|
177
206
|
case "models":
|
|
178
|
-
await cmdModels(rest, { json, color });
|
|
207
|
+
await cmdModels(rest, { json, color, refresh });
|
|
179
208
|
break;
|
|
180
209
|
default:
|
|
181
210
|
console.error(`Perintah tidak dikenal: "${command}"\n`);
|