@kyma-api/agent 0.1.4 → 0.1.5
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/bin/kyma-ter.js +21 -0
- package/dist/main.js +384 -289
- package/package.json +8 -4
- package/scripts/postinstall.js +135 -0
- package/themes/kyma-dark.json +50 -49
package/bin/kyma-ter.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from "child_process";
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import os from "os";
|
|
7
|
+
|
|
8
|
+
const binary = join(os.homedir(), ".kyma", "ter", "bin", "kyma-ter");
|
|
9
|
+
|
|
10
|
+
if (!existsSync(binary)) {
|
|
11
|
+
console.error("kyma-ter binary not found.");
|
|
12
|
+
console.error("Try reinstalling: npm install -g @kyma-api/agent");
|
|
13
|
+
console.error("Or download manually from https://kymaapi.com/ter");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
execFileSync(binary, process.argv.slice(2), { stdio: "inherit" });
|
|
19
|
+
} catch (err) {
|
|
20
|
+
process.exit(err.status || 1);
|
|
21
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -111,7 +111,6 @@ var KymaInteractiveMode = class extends InteractiveMode {
|
|
|
111
111
|
|
|
112
112
|
// src/sdk/onboarding.ts
|
|
113
113
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2, copyFileSync } from "fs";
|
|
114
|
-
import { createInterface } from "readline";
|
|
115
114
|
import { join as join3, dirname as dirname2 } from "path";
|
|
116
115
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
117
116
|
|
|
@@ -175,30 +174,39 @@ async function kymaApi(path, apiKey) {
|
|
|
175
174
|
return res.json();
|
|
176
175
|
}
|
|
177
176
|
var GOLD = "\x1B[38;2;201;168;76m";
|
|
178
|
-
var
|
|
177
|
+
var CYAN = "\x1B[38;2;86;182;194m";
|
|
178
|
+
var DIM = "\x1B[38;2;96;96;96m";
|
|
179
|
+
var BORDER = "\x1B[38;2;60;60;60m";
|
|
179
180
|
var RESET = "\x1B[0m";
|
|
180
181
|
var BOLD = "\x1B[1m";
|
|
181
|
-
var
|
|
182
|
-
var
|
|
182
|
+
var DIM_MOD = "\x1B[2m";
|
|
183
|
+
var ITALIC = "\x1B[3m";
|
|
184
|
+
var WHITE = "\x1B[38;2;238;238;238m";
|
|
185
|
+
var DARK_GRAY = "\x1B[38;2;72;72;72m";
|
|
183
186
|
var term = {
|
|
184
187
|
gold: (s) => `${GOLD}${s}${RESET}`,
|
|
188
|
+
cyan: (s) => `${CYAN}${s}${RESET}`,
|
|
185
189
|
dim: (s) => `${DIM}${s}${RESET}`,
|
|
186
190
|
bold: (s) => `${BOLD}${s}${RESET}`,
|
|
187
191
|
brand: (s) => `${BOLD}${GOLD}${s}${RESET}`,
|
|
188
|
-
// bold + gold
|
|
192
|
+
// bold + gold (brand moments only)
|
|
189
193
|
white: (s) => `${WHITE}${s}${RESET}`,
|
|
190
|
-
|
|
191
|
-
|
|
194
|
+
muted: (s) => `${DIM_MOD}${s}${RESET}`,
|
|
195
|
+
// ANSI dim modifier
|
|
196
|
+
subtle: (s) => `${DIM_MOD}${ITALIC}${s}${RESET}`,
|
|
197
|
+
// dim + italic (thinking)
|
|
198
|
+
hr: (w = 36) => `${DARK_GRAY}${"\u2500".repeat(w)}${RESET}`,
|
|
199
|
+
/** Render a subtle-bordered card. Gold brand reserved for content, not chrome. */
|
|
192
200
|
card(lines) {
|
|
193
201
|
const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
194
202
|
const innerW = Math.max(36, ...lines.map((l) => strip(l).length + 4));
|
|
195
|
-
const top = `${
|
|
196
|
-
const bot = `${
|
|
203
|
+
const top = `${BORDER} \u256D${"\u2500".repeat(innerW)}\u256E${RESET}`;
|
|
204
|
+
const bot = `${BORDER} \u2570${"\u2500".repeat(innerW)}\u256F${RESET}`;
|
|
197
205
|
const row = (s) => {
|
|
198
206
|
const pad = innerW - strip(s).length - 4;
|
|
199
|
-
return `${
|
|
207
|
+
return `${BORDER} \u2502${RESET} ${s}${" ".repeat(Math.max(0, pad))} ${BORDER}\u2502${RESET}`;
|
|
200
208
|
};
|
|
201
|
-
const empty = `${
|
|
209
|
+
const empty = `${BORDER} \u2502${RESET}${" ".repeat(innerW)}${BORDER}\u2502${RESET}`;
|
|
202
210
|
return [top, empty, ...lines.map(row), empty, bot];
|
|
203
211
|
}
|
|
204
212
|
};
|
|
@@ -318,6 +326,32 @@ async function loginKyma(callbacks) {
|
|
|
318
326
|
}
|
|
319
327
|
throw new Error("Authentication timed out. Please try again.");
|
|
320
328
|
}
|
|
329
|
+
async function validateApiKey(apiKey) {
|
|
330
|
+
const res = await fetch(`${KYMA_BASE_URL}/v1/auth/me`, {
|
|
331
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
332
|
+
});
|
|
333
|
+
if (!res.ok) throw new Error("Invalid API key. Check and try again.");
|
|
334
|
+
const me = await res.json();
|
|
335
|
+
let balance;
|
|
336
|
+
try {
|
|
337
|
+
const balRes = await fetch(`${KYMA_BASE_URL}/v1/credits/balance`, {
|
|
338
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
339
|
+
});
|
|
340
|
+
if (balRes.ok) {
|
|
341
|
+
const balData = await balRes.json();
|
|
342
|
+
balance = balData.balance;
|
|
343
|
+
}
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
refresh: "",
|
|
348
|
+
access: apiKey,
|
|
349
|
+
expires: Date.now() + 365 * 24 * 60 * 60 * 1e3,
|
|
350
|
+
email: me.email,
|
|
351
|
+
userId: me.id,
|
|
352
|
+
balance
|
|
353
|
+
};
|
|
354
|
+
}
|
|
321
355
|
async function refreshKymaToken(credentials) {
|
|
322
356
|
const res = await fetch(`${KYMA_BASE_URL}/v1/auth/me`, {
|
|
323
357
|
headers: { Authorization: `Bearer ${credentials.access}` }
|
|
@@ -371,7 +405,7 @@ function ensureAgentDir() {
|
|
|
371
405
|
if (!existsSync2(KYMA_SETTINGS_PATH)) {
|
|
372
406
|
writeFileSync2(KYMA_SETTINGS_PATH, JSON.stringify({
|
|
373
407
|
defaultProvider: "kyma",
|
|
374
|
-
defaultModel: "qwen-3-
|
|
408
|
+
defaultModel: "qwen-3.6-plus",
|
|
375
409
|
quietStartup: true,
|
|
376
410
|
theme: "kyma-dark",
|
|
377
411
|
hideThinkingBlock: false,
|
|
@@ -385,8 +419,8 @@ function ensureAgentDir() {
|
|
|
385
419
|
settings.defaultProvider = "kyma";
|
|
386
420
|
changed = true;
|
|
387
421
|
}
|
|
388
|
-
if (!settings.defaultModel) {
|
|
389
|
-
settings.defaultModel = "qwen-3-
|
|
422
|
+
if (!settings.defaultModel || settings.defaultModel === "qwen-3-32b") {
|
|
423
|
+
settings.defaultModel = "qwen-3.6-plus";
|
|
390
424
|
changed = true;
|
|
391
425
|
}
|
|
392
426
|
if (settings.quietStartup !== true) {
|
|
@@ -412,15 +446,126 @@ function ensureAgentDir() {
|
|
|
412
446
|
}
|
|
413
447
|
}
|
|
414
448
|
}
|
|
415
|
-
function
|
|
416
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
449
|
+
function askHidden(question) {
|
|
417
450
|
return new Promise((resolve) => {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
}
|
|
451
|
+
process.stdout.write(question);
|
|
452
|
+
if (process.stdin.isTTY) {
|
|
453
|
+
process.stdin.setRawMode(true);
|
|
454
|
+
}
|
|
455
|
+
process.stdin.resume();
|
|
456
|
+
let input = "";
|
|
457
|
+
const onData = (data) => {
|
|
458
|
+
const key = data.toString();
|
|
459
|
+
if (key === "\x1B") {
|
|
460
|
+
cleanup();
|
|
461
|
+
console.log("");
|
|
462
|
+
resolve("");
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (key === "") {
|
|
466
|
+
cleanup();
|
|
467
|
+
console.log("");
|
|
468
|
+
resolve("");
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (key === "\r" || key === "\n") {
|
|
472
|
+
cleanup();
|
|
473
|
+
console.log("");
|
|
474
|
+
resolve(input.trim());
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (key === "\x7F" || key === "\b") {
|
|
478
|
+
if (input.length > 0) input = input.slice(0, -1);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (key.length === 1 && key >= " ") {
|
|
482
|
+
input += key;
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
const cleanup = () => {
|
|
486
|
+
process.stdin.removeListener("data", onData);
|
|
487
|
+
if (process.stdin.isTTY) {
|
|
488
|
+
process.stdin.setRawMode(false);
|
|
489
|
+
}
|
|
490
|
+
process.stdin.pause();
|
|
491
|
+
};
|
|
492
|
+
process.stdin.on("data", onData);
|
|
422
493
|
});
|
|
423
494
|
}
|
|
495
|
+
function select(options) {
|
|
496
|
+
return new Promise((resolve) => {
|
|
497
|
+
let selected = 0;
|
|
498
|
+
const render = () => {
|
|
499
|
+
if (renderCount > 0) {
|
|
500
|
+
process.stdout.write(`\x1B[${options.length + 1}A`);
|
|
501
|
+
}
|
|
502
|
+
for (let i = 0; i < options.length; i++) {
|
|
503
|
+
const num = `${i + 1}.`;
|
|
504
|
+
const hint = options[i].hint ? ` ${term.dim(`(${options[i].hint})`)}` : "";
|
|
505
|
+
if (i === selected) {
|
|
506
|
+
process.stdout.write(`\x1B[2K ${term.gold(">")} ${term.gold(num)} ${term.white(options[i].label)}${hint}
|
|
507
|
+
`);
|
|
508
|
+
} else {
|
|
509
|
+
process.stdout.write(`\x1B[2K ${term.dim(num)} ${term.dim(options[i].label)}
|
|
510
|
+
`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
process.stdout.write(`\x1B[2K ${term.dim("\u2191\u2193 navigate \xB7 enter select \xB7 esc cancel")}
|
|
514
|
+
`);
|
|
515
|
+
renderCount++;
|
|
516
|
+
};
|
|
517
|
+
let renderCount = 0;
|
|
518
|
+
render();
|
|
519
|
+
if (process.stdin.isTTY) {
|
|
520
|
+
process.stdin.setRawMode(true);
|
|
521
|
+
}
|
|
522
|
+
process.stdin.resume();
|
|
523
|
+
const onData = (data) => {
|
|
524
|
+
const key = data.toString();
|
|
525
|
+
if (key === "\x1B[A" || key === "k") {
|
|
526
|
+
selected = (selected - 1 + options.length) % options.length;
|
|
527
|
+
render();
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (key === "\x1B[B" || key === "j") {
|
|
531
|
+
selected = (selected + 1) % options.length;
|
|
532
|
+
render();
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (key === "\r" || key === "\n") {
|
|
536
|
+
cleanup();
|
|
537
|
+
resolve(selected);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
if (key === "" || key === "\x1B") {
|
|
541
|
+
cleanup();
|
|
542
|
+
resolve(-1);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
const cleanup = () => {
|
|
547
|
+
process.stdin.removeListener("data", onData);
|
|
548
|
+
if (process.stdin.isTTY) {
|
|
549
|
+
process.stdin.setRawMode(false);
|
|
550
|
+
}
|
|
551
|
+
process.stdin.pause();
|
|
552
|
+
};
|
|
553
|
+
process.stdin.on("data", onData);
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
557
|
+
function startSpinner(message) {
|
|
558
|
+
let frame = 0;
|
|
559
|
+
const interval = setInterval(() => {
|
|
560
|
+
const f = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
|
|
561
|
+
process.stdout.write(`\r ${f} ${message}`);
|
|
562
|
+
frame++;
|
|
563
|
+
}, 100);
|
|
564
|
+
return () => {
|
|
565
|
+
clearInterval(interval);
|
|
566
|
+
process.stdout.write(`\r${" ".repeat(message.length + 10)}\r`);
|
|
567
|
+
};
|
|
568
|
+
}
|
|
424
569
|
async function runOnboarding() {
|
|
425
570
|
ensureAgentDir();
|
|
426
571
|
const onboarded = existsSync2(KYMA_ONBOARDED_PATH);
|
|
@@ -434,42 +579,65 @@ async function runOnboarding() {
|
|
|
434
579
|
console.log("");
|
|
435
580
|
printBanner();
|
|
436
581
|
console.log("");
|
|
437
|
-
console.log(` ${term.
|
|
438
|
-
console.log(` ${term.dim(`v${VERSION} \xB7 kymaapi.com`)}`);
|
|
582
|
+
console.log(` Welcome to ${term.brand("Kyma")}, your AI coding agent`);
|
|
439
583
|
console.log("");
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
584
|
+
console.log(` ${term.dim("Sign in to get started, or connect a Kyma API key")}`);
|
|
585
|
+
console.log(` ${term.dim("for usage-based billing.")}`);
|
|
586
|
+
console.log("");
|
|
587
|
+
let done = false;
|
|
588
|
+
while (!done) {
|
|
589
|
+
const choice = await select([
|
|
590
|
+
{ label: "Sign in with Kyma", hint: "opens browser" },
|
|
591
|
+
{ label: "Paste your Kyma API key", hint: "" }
|
|
592
|
+
]);
|
|
593
|
+
if (choice === -1) {
|
|
594
|
+
console.log("");
|
|
595
|
+
console.log(` ${term.dim("Skipped.")} Use ${term.gold("/connect")} anytime.`);
|
|
596
|
+
console.log("");
|
|
597
|
+
done = true;
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
if (choice === 1) {
|
|
601
|
+
console.log("");
|
|
602
|
+
console.log(` ${term.dim("Paste your Kyma API key below. Press Esc to go back.")}`);
|
|
603
|
+
console.log("");
|
|
604
|
+
const key = await askHidden(` ${term.dim("Kyma API key:")} `);
|
|
605
|
+
if (!key) {
|
|
606
|
+
console.log("");
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
try {
|
|
610
|
+
const stopSpinner = startSpinner("Validating...");
|
|
611
|
+
const credentials = await validateApiKey(key);
|
|
612
|
+
stopSpinner();
|
|
613
|
+
saveKymaCredentials(credentials);
|
|
614
|
+
const balStr = credentials.balance != null ? ` \xB7 $${credentials.balance.toFixed(2)}` : "";
|
|
615
|
+
console.log(` ${term.gold("\u2713")} Connected as ${term.white(credentials.email)}${balStr}`);
|
|
616
|
+
console.log("");
|
|
617
|
+
done = true;
|
|
618
|
+
} catch (err) {
|
|
619
|
+
console.log(` ${term.dim("Invalid key:")} ${err.message}`);
|
|
620
|
+
console.log("");
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
} else {
|
|
624
|
+
console.log("");
|
|
625
|
+
try {
|
|
626
|
+
const stopSpinner = startSpinner("Waiting for authorization...");
|
|
627
|
+
const credentials = await loginKymaStandalone();
|
|
628
|
+
stopSpinner();
|
|
629
|
+
saveKymaCredentials(credentials);
|
|
630
|
+
const balStr = credentials.balance != null ? ` \xB7 $${credentials.balance.toFixed(2)}` : "";
|
|
631
|
+
console.log(` ${term.gold("\u2713")} Connected as ${term.white(credentials.email)}${balStr}`);
|
|
632
|
+
console.log("");
|
|
633
|
+
done = true;
|
|
634
|
+
} catch (err) {
|
|
635
|
+
console.log("");
|
|
636
|
+
console.log(` ${term.dim("Login failed:")} ${err.message}`);
|
|
637
|
+
console.log("");
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
462
640
|
}
|
|
463
|
-
console.log(` ${term.hr()}`);
|
|
464
|
-
console.log("");
|
|
465
|
-
await new Promise((r) => setTimeout(r, 2500));
|
|
466
|
-
process.stdout.write("\x1B[2J\x1B[H");
|
|
467
|
-
process.env.KYMA_FRESH_LOGIN = "1";
|
|
468
|
-
} catch (err) {
|
|
469
|
-
console.log("");
|
|
470
|
-
console.log(` ${term.dim("Login failed:")} ${err.message}`);
|
|
471
|
-
console.log(` ${term.dim("Try again with")} ${term.gold("/connect")}`);
|
|
472
|
-
console.log("");
|
|
473
641
|
}
|
|
474
642
|
markOnboarded();
|
|
475
643
|
return isLoggedIn();
|
|
@@ -547,7 +715,7 @@ async function createKymaSession(options) {
|
|
|
547
715
|
}
|
|
548
716
|
|
|
549
717
|
// src/sdk/extension.ts
|
|
550
|
-
import { existsSync as existsSync3, readFileSync as readFileSync4
|
|
718
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
551
719
|
import { join as join5 } from "path";
|
|
552
720
|
|
|
553
721
|
// src/sdk/config.ts
|
|
@@ -565,19 +733,20 @@ function parseKymaConfig(raw) {
|
|
|
565
733
|
|
|
566
734
|
// src/models.ts
|
|
567
735
|
var KYMA_MODELS = [
|
|
568
|
-
// ---
|
|
736
|
+
// --- Flagship ---
|
|
569
737
|
{
|
|
570
|
-
id: "qwen-3-
|
|
571
|
-
name: "Qwen 3
|
|
572
|
-
tag: "
|
|
573
|
-
reasoning:
|
|
738
|
+
id: "qwen-3.6-plus",
|
|
739
|
+
name: "Qwen 3.6 Plus",
|
|
740
|
+
tag: "flagship",
|
|
741
|
+
reasoning: true,
|
|
574
742
|
vision: false,
|
|
575
743
|
input: ["text"],
|
|
576
|
-
cost: { input: 0.
|
|
577
|
-
contextWindow:
|
|
578
|
-
maxTokens:
|
|
579
|
-
|
|
744
|
+
cost: { input: 0.439, output: 2.633, cacheRead: 0.044, cacheWrite: 0 },
|
|
745
|
+
contextWindow: 131072,
|
|
746
|
+
maxTokens: 16384,
|
|
747
|
+
compat: { thinkingFormat: "qwen" }
|
|
580
748
|
},
|
|
749
|
+
// --- Code Specialist ---
|
|
581
750
|
{
|
|
582
751
|
id: "qwen-3-coder",
|
|
583
752
|
name: "Qwen 3 Coder",
|
|
@@ -590,54 +759,19 @@ var KYMA_MODELS = [
|
|
|
590
759
|
maxTokens: 16384,
|
|
591
760
|
modes: ["coding"]
|
|
592
761
|
},
|
|
762
|
+
// --- Reasoning + Code ---
|
|
593
763
|
{
|
|
594
|
-
id: "
|
|
595
|
-
name: "
|
|
596
|
-
tag: "
|
|
764
|
+
id: "deepseek-v3",
|
|
765
|
+
name: "DeepSeek V3",
|
|
766
|
+
tag: "reasoning + code",
|
|
597
767
|
reasoning: true,
|
|
598
768
|
vision: false,
|
|
599
769
|
input: ["text"],
|
|
600
|
-
cost: { input: 0.
|
|
601
|
-
contextWindow:
|
|
602
|
-
maxTokens: 16384
|
|
603
|
-
compat: { thinkingFormat: "qwen" }
|
|
604
|
-
},
|
|
605
|
-
// --- Google ---
|
|
606
|
-
{
|
|
607
|
-
id: "gemma-4-31b",
|
|
608
|
-
name: "Gemma 4 31B",
|
|
609
|
-
tag: "vision + cheap",
|
|
610
|
-
reasoning: false,
|
|
611
|
-
vision: true,
|
|
612
|
-
input: ["text", "image"],
|
|
613
|
-
cost: { input: 0.189, output: 0.54, cacheRead: 0.019, cacheWrite: 0 },
|
|
614
|
-
contextWindow: 128e3,
|
|
615
|
-
maxTokens: 8192
|
|
616
|
-
},
|
|
617
|
-
{
|
|
618
|
-
id: "gemini-2.5-flash",
|
|
619
|
-
name: "Gemini 2.5 Flash",
|
|
620
|
-
tag: "vision + 1M ctx",
|
|
621
|
-
reasoning: false,
|
|
622
|
-
vision: true,
|
|
623
|
-
input: ["text", "image"],
|
|
624
|
-
cost: { input: 0.405, output: 3.375, cacheRead: 0.041, cacheWrite: 0 },
|
|
625
|
-
contextWindow: 1048576,
|
|
626
|
-
maxTokens: 65536,
|
|
627
|
-
modes: ["vision"]
|
|
628
|
-
},
|
|
629
|
-
{
|
|
630
|
-
id: "gemini-3-flash",
|
|
631
|
-
name: "Gemini 3 Flash",
|
|
632
|
-
tag: "reasoning + vision",
|
|
633
|
-
reasoning: true,
|
|
634
|
-
vision: true,
|
|
635
|
-
input: ["text", "image"],
|
|
636
|
-
cost: { input: 0.675, output: 4.05, cacheRead: 0.068, cacheWrite: 0 },
|
|
637
|
-
contextWindow: 1048576,
|
|
638
|
-
maxTokens: 65536
|
|
770
|
+
cost: { input: 0.81, output: 2.295, cacheRead: 0, cacheWrite: 0 },
|
|
771
|
+
contextWindow: 16e4,
|
|
772
|
+
maxTokens: 16384
|
|
639
773
|
},
|
|
640
|
-
// ---
|
|
774
|
+
// --- Agentic Coding ---
|
|
641
775
|
{
|
|
642
776
|
id: "minimax-m2.5",
|
|
643
777
|
name: "MiniMax M2.5",
|
|
@@ -652,7 +786,7 @@ var KYMA_MODELS = [
|
|
|
652
786
|
{
|
|
653
787
|
id: "minimax-m2.7",
|
|
654
788
|
name: "MiniMax M2.7",
|
|
655
|
-
tag: "agentic coding",
|
|
789
|
+
tag: "agentic coding (new)",
|
|
656
790
|
reasoning: false,
|
|
657
791
|
vision: false,
|
|
658
792
|
input: ["text"],
|
|
@@ -660,18 +794,20 @@ var KYMA_MODELS = [
|
|
|
660
794
|
contextWindow: 204800,
|
|
661
795
|
maxTokens: 16384
|
|
662
796
|
},
|
|
663
|
-
// ---
|
|
797
|
+
// --- Agentic + Long Context ---
|
|
664
798
|
{
|
|
665
|
-
id: "
|
|
666
|
-
name: "
|
|
667
|
-
tag: "
|
|
668
|
-
reasoning:
|
|
799
|
+
id: "kimi-k2.5",
|
|
800
|
+
name: "Kimi K2.5",
|
|
801
|
+
tag: "agentic + long ctx",
|
|
802
|
+
reasoning: false,
|
|
669
803
|
vision: false,
|
|
670
804
|
input: ["text"],
|
|
671
|
-
cost: { input: 0.
|
|
672
|
-
contextWindow:
|
|
673
|
-
maxTokens: 16384
|
|
805
|
+
cost: { input: 0.675, output: 3.78, cacheRead: 0, cacheWrite: 0 },
|
|
806
|
+
contextWindow: 262144,
|
|
807
|
+
maxTokens: 16384,
|
|
808
|
+
modes: ["long"]
|
|
674
809
|
},
|
|
810
|
+
// --- Deep Reasoning ---
|
|
675
811
|
{
|
|
676
812
|
id: "deepseek-r1",
|
|
677
813
|
name: "DeepSeek R1",
|
|
@@ -685,23 +821,11 @@ var KYMA_MODELS = [
|
|
|
685
821
|
compat: { requiresThinkingAsText: true },
|
|
686
822
|
modes: ["reasoning"]
|
|
687
823
|
},
|
|
688
|
-
// ---
|
|
689
|
-
{
|
|
690
|
-
id: "llama-3.3-70b",
|
|
691
|
-
name: "Llama 3.3 70B",
|
|
692
|
-
tag: "all-rounder",
|
|
693
|
-
reasoning: true,
|
|
694
|
-
vision: false,
|
|
695
|
-
input: ["text"],
|
|
696
|
-
cost: { input: 1.188, output: 1.188, cacheRead: 0.119, cacheWrite: 0 },
|
|
697
|
-
contextWindow: 128e3,
|
|
698
|
-
maxTokens: 16384
|
|
699
|
-
},
|
|
700
|
-
// --- OpenAI ---
|
|
824
|
+
// --- Budget + Fast ---
|
|
701
825
|
{
|
|
702
826
|
id: "gpt-oss-120b",
|
|
703
827
|
name: "GPT-OSS 120B",
|
|
704
|
-
tag: "
|
|
828
|
+
tag: "budget + fast",
|
|
705
829
|
reasoning: false,
|
|
706
830
|
vision: false,
|
|
707
831
|
input: ["text"],
|
|
@@ -710,18 +834,18 @@ var KYMA_MODELS = [
|
|
|
710
834
|
maxTokens: 16384,
|
|
711
835
|
modes: ["cheap"]
|
|
712
836
|
},
|
|
713
|
-
// ---
|
|
837
|
+
// --- Vision + Budget ---
|
|
714
838
|
{
|
|
715
|
-
id: "
|
|
716
|
-
name: "
|
|
717
|
-
tag: "
|
|
839
|
+
id: "gemma-4-31b",
|
|
840
|
+
name: "Gemma 4 31B",
|
|
841
|
+
tag: "vision + budget",
|
|
718
842
|
reasoning: false,
|
|
719
|
-
vision:
|
|
720
|
-
input: ["text"],
|
|
721
|
-
cost: { input: 0.
|
|
722
|
-
contextWindow:
|
|
723
|
-
maxTokens:
|
|
724
|
-
modes: ["
|
|
843
|
+
vision: true,
|
|
844
|
+
input: ["text", "image"],
|
|
845
|
+
cost: { input: 0.189, output: 0.54, cacheRead: 0.019, cacheWrite: 0 },
|
|
846
|
+
contextWindow: 128e3,
|
|
847
|
+
maxTokens: 8192,
|
|
848
|
+
modes: ["vision"]
|
|
725
849
|
}
|
|
726
850
|
];
|
|
727
851
|
var MODEL_BY_ID = new Map(KYMA_MODELS.map((m) => [m.id, m]));
|
|
@@ -781,6 +905,10 @@ var kymaRuntimeFactory = (pi) => {
|
|
|
781
905
|
let turnCount = 0;
|
|
782
906
|
let wasLoggedIn = !!getKymaApiKey();
|
|
783
907
|
let postLoginShown = false;
|
|
908
|
+
let headerEmail = getKymaEmail();
|
|
909
|
+
let headerBalance = "";
|
|
910
|
+
let headerLoggedIn = wasLoggedIn;
|
|
911
|
+
let refreshHeader = null;
|
|
784
912
|
pi.on("session_start", async (_event, ctx) => {
|
|
785
913
|
sessionCost = 0;
|
|
786
914
|
sessionTokens = 0;
|
|
@@ -788,82 +916,39 @@ var kymaRuntimeFactory = (pi) => {
|
|
|
788
916
|
if (!ctx.hasUI) return;
|
|
789
917
|
ctx.ui.setHideThinkingBlock?.(false);
|
|
790
918
|
ctx.ui.setHiddenThinkingLabel?.("Thinking... (Ctrl+T to expand)");
|
|
791
|
-
|
|
919
|
+
headerEmail = getKymaEmail();
|
|
792
920
|
const apiKey = getKymaApiKey();
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
if (loggedIn && apiKey) {
|
|
921
|
+
headerLoggedIn = !!apiKey;
|
|
922
|
+
if (headerLoggedIn && apiKey) {
|
|
796
923
|
try {
|
|
797
924
|
const b = await kymaApi("/v1/credits/balance", apiKey);
|
|
798
|
-
|
|
925
|
+
headerBalance = `$${b.balance.toFixed(2)}`;
|
|
799
926
|
} catch {
|
|
800
927
|
}
|
|
801
928
|
}
|
|
802
|
-
const
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
visible++;
|
|
819
|
-
i++;
|
|
820
|
-
}
|
|
821
|
-
return i < s.length ? s.slice(0, i) : s;
|
|
822
|
-
};
|
|
823
|
-
const maxW = Math.max(width, 30);
|
|
824
|
-
const logo = theme.bold(theme.fg("accent", ` \u03A8 kyma`)) + theme.fg("dim", ` v${VERSION}`);
|
|
825
|
-
const url = ` https://kymaapi.com`;
|
|
826
|
-
const accountStr = loggedIn ? `${email || "connected"}${balanceStr ? ` \xB7 ${balanceStr}` : ""}` : "not connected";
|
|
827
|
-
const dirLine = `~/${repoName}`;
|
|
828
|
-
const connectLine = loggedIn ? null : `${theme.bold("/connect")} to sign in`;
|
|
829
|
-
const contentLines = [accountStr, dirLine];
|
|
830
|
-
if (connectLine) contentLines.push(connectLine);
|
|
831
|
-
const visibleWidths = contentLines.map((l) => strip(l).length);
|
|
832
|
-
const contentMax = Math.max(...visibleWidths);
|
|
833
|
-
const innerW = Math.min(maxW - 4, Math.max(contentMax + 4, 24));
|
|
834
|
-
const boxTop = theme.fg("dim", ` \u256D${"\u2500".repeat(innerW)}\u256E`);
|
|
835
|
-
const boxBot = theme.fg("dim", ` \u2570${"\u2500".repeat(innerW)}\u256F`);
|
|
836
|
-
const boxRow = (s) => {
|
|
837
|
-
const visLen = strip(s).length;
|
|
838
|
-
const pad = Math.max(0, innerW - visLen - 4);
|
|
839
|
-
const row = theme.fg("dim", " \u2502") + ` ${s}${" ".repeat(pad)} ` + theme.fg("dim", "\u2502");
|
|
840
|
-
return truncate(row, maxW);
|
|
841
|
-
};
|
|
842
|
-
return [truncate(logo, maxW), truncate(url, maxW), boxTop, ...contentLines.map(boxRow), boxBot];
|
|
843
|
-
},
|
|
844
|
-
invalidate() {
|
|
845
|
-
}
|
|
846
|
-
}));
|
|
929
|
+
const setCompactHeader = (uiCtx) => {
|
|
930
|
+
const modelId = uiCtx.model?.id || ctx.model?.id || "qwen-3.6-plus";
|
|
931
|
+
uiCtx.ui.setHeader((_tui, theme) => ({
|
|
932
|
+
render(_width) {
|
|
933
|
+
const status = headerLoggedIn ? `${headerEmail || "connected"}${headerBalance ? ` \xB7 ${headerBalance}` : ""}` : "not connected";
|
|
934
|
+
const line1 = theme.bold(theme.fg("accent", " \u03A8 Kyma")) + theme.fg("dim", ` v${VERSION}`) + theme.fg("dim", " \xB7 ") + `${modelId}` + theme.fg("dim", " \xB7 ") + (headerLoggedIn ? status : theme.fg("dim", status));
|
|
935
|
+
const hints = headerLoggedIn ? "/models to switch \xB7 /balance \xB7 /help" : "/connect to sign in \xB7 /models to switch \xB7 /help";
|
|
936
|
+
const line2 = theme.fg("dim", ` ${hints}`);
|
|
937
|
+
return [line1, line2, ""];
|
|
938
|
+
},
|
|
939
|
+
invalidate() {
|
|
940
|
+
}
|
|
941
|
+
}));
|
|
942
|
+
};
|
|
943
|
+
refreshHeader = setCompactHeader;
|
|
944
|
+
setCompactHeader(ctx);
|
|
847
945
|
ctx.ui.setTitle("kyma");
|
|
848
|
-
const isOnboarded = existsSync3(KYMA_ONBOARDED_PATH);
|
|
849
|
-
if (!loggedIn && !isOnboarded) {
|
|
850
|
-
try {
|
|
851
|
-
writeFileSync3(KYMA_ONBOARDED_PATH, (/* @__PURE__ */ new Date()).toISOString(), { mode: 384 });
|
|
852
|
-
} catch {
|
|
853
|
-
}
|
|
854
|
-
} else if (!loggedIn) {
|
|
855
|
-
} else if (!isOnboarded) {
|
|
856
|
-
try {
|
|
857
|
-
writeFileSync3(KYMA_ONBOARDED_PATH, (/* @__PURE__ */ new Date()).toISOString(), { mode: 384 });
|
|
858
|
-
} catch {
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
946
|
const kymaConfigPath = join5(ctx.cwd, "KYMA.md");
|
|
862
947
|
if (existsSync3(kymaConfigPath)) {
|
|
863
948
|
try {
|
|
864
949
|
const raw = readFileSync4(kymaConfigPath, "utf-8");
|
|
865
950
|
const config = parseKymaConfig(raw);
|
|
866
|
-
if (config.model && (
|
|
951
|
+
if (config.model && (headerLoggedIn || getKymaApiKey())) {
|
|
867
952
|
const entry = MODEL_BY_ID.get(config.model);
|
|
868
953
|
if (entry) {
|
|
869
954
|
await pi.setModel(toRuntimeModel(entry, `${KYMA_BASE_URL}/v1`));
|
|
@@ -876,14 +961,6 @@ var kymaRuntimeFactory = (pi) => {
|
|
|
876
961
|
} catch {
|
|
877
962
|
}
|
|
878
963
|
}
|
|
879
|
-
const freshLogin = !!process.env.KYMA_FRESH_LOGIN;
|
|
880
|
-
delete process.env.KYMA_FRESH_LOGIN;
|
|
881
|
-
if (loggedIn && isOnboarded && !freshLogin) {
|
|
882
|
-
const modelId2 = ctx.model?.id || "qwen-3-32b";
|
|
883
|
-
ctx.ui.setWidget?.("kyma-hint", [
|
|
884
|
-
` ${modelId2} \xB7 /models to switch \xB7 /help for commands`
|
|
885
|
-
], { placement: "aboveEditor" });
|
|
886
|
-
}
|
|
887
964
|
});
|
|
888
965
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
889
966
|
if (!ctx.hasUI || turnCount === 0) return;
|
|
@@ -917,9 +994,9 @@ var kymaRuntimeFactory = (pi) => {
|
|
|
917
994
|
const postEmail = getKymaEmail() || "connected";
|
|
918
995
|
try {
|
|
919
996
|
const bal = await kymaApi("/v1/credits/balance", postKey);
|
|
920
|
-
ctx.ui.notify(`Authorized \xB7 ${postEmail} \xB7 $${bal.balance.toFixed(2)} \u2014 /
|
|
997
|
+
ctx.ui.notify(`Authorized \xB7 ${postEmail} \xB7 $${bal.balance.toFixed(2)} \u2014 /models to switch`, "info");
|
|
921
998
|
} catch {
|
|
922
|
-
ctx.ui.notify(`Authorized \xB7 ${postEmail} \u2014 /
|
|
999
|
+
ctx.ui.notify(`Authorized \xB7 ${postEmail} \u2014 /models to switch`, "info");
|
|
923
1000
|
}
|
|
924
1001
|
}
|
|
925
1002
|
});
|
|
@@ -947,25 +1024,31 @@ var kymaRuntimeFactory = (pi) => {
|
|
|
947
1024
|
return;
|
|
948
1025
|
}
|
|
949
1026
|
}
|
|
950
|
-
const options = filtered.map((m) =>
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
const badges = [];
|
|
956
|
-
if (m.vision) badges.push("[img]");
|
|
957
|
-
if (m.reasoning) badges.push("[think]");
|
|
958
|
-
const badgeStr = badges.length ? " " + badges.join(" ") : "";
|
|
959
|
-
return `${star}${name}${tag}${price}${badgeStr}`;
|
|
960
|
-
});
|
|
961
|
-
const selected = await ctx.ui.select("Models available in your Kyma account", options);
|
|
1027
|
+
const options = filtered.map((m) => formatModelLine(m, currentId));
|
|
1028
|
+
const selected = await ctx.ui.select(
|
|
1029
|
+
"/models coding \xB7 /models vision \xB7 /models cheap",
|
|
1030
|
+
options
|
|
1031
|
+
);
|
|
962
1032
|
if (!selected) return;
|
|
963
1033
|
const idx = options.indexOf(selected);
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1034
|
+
return switchTo(filtered[idx], currentId, ctx);
|
|
1035
|
+
}
|
|
1036
|
+
function formatModelLine(m, currentId) {
|
|
1037
|
+
const star = m.id === currentId ? "* " : " ";
|
|
1038
|
+
const name = m.name.padEnd(20);
|
|
1039
|
+
const tag = m.tag.padEnd(22);
|
|
1040
|
+
const price = `$${m.cost.input.toFixed(2)}/$${m.cost.output.toFixed(2)}`;
|
|
1041
|
+
const badges = [];
|
|
1042
|
+
if (m.vision) badges.push("[img]");
|
|
1043
|
+
if (m.reasoning) badges.push("[think]");
|
|
1044
|
+
const badgeStr = badges.length ? " " + badges.join(" ") : "";
|
|
1045
|
+
return `${star}${name}${tag}${price}${badgeStr}`;
|
|
1046
|
+
}
|
|
1047
|
+
async function switchTo(model, currentId, ctx) {
|
|
1048
|
+
if (model.id === currentId) return;
|
|
1049
|
+
const ok = await pi.setModel(toRuntimeModel(model, `${KYMA_BASE_URL}/v1`));
|
|
967
1050
|
if (ok) {
|
|
968
|
-
ctx.ui.notify(`Switched to ${
|
|
1051
|
+
ctx.ui.notify(`Switched to ${model.name} \u2014 ${model.tag}`, "info");
|
|
969
1052
|
} else {
|
|
970
1053
|
ctx.ui.notify("Failed to switch model. Run /connect to sign in.", "error");
|
|
971
1054
|
}
|
|
@@ -974,40 +1057,6 @@ var kymaRuntimeFactory = (pi) => {
|
|
|
974
1057
|
description: "Browse and switch Kyma models",
|
|
975
1058
|
handler: modelsHandler
|
|
976
1059
|
});
|
|
977
|
-
const MODE_LIST = [...MODE_TO_MODEL.entries()].map(([mode, m]) => ({
|
|
978
|
-
mode,
|
|
979
|
-
model: m,
|
|
980
|
-
label: `${mode.padEnd(12)} ${m.id.padEnd(20)} ${m.tag}`
|
|
981
|
-
}));
|
|
982
|
-
pi.registerCommand("mode", {
|
|
983
|
-
description: "Switch model by task type",
|
|
984
|
-
async handler(args, ctx) {
|
|
985
|
-
let entry;
|
|
986
|
-
if (args.trim()) {
|
|
987
|
-
const m = MODE_TO_MODEL.get(args.trim());
|
|
988
|
-
if (!m) {
|
|
989
|
-
ctx.ui.notify(`Unknown mode "${args.trim()}". Available: ${[...MODE_TO_MODEL.keys()].join(", ")}`, "error");
|
|
990
|
-
return;
|
|
991
|
-
}
|
|
992
|
-
entry = { mode: args.trim(), model: m };
|
|
993
|
-
} else {
|
|
994
|
-
const selected = await ctx.ui.select(
|
|
995
|
-
"Choose mode",
|
|
996
|
-
MODE_LIST.map((p) => p.label)
|
|
997
|
-
);
|
|
998
|
-
if (!selected) return;
|
|
999
|
-
const idx = MODE_LIST.findIndex((p) => p.label === selected);
|
|
1000
|
-
entry = MODE_LIST[idx];
|
|
1001
|
-
}
|
|
1002
|
-
if (!entry) return;
|
|
1003
|
-
const ok = await pi.setModel(toRuntimeModel(entry.model, `${KYMA_BASE_URL}/v1`));
|
|
1004
|
-
if (ok) {
|
|
1005
|
-
ctx.ui.notify(`Mode: ${entry.mode} \u2192 ${entry.model.name}`, "info");
|
|
1006
|
-
} else {
|
|
1007
|
-
ctx.ui.notify("Failed to switch model. Run /connect to sign in.", "error");
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
});
|
|
1011
1060
|
pi.registerCommand("status", {
|
|
1012
1061
|
description: "Account, credits, and diagnostics",
|
|
1013
1062
|
async handler(_args, ctx) {
|
|
@@ -1192,7 +1241,27 @@ var kymaRuntimeFactory = (pi) => {
|
|
|
1192
1241
|
});
|
|
1193
1242
|
pi.registerCommand("connect", {
|
|
1194
1243
|
description: "Sign in to your Kyma account",
|
|
1195
|
-
async handler(
|
|
1244
|
+
async handler(args, ctx) {
|
|
1245
|
+
const trimmed = args.trim();
|
|
1246
|
+
if (trimmed && (trimmed.startsWith("sk-") || trimmed.startsWith("ky-") || trimmed.startsWith("kyma-"))) {
|
|
1247
|
+
ctx.ui.notify("Validating API key...", "info");
|
|
1248
|
+
try {
|
|
1249
|
+
const credentials = await validateApiKey(trimmed);
|
|
1250
|
+
saveKymaCredentials(credentials);
|
|
1251
|
+
wasLoggedIn = true;
|
|
1252
|
+
postLoginShown = true;
|
|
1253
|
+
headerLoggedIn = true;
|
|
1254
|
+
headerEmail = credentials.email;
|
|
1255
|
+
headerBalance = credentials.balance != null ? `$${credentials.balance.toFixed(2)}` : "";
|
|
1256
|
+
if (refreshHeader) refreshHeader(ctx);
|
|
1257
|
+
const bal = credentials.balance != null ? ` \xB7 $${credentials.balance.toFixed(2)}` : "";
|
|
1258
|
+
ctx.ui.notify(`\u2713 Connected as ${credentials.email}${bal}
|
|
1259
|
+
/models to switch \xB7 /balance to check credits`, "info");
|
|
1260
|
+
} catch (err) {
|
|
1261
|
+
ctx.ui.notify(`Invalid API key: ${err.message}`, "error");
|
|
1262
|
+
}
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1196
1265
|
if (getKymaApiKey()) {
|
|
1197
1266
|
const email = getKymaEmail();
|
|
1198
1267
|
const reconnect = await ctx.ui.confirm(
|
|
@@ -1201,25 +1270,50 @@ var kymaRuntimeFactory = (pi) => {
|
|
|
1201
1270
|
);
|
|
1202
1271
|
if (!reconnect) return;
|
|
1203
1272
|
}
|
|
1204
|
-
|
|
1273
|
+
const SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1274
|
+
let spinnerFrame = 0;
|
|
1275
|
+
let spinnerInterval = null;
|
|
1205
1276
|
try {
|
|
1206
1277
|
const credentials = await loginKyma({
|
|
1207
1278
|
onAuth({ url, instructions }) {
|
|
1208
|
-
|
|
1209
|
-
|
|
1279
|
+
const urlObj = new URL(url);
|
|
1280
|
+
const code = urlObj.searchParams.get("code") || "";
|
|
1281
|
+
ctx.ui.notify(
|
|
1282
|
+
`${instructions}
|
|
1283
|
+
${url}
|
|
1284
|
+
|
|
1285
|
+
Can't open browser? Run: /connect YOUR_API_KEY`,
|
|
1286
|
+
"info"
|
|
1287
|
+
);
|
|
1210
1288
|
openBrowser(url);
|
|
1289
|
+
spinnerInterval = setInterval(() => {
|
|
1290
|
+
const frame = SPINNER[spinnerFrame % SPINNER.length];
|
|
1291
|
+
ctx.ui.setWidget?.("connect-spinner", [
|
|
1292
|
+
` ${frame} Waiting for authorization...`
|
|
1293
|
+
], { placement: "aboveEditor" });
|
|
1294
|
+
spinnerFrame++;
|
|
1295
|
+
}, 120);
|
|
1211
1296
|
},
|
|
1212
|
-
onProgress(
|
|
1213
|
-
if (msg) ctx.ui.notify(msg, "info");
|
|
1297
|
+
onProgress() {
|
|
1214
1298
|
}
|
|
1215
1299
|
});
|
|
1300
|
+
if (spinnerInterval) clearInterval(spinnerInterval);
|
|
1301
|
+
ctx.ui.setWidget?.("connect-spinner", void 0);
|
|
1216
1302
|
saveKymaCredentials(credentials);
|
|
1217
1303
|
wasLoggedIn = true;
|
|
1218
1304
|
postLoginShown = true;
|
|
1219
|
-
|
|
1220
|
-
|
|
1305
|
+
headerLoggedIn = true;
|
|
1306
|
+
headerEmail = credentials.email;
|
|
1307
|
+
headerBalance = credentials.balance != null ? `$${credentials.balance.toFixed(2)}` : "";
|
|
1308
|
+
if (refreshHeader) refreshHeader(ctx);
|
|
1309
|
+
const bal = credentials.balance != null ? ` \xB7 $${credentials.balance.toFixed(2)}` : "";
|
|
1310
|
+
ctx.ui.notify(`\u2713 Connected as ${credentials.email}${bal}
|
|
1311
|
+
/models to switch \xB7 /balance to check credits`, "info");
|
|
1221
1312
|
} catch (err) {
|
|
1222
|
-
|
|
1313
|
+
if (spinnerInterval) clearInterval(spinnerInterval);
|
|
1314
|
+
ctx.ui.setWidget?.("connect-spinner", void 0);
|
|
1315
|
+
ctx.ui.notify(`Login failed: ${err.message}
|
|
1316
|
+
Try again with /connect or /connect YOUR_API_KEY`, "error");
|
|
1223
1317
|
}
|
|
1224
1318
|
}
|
|
1225
1319
|
});
|
|
@@ -1233,7 +1327,7 @@ URL: ${url}`, "info");
|
|
|
1233
1327
|
" /connect Sign in to Kyma",
|
|
1234
1328
|
" /disconnect Sign out",
|
|
1235
1329
|
" /models Browse and switch models",
|
|
1236
|
-
"
|
|
1330
|
+
" /models coding \xB7 /models vision \xB7 /models cheap",
|
|
1237
1331
|
" /status Account, credits, diagnostics",
|
|
1238
1332
|
" /balance Credits and rate limits",
|
|
1239
1333
|
" /usage Session cost and tokens",
|
|
@@ -1293,7 +1387,7 @@ URL: ${url}`, "info");
|
|
|
1293
1387
|
|
|
1294
1388
|
// src/sdk/main.ts
|
|
1295
1389
|
var PKG_DIR2 = process.env.PI_PACKAGE_DIR || dirname4(dirname4(dirname4(fileURLToPath4(import.meta.url))));
|
|
1296
|
-
var
|
|
1390
|
+
var VERSION3 = (() => {
|
|
1297
1391
|
try {
|
|
1298
1392
|
return JSON.parse(readFileSync5(join6(PKG_DIR2, "package.json"), "utf-8")).version || "0.1.0";
|
|
1299
1393
|
} catch {
|
|
@@ -1301,20 +1395,21 @@ var VERSION2 = (() => {
|
|
|
1301
1395
|
}
|
|
1302
1396
|
})();
|
|
1303
1397
|
function printHelp() {
|
|
1304
|
-
console.log(`kyma v${
|
|
1398
|
+
console.log(`kyma v${VERSION3} \u2014 one account, many models \xB7 kymaapi.com
|
|
1305
1399
|
|
|
1306
1400
|
Usage:
|
|
1307
1401
|
kyma [messages...] Interactive coding agent
|
|
1308
1402
|
kyma "prompt" Start with a message
|
|
1309
1403
|
kyma -c Continue previous session
|
|
1310
1404
|
|
|
1311
|
-
Models:
|
|
1312
|
-
qwen-3-
|
|
1405
|
+
Models (9 curated for coding):
|
|
1406
|
+
qwen-3.6-plus Flagship (default)
|
|
1313
1407
|
qwen-3-coder Code specialist
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1408
|
+
deepseek-v3 Reasoning + code
|
|
1409
|
+
minimax-m2.5 Agentic coding
|
|
1410
|
+
kimi-k2.5 Agentic + long context
|
|
1317
1411
|
deepseek-r1 Deep reasoning
|
|
1412
|
+
gpt-oss-120b Budget + fast
|
|
1318
1413
|
... run /models inside kyma for full list
|
|
1319
1414
|
|
|
1320
1415
|
Commands (inside kyma):
|
|
@@ -1389,12 +1484,12 @@ async function checkAndUpdate() {
|
|
|
1389
1484
|
if (!res.ok) return false;
|
|
1390
1485
|
const data = await res.json();
|
|
1391
1486
|
const latest = data.version;
|
|
1392
|
-
if (!latest || latest ===
|
|
1487
|
+
if (!latest || latest === VERSION3) return false;
|
|
1393
1488
|
const parse = (v) => v.split(".").map(Number);
|
|
1394
|
-
const [cM, cm, cp] = parse(
|
|
1489
|
+
const [cM, cm, cp] = parse(VERSION3);
|
|
1395
1490
|
const [lM, lm, lp] = parse(latest);
|
|
1396
1491
|
if (lM < cM || lM === cM && lm < cm || lM === cM && lm === cm && lp <= cp) return false;
|
|
1397
|
-
console.log(` ${term.dim(`Updating kyma v${
|
|
1492
|
+
console.log(` ${term.dim(`Updating kyma v${VERSION3} \u2192 v${latest}...`)}`);
|
|
1398
1493
|
execSync("npm install -g @kyma-api/agent@latest", { stdio: "ignore", timeout: 6e4 });
|
|
1399
1494
|
console.log(` ${term.dim(`Updated to v${latest}. Restarting...`)}`);
|
|
1400
1495
|
return true;
|
|
@@ -1409,7 +1504,7 @@ async function main(argv) {
|
|
|
1409
1504
|
process.exit(0);
|
|
1410
1505
|
}
|
|
1411
1506
|
if (parsed.version) {
|
|
1412
|
-
console.log(`kyma v${
|
|
1507
|
+
console.log(`kyma v${VERSION3}`);
|
|
1413
1508
|
process.exit(0);
|
|
1414
1509
|
}
|
|
1415
1510
|
if (parsed.verbose || process.env.KYMA_DEBUG === "1") {
|
|
@@ -1428,7 +1523,10 @@ async function main(argv) {
|
|
|
1428
1523
|
}
|
|
1429
1524
|
}
|
|
1430
1525
|
ensureAgentDir();
|
|
1431
|
-
await runOnboarding();
|
|
1526
|
+
const wasLoggedIn = await runOnboarding();
|
|
1527
|
+
if (wasLoggedIn && process.stdout.isTTY) {
|
|
1528
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
1529
|
+
}
|
|
1432
1530
|
if (process.env.KYMA_VERBOSE) {
|
|
1433
1531
|
const missing = ["fd", "rg"].filter((cmd) => {
|
|
1434
1532
|
try {
|
|
@@ -1448,9 +1546,6 @@ async function main(argv) {
|
|
|
1448
1546
|
initialMessage: parsed.initialMessage,
|
|
1449
1547
|
continueSession: parsed.continueSession
|
|
1450
1548
|
});
|
|
1451
|
-
if (process.stdout.isTTY) {
|
|
1452
|
-
process.stdout.write("\x1B[2J\x1B[H");
|
|
1453
|
-
}
|
|
1454
1549
|
const interactiveMode = new KymaInteractiveMode(runtime, interactiveOptions);
|
|
1455
1550
|
await interactiveMode.run();
|
|
1456
1551
|
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kyma-api/agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Kyma coding agent — one account, many models. AI-powered coding assistant backed by Kyma API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"kyma": "bin/kyma.js"
|
|
7
|
+
"kyma": "bin/kyma.js",
|
|
8
|
+
"kyma-ter": "bin/kyma-ter.js"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"bin/",
|
|
11
12
|
"dist/",
|
|
12
|
-
"themes/"
|
|
13
|
+
"themes/",
|
|
14
|
+
"scripts/"
|
|
13
15
|
],
|
|
14
16
|
"scripts": {
|
|
17
|
+
"postinstall": "node scripts/postinstall.js",
|
|
15
18
|
"build": "tsup",
|
|
16
19
|
"pretest": "npm run build",
|
|
17
20
|
"test": "vitest run",
|
|
@@ -37,7 +40,7 @@
|
|
|
37
40
|
"ai",
|
|
38
41
|
"llm",
|
|
39
42
|
"cli",
|
|
40
|
-
"
|
|
43
|
+
"models",
|
|
41
44
|
"coding-assistant"
|
|
42
45
|
],
|
|
43
46
|
"repository": {
|
|
@@ -47,6 +50,7 @@
|
|
|
47
50
|
},
|
|
48
51
|
"homepage": "https://kymaapi.com",
|
|
49
52
|
"author": "Son Piaz <sonxpiaz@gmail.com>",
|
|
53
|
+
"kymaTerminal": "0.1.0",
|
|
50
54
|
"license": "MIT",
|
|
51
55
|
"dependencies": {
|
|
52
56
|
"@mariozechner/pi-coding-agent": "0.66.1",
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// 1. Symlink Pi's built-in assets (themes, export-html) into the expected paths.
|
|
4
|
+
// 2. Download kyma-ter Go binary if needed.
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
existsSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
symlinkSync,
|
|
10
|
+
unlinkSync,
|
|
11
|
+
readdirSync,
|
|
12
|
+
readFileSync,
|
|
13
|
+
writeFileSync,
|
|
14
|
+
chmodSync,
|
|
15
|
+
} from "fs";
|
|
16
|
+
import { join, dirname } from "path";
|
|
17
|
+
import { fileURLToPath } from "url";
|
|
18
|
+
import { createRequire } from "module";
|
|
19
|
+
import https from "https";
|
|
20
|
+
import http from "http";
|
|
21
|
+
import os from "os";
|
|
22
|
+
|
|
23
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const root = dirname(__dirname);
|
|
25
|
+
|
|
26
|
+
// ── Pi symlinks ─────────────────────────────────────────────────────────
|
|
27
|
+
const piDist = join(root, "node_modules/@mariozechner/pi-coding-agent/dist");
|
|
28
|
+
if (existsSync(piDist)) {
|
|
29
|
+
const links = [
|
|
30
|
+
{
|
|
31
|
+
src: join(piDist, "modes/interactive/theme"),
|
|
32
|
+
dest: join(root, "src/modes/interactive/theme"),
|
|
33
|
+
files: ["dark.json", "light.json"],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
src: join(piDist, "core/export-html"),
|
|
37
|
+
dest: join(root, "src/core/export-html"),
|
|
38
|
+
files: null,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
for (const { src, dest, files } of links) {
|
|
43
|
+
if (!existsSync(src)) continue;
|
|
44
|
+
mkdirSync(dest, { recursive: true });
|
|
45
|
+
|
|
46
|
+
const targets = files || readdirSync(src);
|
|
47
|
+
for (const file of targets) {
|
|
48
|
+
const srcPath = join(src, file);
|
|
49
|
+
const destPath = join(dest, file);
|
|
50
|
+
if (!existsSync(srcPath)) continue;
|
|
51
|
+
if (existsSync(destPath)) unlinkSync(destPath);
|
|
52
|
+
symlinkSync(srcPath, destPath);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── kyma-ter binary download ────────────────────────────────────────────
|
|
58
|
+
const KYMA_HOME = join(os.homedir(), ".kyma", "ter");
|
|
59
|
+
const BIN_DIR = join(KYMA_HOME, "bin");
|
|
60
|
+
const VERSION_FILE = join(KYMA_HOME, "version");
|
|
61
|
+
const BINARY_PATH = join(BIN_DIR, "kyma-ter");
|
|
62
|
+
const BASE_URL = "https://github.com/sonpiaz/kyma-releases/releases/download";
|
|
63
|
+
|
|
64
|
+
function getPlatform() {
|
|
65
|
+
const platformMap = { darwin: "darwin", linux: "linux" };
|
|
66
|
+
const archMap = { arm64: "arm64", x64: "amd64" };
|
|
67
|
+
return {
|
|
68
|
+
os: platformMap[process.platform],
|
|
69
|
+
cpu: archMap[process.arch],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function download(url) {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const client = url.startsWith("https") ? https : http;
|
|
76
|
+
client
|
|
77
|
+
.get(url, (res) => {
|
|
78
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
79
|
+
return download(res.headers.location).then(resolve).catch(reject);
|
|
80
|
+
}
|
|
81
|
+
if (res.statusCode !== 200) {
|
|
82
|
+
return reject(new Error(`HTTP ${res.statusCode}`));
|
|
83
|
+
}
|
|
84
|
+
const chunks = [];
|
|
85
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
86
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
87
|
+
res.on("error", reject);
|
|
88
|
+
})
|
|
89
|
+
.on("error", reject);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function installKymaTer() {
|
|
94
|
+
const require = createRequire(import.meta.url);
|
|
95
|
+
const pkg = require("../package.json");
|
|
96
|
+
const targetVersion = pkg.kymaTerminal;
|
|
97
|
+
if (!targetVersion) return;
|
|
98
|
+
|
|
99
|
+
const { os: platform, cpu } = getPlatform();
|
|
100
|
+
if (!platform || !cpu) {
|
|
101
|
+
console.log(
|
|
102
|
+
`kyma-ter: skipping binary download (unsupported platform: ${process.platform}-${process.arch})`
|
|
103
|
+
);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check cached version
|
|
108
|
+
if (existsSync(VERSION_FILE) && existsSync(BINARY_PATH)) {
|
|
109
|
+
const installed = readFileSync(VERSION_FILE, "utf8").trim();
|
|
110
|
+
if (installed === targetVersion) return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const url = `${BASE_URL}/ter-v${targetVersion}/kyma-ter-${platform}-${cpu}`;
|
|
114
|
+
console.log(`Downloading kyma-ter v${targetVersion} for ${platform}/${cpu}...`);
|
|
115
|
+
|
|
116
|
+
const data = await download(url);
|
|
117
|
+
mkdirSync(BIN_DIR, { recursive: true });
|
|
118
|
+
writeFileSync(BINARY_PATH, data);
|
|
119
|
+
chmodSync(BINARY_PATH, 0o755);
|
|
120
|
+
writeFileSync(VERSION_FILE, targetVersion);
|
|
121
|
+
console.log(`kyma-ter v${targetVersion} installed.`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await installKymaTer();
|
|
126
|
+
} catch (err) {
|
|
127
|
+
// Don't fail the install — kyma CLI should still work without kyma-ter
|
|
128
|
+
if (process.env.npm_config_loglevel !== "silent") {
|
|
129
|
+
console.log(`kyma-ter: binary download skipped (${err.message})`);
|
|
130
|
+
console.log(` You can install manually from https://kymaapi.com/ter`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Always exit cleanly — postinstall must not block npm link/install
|
|
135
|
+
process.exit(0);
|
package/themes/kyma-dark.json
CHANGED
|
@@ -2,33 +2,34 @@
|
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
3
|
"name": "kyma-dark",
|
|
4
4
|
"vars": {
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"green": "#
|
|
11
|
-
"red": "#
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
5
|
+
"brand": "#C9A84C",
|
|
6
|
+
"brandLight": "#DDBF6F",
|
|
7
|
+
"cyan": "#56B6C2",
|
|
8
|
+
"purple": "#9D7CD8",
|
|
9
|
+
"peach": "#FAB283",
|
|
10
|
+
"green": "#7FD88F",
|
|
11
|
+
"red": "#E06C75",
|
|
12
|
+
"amber": "#F5A742",
|
|
13
|
+
"text": "#EEEEEE",
|
|
14
|
+
"gray": "#808080",
|
|
15
|
+
"dimGray": "#606060",
|
|
16
|
+
"darkGray": "#484848",
|
|
17
|
+
"subtleBorder": "#3C3C3C",
|
|
18
|
+
"selectedBg": "#1E1E1E",
|
|
19
|
+
"userMsgBg": "#1A1A1A",
|
|
20
|
+
"toolPendingBg": "#141414",
|
|
21
|
+
"toolSuccessBg": "#1A2B1F",
|
|
22
|
+
"toolErrorBg": "#2A1A1A",
|
|
23
|
+
"customMsgBg": "#1A1A1A"
|
|
23
24
|
},
|
|
24
25
|
"colors": {
|
|
25
|
-
"accent": "
|
|
26
|
-
"border": "
|
|
27
|
-
"borderAccent": "
|
|
26
|
+
"accent": "brand",
|
|
27
|
+
"border": "subtleBorder",
|
|
28
|
+
"borderAccent": "cyan",
|
|
28
29
|
"borderMuted": "darkGray",
|
|
29
30
|
"success": "green",
|
|
30
31
|
"error": "red",
|
|
31
|
-
"warning": "
|
|
32
|
+
"warning": "amber",
|
|
32
33
|
"muted": "gray",
|
|
33
34
|
"dim": "dimGray",
|
|
34
35
|
"text": "",
|
|
@@ -39,55 +40,55 @@
|
|
|
39
40
|
"userMessageText": "",
|
|
40
41
|
"customMessageBg": "customMsgBg",
|
|
41
42
|
"customMessageText": "",
|
|
42
|
-
"customMessageLabel": "
|
|
43
|
+
"customMessageLabel": "gray",
|
|
43
44
|
"toolPendingBg": "toolPendingBg",
|
|
44
45
|
"toolSuccessBg": "toolSuccessBg",
|
|
45
46
|
"toolErrorBg": "toolErrorBg",
|
|
46
47
|
"toolTitle": "",
|
|
47
|
-
"toolOutput": "#
|
|
48
|
+
"toolOutput": "#D4D4D4",
|
|
48
49
|
|
|
49
|
-
"mdHeading": "
|
|
50
|
-
"mdLink": "
|
|
50
|
+
"mdHeading": "purple",
|
|
51
|
+
"mdLink": "cyan",
|
|
51
52
|
"mdLinkUrl": "dimGray",
|
|
52
|
-
"mdCode": "
|
|
53
|
-
"mdCodeBlock": "
|
|
54
|
-
"mdCodeBlockBorder": "
|
|
53
|
+
"mdCode": "green",
|
|
54
|
+
"mdCodeBlock": "text",
|
|
55
|
+
"mdCodeBlockBorder": "darkGray",
|
|
55
56
|
"mdQuote": "gray",
|
|
56
|
-
"mdQuoteBorder": "
|
|
57
|
-
"mdHr": "
|
|
58
|
-
"mdListBullet": "
|
|
57
|
+
"mdQuoteBorder": "darkGray",
|
|
58
|
+
"mdHr": "darkGray",
|
|
59
|
+
"mdListBullet": "cyan",
|
|
59
60
|
|
|
60
|
-
"toolDiffAdded": "
|
|
61
|
-
"toolDiffRemoved": "
|
|
61
|
+
"toolDiffAdded": "#4FD6BE",
|
|
62
|
+
"toolDiffRemoved": "#C53B53",
|
|
62
63
|
"toolDiffContext": "gray",
|
|
63
64
|
|
|
64
65
|
"syntaxComment": "#6A9955",
|
|
65
66
|
"syntaxKeyword": "purple",
|
|
66
|
-
"syntaxFunction": "
|
|
67
|
-
"syntaxVariable": "#
|
|
68
|
-
"syntaxString": "
|
|
69
|
-
"syntaxNumber": "
|
|
70
|
-
"syntaxType": "
|
|
71
|
-
"syntaxOperator": "
|
|
67
|
+
"syntaxFunction": "peach",
|
|
68
|
+
"syntaxVariable": "#E06C75",
|
|
69
|
+
"syntaxString": "green",
|
|
70
|
+
"syntaxNumber": "amber",
|
|
71
|
+
"syntaxType": "cyan",
|
|
72
|
+
"syntaxOperator": "cyan",
|
|
72
73
|
"syntaxPunctuation": "#D4D4D4",
|
|
73
74
|
|
|
74
75
|
"thinkingOff": "darkGray",
|
|
75
76
|
"thinkingMinimal": "darkGray",
|
|
76
|
-
"thinkingLow": "#
|
|
77
|
+
"thinkingLow": "#505050",
|
|
77
78
|
"thinkingMedium": "dimGray",
|
|
78
|
-
"thinkingHigh": "#
|
|
79
|
-
"thinkingXhigh": "
|
|
79
|
+
"thinkingHigh": "#76B5BD",
|
|
80
|
+
"thinkingXhigh": "cyan",
|
|
80
81
|
|
|
81
82
|
"bashMode": "green",
|
|
82
83
|
|
|
83
84
|
"modeCode": "#5B9CF5",
|
|
84
|
-
"modeReason": "
|
|
85
|
-
"modeFast": "
|
|
86
|
-
"modeCreative": "
|
|
85
|
+
"modeReason": "purple",
|
|
86
|
+
"modeFast": "green",
|
|
87
|
+
"modeCreative": "red"
|
|
87
88
|
},
|
|
88
89
|
"export": {
|
|
89
|
-
"pageBg": "#
|
|
90
|
-
"cardBg": "#
|
|
91
|
-
"infoBg": "#
|
|
90
|
+
"pageBg": "#0A0A0A",
|
|
91
|
+
"cardBg": "#141414",
|
|
92
|
+
"infoBg": "#1A1A1A"
|
|
92
93
|
}
|
|
93
94
|
}
|