kiosapi 0.1.0 → 0.1.2

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
@@ -21,6 +21,31 @@ export async function fetchModels() {
21
21
  const body = (await res.json());
22
22
  return body.data ?? [];
23
23
  }
24
+ /** Latest published version of the CLI on npm (best-effort; null on failure/offline). */
25
+ export async function fetchLatestVersion(timeoutMs = 2500) {
26
+ try {
27
+ const ctrl = new AbortController();
28
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
29
+ const res = await fetch('https://registry.npmjs.org/kiosapi/latest', { signal: ctrl.signal });
30
+ clearTimeout(timer);
31
+ if (!res.ok)
32
+ return null;
33
+ const body = (await res.json());
34
+ return body.version ?? null;
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ /** Best-effort: does a model support tool calling? Defaults to true if unknown/offline. */
41
+ export async function modelSupportsTools(id) {
42
+ try {
43
+ return (await fetchModels()).find((m) => m.id === id)?.tools !== false;
44
+ }
45
+ catch {
46
+ return true;
47
+ }
48
+ }
24
49
  /** Pick the model to use: explicit flag → configured default → first free text model. */
25
50
  export async function resolveModel(flag) {
26
51
  if (flag)
package/dist/commands.js CHANGED
@@ -5,9 +5,10 @@ import { createInterface } from 'node:readline/promises';
5
5
  import { parseArgs } from 'node:util';
6
6
  import { runAgent } from './agent/run.js';
7
7
  import { runTeam } from './agent/team.js';
8
- import { createTopup, fetchBytesAuthed, fetchModels, fetchPemakaian, fetchSaldo, generateImage, pollJob, resolveMediaModel, resolveModel, streamChat, streamVision, submitVideo, } from './api.js';
8
+ import { createTopup, fetchBytesAuthed, fetchLatestVersion, fetchModels, fetchPemakaian, fetchSaldo, generateImage, modelSupportsTools, pollJob, resolveMediaModel, resolveModel, streamChat, streamVision, submitVideo, } from './api.js';
9
9
  import { clearKey, fileConfig, loadConfig, saveConfig } from './config.js';
10
- import { bold, cyan, dim, green, idn, promptHidden, readStdin, red, rupiah, sleep, thinking, yellow, } from './ui.js';
10
+ import { VERSION } from './help.js';
11
+ import { bold, cyan, dim, green, idn, prompt, promptHidden, readStdin, red, rupiah, sleep, thinking, yellow, } from './ui.js';
11
12
  const IMAGE_MIME = {
12
13
  '.png': 'image/png',
13
14
  '.jpg': 'image/jpeg',
@@ -48,19 +49,77 @@ export async function cmdPeriksa() {
48
49
  catch {
49
50
  console.log(red('tidak terhubung'));
50
51
  }
52
+ const latest = await fetchLatestVersion();
53
+ const upd = latest && isNewerVersion(latest, VERSION)
54
+ ? yellow(` → versi ${latest} tersedia (kiosapi perbarui)`)
55
+ : latest
56
+ ? green(' ✓ terbaru')
57
+ : '';
58
+ console.log(` Versi : ${VERSION}${upd}`);
51
59
  }
52
- /** model list available models (optionally filtered). */
60
+ /** Compare two dotted versions; true if `latest` is strictly newer than `current`. */
61
+ function isNewerVersion(latest, current) {
62
+ const a = latest.split('.').map((n) => Number(n) || 0);
63
+ const b = current.split('.').map((n) => Number(n) || 0);
64
+ for (let i = 0; i < 3; i++) {
65
+ if ((a[i] ?? 0) !== (b[i] ?? 0))
66
+ return (a[i] ?? 0) > (b[i] ?? 0);
67
+ }
68
+ return false;
69
+ }
70
+ /** perbarui — update the CLI to the latest npm version (or print the manual command). */
71
+ export async function cmdPerbarui() {
72
+ const latest = await fetchLatestVersion();
73
+ if (!latest) {
74
+ console.log(dim('Tak bisa cek versi terbaru (offline?).'));
75
+ console.log(`Update manual: ${cyan('npm i -g kiosapi@latest')}`);
76
+ return;
77
+ }
78
+ if (!isNewerVersion(latest, VERSION)) {
79
+ console.log(green(`✓ Sudah versi terbaru (${VERSION}).`));
80
+ return;
81
+ }
82
+ console.log(`Versi baru ${green(latest)} (sekarang ${VERSION}). Memperbarui…`);
83
+ const res = spawnSync('npm', ['i', '-g', `kiosapi@${latest}`], {
84
+ stdio: 'inherit',
85
+ shell: process.platform === 'win32',
86
+ });
87
+ if (res.status === 0) {
88
+ console.log(green(`✓ Terpasang kiosapi ${latest}.`));
89
+ }
90
+ else {
91
+ console.log(yellow('Gagal update otomatis. Jalankan manual:'));
92
+ console.log(` ${cyan('npm i -g kiosapi@latest')}`);
93
+ }
94
+ }
95
+ /** Once-a-day best-effort "versi baru tersedia" nudge (TTY only; cached in config). */
96
+ export async function maybeNotifyUpdate() {
97
+ if (!process.stdout.isTTY)
98
+ return;
99
+ const cfg = fileConfig();
100
+ const now = Date.now();
101
+ const DAY = 24 * 3_600_000;
102
+ let latest = cfg.latestVersion;
103
+ if (!cfg.updateCheckedAt || now - cfg.updateCheckedAt > DAY) {
104
+ latest = (await fetchLatestVersion(1500)) ?? undefined;
105
+ saveConfig({ updateCheckedAt: now, latestVersion: latest });
106
+ }
107
+ if (latest && isNewerVersion(latest, VERSION)) {
108
+ console.log(yellow(`Versi baru kiosapi ${latest} tersedia (sekarang ${VERSION}). Jalankan: kiosapi perbarui`));
109
+ }
110
+ }
111
+ /** model — list available models. `--cari q` filters; `--tools` shows only tool-capable models. */
53
112
  export async function cmdModel(args) {
54
113
  const { values } = parseArgs({
55
114
  args,
56
- options: { cari: { type: 'string', short: 'c' } },
115
+ options: { cari: { type: 'string', short: 'c' }, tools: { type: 'boolean' } },
57
116
  allowPositionals: true,
58
117
  });
59
118
  const q = (values.cari ?? '').toLowerCase();
60
119
  const models = await fetchModels();
61
- const rows = q
62
- ? models.filter((m) => m.id.toLowerCase().includes(q) || (m.provider ?? '').toLowerCase().includes(q))
63
- : models;
120
+ const rows = models
121
+ .filter((m) => !values.tools || m.tools)
122
+ .filter((m) => !q || m.id.toLowerCase().includes(q) || (m.provider ?? '').toLowerCase().includes(q));
64
123
  if (rows.length === 0) {
65
124
  console.log(dim('Tidak ada model yang cocok.'));
66
125
  return;
@@ -69,9 +128,16 @@ export async function cmdModel(args) {
69
128
  for (const m of rows) {
70
129
  const tier = (m.tier ?? '').padEnd(5);
71
130
  const kind = (m.kind ?? 'text').padEnd(5);
72
- console.log(`${m.id.padEnd(width)} ${dim(tier)} ${kind} ${dim(m.provider ?? '')}`);
131
+ const wrench = m.tools ? '🔧' : ' ';
132
+ console.log(`${wrench} ${m.id.padEnd(width)} ${dim(tier)} ${kind} ${dim(m.provider ?? '')}`);
133
+ }
134
+ console.log(dim(`\n${rows.length} model. 🔧 = mendukung tool calling (untuk agen).`));
135
+ }
136
+ /** Warn (best-effort) if the chosen model likely can't drive the agent's tools. */
137
+ export async function warnIfNoTools(model) {
138
+ if (!(await modelSupportsTools(model))) {
139
+ console.error(yellow(`⚠ Model "${model}" mungkin tak mendukung tool calling — agen bisa tak bekerja. Pilih model 🔧 (lihat: kiosapi model --tools), mis. anthropic/claude-sonnet-4-6.`));
73
140
  }
74
- console.log(dim(`\n${rows.length} model.`));
75
141
  }
76
142
  /** tanya — one-shot streaming question (also reads piped stdin as context). */
77
143
  export async function cmdTanya(args) {
@@ -164,23 +230,58 @@ export async function cmdNgobrol(args) {
164
230
  rl.close();
165
231
  }
166
232
  }
167
- /** setel — read/write persisted settings. */
168
- export function cmdSetel(args) {
233
+ /**
234
+ * Interactive model picker: list text models (optionally filtered), choose by number. Returns the
235
+ * chosen id, or null if cancelled. Shared by `/model` (session) and `kiosapi setel model`.
236
+ */
237
+ export async function pickModel(filter, current) {
238
+ const q = (filter ?? '').toLowerCase();
239
+ const models = (await fetchModels())
240
+ .filter((m) => (m.kind ?? 'text') === 'text')
241
+ .filter((m) => !q || m.id.toLowerCase().includes(q) || (m.provider ?? '').toLowerCase().includes(q));
242
+ if (models.length === 0) {
243
+ console.log(dim(q ? `Tidak ada model cocok "${filter}".` : 'Tidak ada model.'));
244
+ return null;
245
+ }
246
+ models.forEach((m, i) => {
247
+ const mark = m.id === current ? green('●') : ' ';
248
+ const wrench = m.tools ? '🔧' : ' ';
249
+ console.log(` ${String(i + 1).padStart(2)}. ${mark} ${wrench} ${m.id} ${dim(m.tier ?? '')}`);
250
+ });
251
+ const ans = (await prompt('Pilih nomor (Enter = batal): ')).trim();
252
+ if (!ans)
253
+ return null;
254
+ const pick = models[Number(ans) - 1];
255
+ if (!pick) {
256
+ console.log(red('Nomor tidak valid.'));
257
+ return null;
258
+ }
259
+ return pick.id;
260
+ }
261
+ /** setel — read/write persisted settings. `setel model` with no value opens the picker. */
262
+ export async function cmdSetel(args) {
169
263
  const [key, ...rest] = args;
170
264
  if (!key) {
171
265
  console.log(JSON.stringify(fileConfig(), null, 2));
172
266
  return;
173
267
  }
174
268
  const value = rest.join(' ').trim();
175
- if (!value)
176
- throw new Error('Beri nilai. Contoh: kiosapi setel model deepseek/deepseek-v3');
177
- if (key === 'model' || key === 'model-default')
178
- saveConfig({ defaultModel: value });
179
- else if (key === 'base-url' || key === 'url')
269
+ if (key === 'model' || key === 'model-default') {
270
+ const chosen = value || (await pickModel(undefined, fileConfig().defaultModel));
271
+ if (!chosen)
272
+ return;
273
+ saveConfig({ defaultModel: chosen });
274
+ console.log(green(`✓ model = ${chosen}`));
275
+ return;
276
+ }
277
+ if (key === 'base-url' || key === 'url') {
278
+ if (!value)
279
+ throw new Error('Beri nilai. Contoh: kiosapi setel base-url https://api.kiosapi.id');
180
280
  saveConfig({ baseUrl: value });
181
- else
182
- throw new Error(`Kunci tidak dikenal: ${key} (gunakan: model | base-url)`);
183
- console.log(green(`✓ ${key} = ${value}`));
281
+ console.log(green(`✓ base-url = ${value}`));
282
+ return;
283
+ }
284
+ throw new Error(`Kunci tidak dikenal: ${key} (gunakan: model | base-url)`);
184
285
  }
185
286
  /** Shared driver for the agent modes (rencana/edit/buat). */
186
287
  async function runAgentCommand(args, mode) {
@@ -193,6 +294,7 @@ async function runAgentCommand(args, mode) {
193
294
  if (!task)
194
295
  throw new Error('Beri tugas. Contoh: kiosapi buat "bikin REST API Express + tes"');
195
296
  const model = await resolveModel(values.model);
297
+ await warnIfNoTools(model);
196
298
  await runAgent(task, mode, { model, otomatis: Boolean(values.otomatis) });
197
299
  }
198
300
  /** rencana — read-only: explore the codebase and produce a plan. */
@@ -313,6 +415,7 @@ export async function cmdTim(args) {
313
415
  if (!task)
314
416
  throw new Error('Beri tugas. Contoh: kiosapi tim "bikin endpoint /health + tes"');
315
417
  const model = await resolveModel(values.model);
418
+ await warnIfNoTools(model);
316
419
  await runTeam(task, { model, otomatis: Boolean(values.otomatis) });
317
420
  }
318
421
  /** saldo — show own balance, bonus tokens, and month-to-date spend. */
package/dist/help.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { bold, dim } from './ui.js';
2
- export const VERSION = '0.1.0';
2
+ export const VERSION = '0.1.2';
3
3
  export function printVersion() {
4
4
  console.log(`kiosapi ${VERSION}`);
5
5
  }
@@ -12,8 +12,9 @@ ${bold('Penggunaan:')} kiosapi [perintah] [opsi]
12
12
  ${bold('Perintah:')}
13
13
  masuk Simpan API key (kios_live_…)
14
14
  keluar Hapus API key tersimpan
15
- periksa Cek konektivitas, key, & setelan
16
- model [--cari q] Daftar model yang tersedia
15
+ periksa Cek konektivitas, key, versi & setelan
16
+ perbarui Update CLI ke versi npm terbaru
17
+ model [--cari q] Daftar model (--tools = hanya yang mendukung tool/agen; 🔧)
17
18
  tanya "…" Tanya sekali (streaming); dukung pipe stdin
18
19
  ngobrol Mode percakapan interaktif (REPL)
19
20
  setel <k> <v> Atur setelan (model | base-url)
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { cmdBuat, cmdEdit, cmdGambar, cmdIsi, cmdKeluar, cmdLihat, cmdMasuk, cmdModel, cmdNgobrol, cmdPakai, cmdPeriksa, cmdRencana, cmdSaldo, cmdSambung, cmdSetel, cmdTanya, cmdTim, cmdVideo, } from './commands.js';
2
+ import { cmdBuat, cmdEdit, cmdGambar, cmdIsi, cmdKeluar, cmdLihat, cmdMasuk, cmdModel, cmdNgobrol, cmdPakai, cmdPerbarui, cmdPeriksa, cmdRencana, cmdSaldo, cmdSambung, cmdSetel, cmdTanya, cmdTim, cmdVideo, } from './commands.js';
3
3
  import { printHelp, printVersion } from './help.js';
4
4
  import { startSession } from './session.js';
5
5
  import { red } from './ui.js';
@@ -11,6 +11,8 @@ const COMMANDS = {
11
11
  logout: cmdKeluar,
12
12
  periksa: cmdPeriksa,
13
13
  doctor: cmdPeriksa,
14
+ perbarui: cmdPerbarui,
15
+ update: cmdPerbarui,
14
16
  model: cmdModel,
15
17
  models: cmdModel,
16
18
  tanya: cmdTanya,
package/dist/session.js CHANGED
@@ -2,7 +2,7 @@ import { createInterface } from 'node:readline/promises';
2
2
  import { newSession, resetSession, runTurn } from './agent/run.js';
3
3
  import { runTeam } from './agent/team.js';
4
4
  import { resolveModel } from './api.js';
5
- import { cmdGambar, cmdIsi, cmdLihat, cmdMasuk, cmdPakai, cmdSaldo, cmdVideo } from './commands.js';
5
+ import { cmdGambar, cmdIsi, cmdLihat, cmdMasuk, cmdPakai, cmdSaldo, cmdVideo, maybeNotifyUpdate, pickModel, warnIfNoTools, } from './commands.js';
6
6
  import { loadConfig } from './config.js';
7
7
  import { bold, cyan, dim, green, red } from './ui.js';
8
8
  const MODES = ['rencana', 'edit', 'buat'];
@@ -24,7 +24,7 @@ function slashHelp() {
24
24
  /video <prompt> Buat video → file
25
25
  /lihat <file> <pertanyaan> Tanya model vision tentang gambar
26
26
  /mode [rencana|edit|buat] Lihat/ubah mode agen
27
- /model [id] Lihat/ubah model
27
+ /model [id|filter] Pilih model (tanpa argumen = daftar bernomor)
28
28
  /otomatis Hidup/matikan auto-approve (lewati konfirmasi)
29
29
  /bersih Bersihkan riwayat percakapan
30
30
  /saldo Lihat saldo
@@ -64,14 +64,17 @@ async function runSlash(line, s) {
64
64
  return false;
65
65
  }
66
66
  case 'model': {
67
- const id = rest[0];
68
- if (id) {
69
- s.model = id;
70
- console.log(dim(`Model: ${s.model}`));
67
+ const arg = rest.join(' ').trim();
68
+ // A full id (has "/") is set directly; otherwise open the numbered picker (filtered by arg).
69
+ if (arg.includes('/')) {
70
+ s.model = arg;
71
71
  }
72
72
  else {
73
- console.log(dim(`Model: ${s.model}`));
73
+ const picked = await pickModel(arg || undefined, s.model);
74
+ if (picked)
75
+ s.model = picked;
74
76
  }
77
+ console.log(dim(`Model: ${s.model}`));
75
78
  return false;
76
79
  }
77
80
  case 'otomatis':
@@ -131,9 +134,11 @@ export async function startSession() {
131
134
  if (!loadConfig().apiKey)
132
135
  return;
133
136
  }
137
+ await maybeNotifyUpdate();
134
138
  const model = await resolveModel(undefined);
135
139
  const s = newSession(model, 'buat', false);
136
140
  banner(s);
141
+ await warnIfNoTools(model);
137
142
  const rl = createInterface({ input: process.stdin, output: process.stdout });
138
143
  try {
139
144
  while (true) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kiosapi",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "CLI Kiosapi.id berbahasa Indonesia — bangun aplikasimu pakai API key Kiosapi (agen + multimodal).",
6
6
  "keywords": [