kiosapi 0.1.6 → 0.1.7

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/dist/api.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { loadConfig } from './config.js';
2
- import { dim } from './ui.js';
2
+ import { dim, sleep } from './ui.js';
3
3
  /** Turn an upstream HTTP status into an actionable Indonesian message. */
4
4
  function humanizeError(status) {
5
5
  if (status === 401)
@@ -61,22 +61,45 @@ export async function resolveModel(flag) {
61
61
  process.stderr.write(`${dim(`(model otomatis: ${pick.id} — atur tetap dengan: kiosapi setel model <id>)`)}\n`);
62
62
  return pick.id;
63
63
  }
64
- /** Authenticated request to the gateway with a humanized error on failure. */
64
+ /** Prefer the gateway's own (localized) error message; fall back to a status-based one. */
65
+ async function gatewayError(res) {
66
+ try {
67
+ const body = (await res.json());
68
+ if (body?.error?.message)
69
+ return body.error.message;
70
+ }
71
+ catch {
72
+ // non-JSON body
73
+ }
74
+ return humanizeError(res.status);
75
+ }
76
+ const RETRY_BACKOFF_MS = [5000, 10000, 20000];
77
+ /**
78
+ * Authenticated request to the gateway. Retries 429 (rate limit) with a silent backoff — agent/`tim`
79
+ * runs burst many requests and easily hit the per-minute cap, so a brief wait + retry keeps them
80
+ * going instead of failing. On other errors, surfaces the gateway's own message.
81
+ */
65
82
  async function authedFetch(path, init) {
66
- const { baseUrl, apiKey } = loadConfig();
67
- if (!apiKey)
68
- throw new Error('Belum masuk. Jalankan: kiosapi masuk');
69
- const res = await fetch(`${baseUrl}${path}`, {
70
- ...init,
71
- headers: {
72
- Authorization: `Bearer ${apiKey}`,
73
- 'Content-Type': 'application/json',
74
- ...init?.headers,
75
- },
76
- });
77
- if (!res.ok)
78
- throw new Error(humanizeError(res.status));
79
- return res;
83
+ for (let attempt = 0;; attempt++) {
84
+ const { baseUrl, apiKey } = loadConfig();
85
+ if (!apiKey)
86
+ throw new Error('Belum masuk. Jalankan: kiosapi masuk');
87
+ const res = await fetch(`${baseUrl}${path}`, {
88
+ ...init,
89
+ headers: {
90
+ Authorization: `Bearer ${apiKey}`,
91
+ 'Content-Type': 'application/json',
92
+ ...init?.headers,
93
+ },
94
+ });
95
+ if (res.ok)
96
+ return res;
97
+ if (res.status === 429 && attempt < RETRY_BACKOFF_MS.length) {
98
+ await sleep(RETRY_BACKOFF_MS[attempt] ?? 20000);
99
+ continue;
100
+ }
101
+ throw new Error(await gatewayError(res));
102
+ }
80
103
  }
81
104
  /** GET /v1/saldo — own balance summary. */
82
105
  export async function fetchSaldo() {
package/dist/commands.js CHANGED
@@ -436,10 +436,9 @@ export async function cmdTim(args) {
436
436
  /** saldo — show own balance, bonus tokens, and month-to-date spend. */
437
437
  export async function cmdSaldo() {
438
438
  const s = await fetchSaldo();
439
- console.log(`${bold('Saldo')} : ${green(rupiah(s.balance_rupiah))}`);
440
- console.log(`Bonus token : ${idn(s.bonus_tokens)}`);
439
+ console.log(`${bold('Saldo')} : ${green(rupiah(s.balance_rupiah))}`);
441
440
  const cap = s.monthly_cap_rupiah != null ? ` / batas ${rupiah(s.monthly_cap_rupiah)}` : '';
442
- console.log(`Bulan ini : ${rupiah(s.month_spend_rupiah)}${dim(cap)}`);
441
+ console.log(`Bulan ini : ${rupiah(s.month_spend_rupiah)}${dim(cap)}`);
443
442
  }
444
443
  /** pakai — usage summary over the last N days (default 30). */
445
444
  export async function cmdPakai(args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kiosapi",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "description": "CLI Kiosapi.id berbahasa Indonesia — bangun aplikasimu pakai API key Kiosapi (agen + multimodal).",
6
6
  "keywords": [