kiosapi 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/dist/api.js CHANGED
@@ -21,6 +21,15 @@ export async function fetchModels() {
21
21
  const body = (await res.json());
22
22
  return body.data ?? [];
23
23
  }
24
+ /** Best-effort: does a model support tool calling? Defaults to true if unknown/offline. */
25
+ export async function modelSupportsTools(id) {
26
+ try {
27
+ return (await fetchModels()).find((m) => m.id === id)?.tools !== false;
28
+ }
29
+ catch {
30
+ return true;
31
+ }
32
+ }
24
33
  /** Pick the model to use: explicit flag → configured default → first free text model. */
25
34
  export async function resolveModel(flag) {
26
35
  if (flag)
package/dist/commands.js CHANGED
@@ -5,9 +5,9 @@ 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, 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 { bold, cyan, dim, green, idn, prompt, promptHidden, readStdin, red, rupiah, sleep, thinking, yellow, } from './ui.js';
11
11
  const IMAGE_MIME = {
12
12
  '.png': 'image/png',
13
13
  '.jpg': 'image/jpeg',
@@ -49,18 +49,18 @@ export async function cmdPeriksa() {
49
49
  console.log(red('tidak terhubung'));
50
50
  }
51
51
  }
52
- /** model — list available models (optionally filtered). */
52
+ /** model — list available models. `--cari q` filters; `--tools` shows only tool-capable models. */
53
53
  export async function cmdModel(args) {
54
54
  const { values } = parseArgs({
55
55
  args,
56
- options: { cari: { type: 'string', short: 'c' } },
56
+ options: { cari: { type: 'string', short: 'c' }, tools: { type: 'boolean' } },
57
57
  allowPositionals: true,
58
58
  });
59
59
  const q = (values.cari ?? '').toLowerCase();
60
60
  const models = await fetchModels();
61
- const rows = q
62
- ? models.filter((m) => m.id.toLowerCase().includes(q) || (m.provider ?? '').toLowerCase().includes(q))
63
- : models;
61
+ const rows = models
62
+ .filter((m) => !values.tools || m.tools)
63
+ .filter((m) => !q || m.id.toLowerCase().includes(q) || (m.provider ?? '').toLowerCase().includes(q));
64
64
  if (rows.length === 0) {
65
65
  console.log(dim('Tidak ada model yang cocok.'));
66
66
  return;
@@ -69,9 +69,16 @@ export async function cmdModel(args) {
69
69
  for (const m of rows) {
70
70
  const tier = (m.tier ?? '').padEnd(5);
71
71
  const kind = (m.kind ?? 'text').padEnd(5);
72
- console.log(`${m.id.padEnd(width)} ${dim(tier)} ${kind} ${dim(m.provider ?? '')}`);
72
+ const wrench = m.tools ? '🔧' : ' ';
73
+ console.log(`${wrench} ${m.id.padEnd(width)} ${dim(tier)} ${kind} ${dim(m.provider ?? '')}`);
74
+ }
75
+ console.log(dim(`\n${rows.length} model. 🔧 = mendukung tool calling (untuk agen).`));
76
+ }
77
+ /** Warn (best-effort) if the chosen model likely can't drive the agent's tools. */
78
+ export async function warnIfNoTools(model) {
79
+ if (!(await modelSupportsTools(model))) {
80
+ 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
81
  }
74
- console.log(dim(`\n${rows.length} model.`));
75
82
  }
76
83
  /** tanya — one-shot streaming question (also reads piped stdin as context). */
77
84
  export async function cmdTanya(args) {
@@ -164,23 +171,58 @@ export async function cmdNgobrol(args) {
164
171
  rl.close();
165
172
  }
166
173
  }
167
- /** setel — read/write persisted settings. */
168
- export function cmdSetel(args) {
174
+ /**
175
+ * Interactive model picker: list text models (optionally filtered), choose by number. Returns the
176
+ * chosen id, or null if cancelled. Shared by `/model` (session) and `kiosapi setel model`.
177
+ */
178
+ export async function pickModel(filter, current) {
179
+ const q = (filter ?? '').toLowerCase();
180
+ const models = (await fetchModels())
181
+ .filter((m) => (m.kind ?? 'text') === 'text')
182
+ .filter((m) => !q || m.id.toLowerCase().includes(q) || (m.provider ?? '').toLowerCase().includes(q));
183
+ if (models.length === 0) {
184
+ console.log(dim(q ? `Tidak ada model cocok "${filter}".` : 'Tidak ada model.'));
185
+ return null;
186
+ }
187
+ models.forEach((m, i) => {
188
+ const mark = m.id === current ? green('●') : ' ';
189
+ const wrench = m.tools ? '🔧' : ' ';
190
+ console.log(` ${String(i + 1).padStart(2)}. ${mark} ${wrench} ${m.id} ${dim(m.tier ?? '')}`);
191
+ });
192
+ const ans = (await prompt('Pilih nomor (Enter = batal): ')).trim();
193
+ if (!ans)
194
+ return null;
195
+ const pick = models[Number(ans) - 1];
196
+ if (!pick) {
197
+ console.log(red('Nomor tidak valid.'));
198
+ return null;
199
+ }
200
+ return pick.id;
201
+ }
202
+ /** setel — read/write persisted settings. `setel model` with no value opens the picker. */
203
+ export async function cmdSetel(args) {
169
204
  const [key, ...rest] = args;
170
205
  if (!key) {
171
206
  console.log(JSON.stringify(fileConfig(), null, 2));
172
207
  return;
173
208
  }
174
209
  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')
210
+ if (key === 'model' || key === 'model-default') {
211
+ const chosen = value || (await pickModel(undefined, fileConfig().defaultModel));
212
+ if (!chosen)
213
+ return;
214
+ saveConfig({ defaultModel: chosen });
215
+ console.log(green(`✓ model = ${chosen}`));
216
+ return;
217
+ }
218
+ if (key === 'base-url' || key === 'url') {
219
+ if (!value)
220
+ throw new Error('Beri nilai. Contoh: kiosapi setel base-url https://api.kiosapi.id');
180
221
  saveConfig({ baseUrl: value });
181
- else
182
- throw new Error(`Kunci tidak dikenal: ${key} (gunakan: model | base-url)`);
183
- console.log(green(`✓ ${key} = ${value}`));
222
+ console.log(green(`✓ base-url = ${value}`));
223
+ return;
224
+ }
225
+ throw new Error(`Kunci tidak dikenal: ${key} (gunakan: model | base-url)`);
184
226
  }
185
227
  /** Shared driver for the agent modes (rencana/edit/buat). */
186
228
  async function runAgentCommand(args, mode) {
@@ -193,6 +235,7 @@ async function runAgentCommand(args, mode) {
193
235
  if (!task)
194
236
  throw new Error('Beri tugas. Contoh: kiosapi buat "bikin REST API Express + tes"');
195
237
  const model = await resolveModel(values.model);
238
+ await warnIfNoTools(model);
196
239
  await runAgent(task, mode, { model, otomatis: Boolean(values.otomatis) });
197
240
  }
198
241
  /** rencana — read-only: explore the codebase and produce a plan. */
@@ -313,6 +356,7 @@ export async function cmdTim(args) {
313
356
  if (!task)
314
357
  throw new Error('Beri tugas. Contoh: kiosapi tim "bikin endpoint /health + tes"');
315
358
  const model = await resolveModel(values.model);
359
+ await warnIfNoTools(model);
316
360
  await runTeam(task, { model, otomatis: Boolean(values.otomatis) });
317
361
  }
318
362
  /** 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.1';
3
3
  export function printVersion() {
4
4
  console.log(`kiosapi ${VERSION}`);
5
5
  }
@@ -13,7 +13,7 @@ ${bold('Perintah:')}
13
13
  masuk Simpan API key (kios_live_…)
14
14
  keluar Hapus API key tersimpan
15
15
  periksa Cek konektivitas, key, & setelan
16
- model [--cari q] Daftar model yang tersedia
16
+ model [--cari q] Daftar model (--tools = hanya yang mendukung tool/agen; 🔧)
17
17
  tanya "…" Tanya sekali (streaming); dukung pipe stdin
18
18
  ngobrol Mode percakapan interaktif (REPL)
19
19
  setel <k> <v> Atur setelan (model | base-url)
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, 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':
@@ -134,6 +137,7 @@ export async function startSession() {
134
137
  const model = await resolveModel(undefined);
135
138
  const s = newSession(model, 'buat', false);
136
139
  banner(s);
140
+ await warnIfNoTools(model);
137
141
  const rl = createInterface({ input: process.stdin, output: process.stdout });
138
142
  try {
139
143
  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.1",
4
4
  "type": "module",
5
5
  "description": "CLI Kiosapi.id berbahasa Indonesia — bangun aplikasimu pakai API key Kiosapi (agen + multimodal).",
6
6
  "keywords": [