@kyma-api/agent 0.1.3 → 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 +396 -314
- package/package.json +8 -4
- package/scripts/postinstall.js +135 -0
- package/themes/kyma-dark.json +50 -49
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
|
|
|
@@ -146,7 +145,7 @@ var VERSION = (() => {
|
|
|
146
145
|
function normalizeKymaError(status, body) {
|
|
147
146
|
switch (status) {
|
|
148
147
|
case 401:
|
|
149
|
-
return { code: 401, severity: "error", message: "Session expired.", action: "Run /
|
|
148
|
+
return { code: 401, severity: "error", message: "Session expired.", action: "Run /connect to reconnect." };
|
|
150
149
|
case 402:
|
|
151
150
|
return { code: 402, severity: "error", message: "Insufficient Kyma credits.", action: "Run /billing to top up." };
|
|
152
151
|
case 429:
|
|
@@ -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);
|
|
493
|
+
});
|
|
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);
|
|
422
554
|
});
|
|
423
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("/login")}`);
|
|
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}${balanceStr ? ` \xB7 ${balanceStr}` : ""}` : "not connected";
|
|
827
|
-
const dirLine = `~/${repoName}`;
|
|
828
|
-
const connectLine = loggedIn ? null : `${theme.bold("/login")} 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;
|
|
@@ -914,25 +991,12 @@ var kymaRuntimeFactory = (pi) => {
|
|
|
914
991
|
wasLoggedIn = true;
|
|
915
992
|
postLoginShown = true;
|
|
916
993
|
const postKey = getKymaApiKey();
|
|
917
|
-
const postEmail = getKymaEmail();
|
|
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)}`, "info");
|
|
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}`, "info");
|
|
923
|
-
}
|
|
924
|
-
const modeOptions = [...MODE_TO_MODEL.entries()].map(
|
|
925
|
-
([mode, m]) => `${mode.padEnd(12)} ${m.name.padEnd(20)} ${m.tag}`
|
|
926
|
-
);
|
|
927
|
-
modeOptions.push("Keep default (fast)");
|
|
928
|
-
const modeChoice = await ctx.ui.select("Choose a mode for this session", modeOptions);
|
|
929
|
-
if (modeChoice && !modeChoice.startsWith("Keep")) {
|
|
930
|
-
const modeName = modeChoice.split(/\s+/)[0];
|
|
931
|
-
const modeModel = MODE_TO_MODEL.get(modeName);
|
|
932
|
-
if (modeModel) {
|
|
933
|
-
const ok = await pi.setModel(toRuntimeModel(modeModel, `${KYMA_BASE_URL}/v1`));
|
|
934
|
-
if (ok) ctx.ui.notify(`Mode: ${modeName} \u2192 ${modeModel.name}`, "info");
|
|
935
|
-
}
|
|
999
|
+
ctx.ui.notify(`Authorized \xB7 ${postEmail} \u2014 /models to switch`, "info");
|
|
936
1000
|
}
|
|
937
1001
|
}
|
|
938
1002
|
});
|
|
@@ -960,67 +1024,39 @@ var kymaRuntimeFactory = (pi) => {
|
|
|
960
1024
|
return;
|
|
961
1025
|
}
|
|
962
1026
|
}
|
|
963
|
-
const options = filtered.map((m) =>
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
const badges = [];
|
|
969
|
-
if (m.vision) badges.push("[img]");
|
|
970
|
-
if (m.reasoning) badges.push("[think]");
|
|
971
|
-
const badgeStr = badges.length ? " " + badges.join(" ") : "";
|
|
972
|
-
return `${star}${name}${tag}${price}${badgeStr}`;
|
|
973
|
-
});
|
|
974
|
-
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
|
+
);
|
|
975
1032
|
if (!selected) return;
|
|
976
1033
|
const idx = options.indexOf(selected);
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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`));
|
|
980
1050
|
if (ok) {
|
|
981
|
-
ctx.ui.notify(`Switched to ${
|
|
1051
|
+
ctx.ui.notify(`Switched to ${model.name} \u2014 ${model.tag}`, "info");
|
|
982
1052
|
} else {
|
|
983
|
-
ctx.ui.notify("Failed to switch model. Run /
|
|
1053
|
+
ctx.ui.notify("Failed to switch model. Run /connect to sign in.", "error");
|
|
984
1054
|
}
|
|
985
1055
|
}
|
|
986
1056
|
pi.registerCommand("models", {
|
|
987
1057
|
description: "Browse and switch Kyma models",
|
|
988
1058
|
handler: modelsHandler
|
|
989
1059
|
});
|
|
990
|
-
const MODE_LIST = [...MODE_TO_MODEL.entries()].map(([mode, m]) => ({
|
|
991
|
-
mode,
|
|
992
|
-
model: m,
|
|
993
|
-
label: `${mode.padEnd(12)} ${m.id.padEnd(20)} ${m.tag}`
|
|
994
|
-
}));
|
|
995
|
-
pi.registerCommand("mode", {
|
|
996
|
-
description: "Switch model by task type",
|
|
997
|
-
async handler(args, ctx) {
|
|
998
|
-
let entry;
|
|
999
|
-
if (args.trim()) {
|
|
1000
|
-
const m = MODE_TO_MODEL.get(args.trim());
|
|
1001
|
-
if (!m) {
|
|
1002
|
-
ctx.ui.notify(`Unknown mode "${args.trim()}". Available: ${[...MODE_TO_MODEL.keys()].join(", ")}`, "error");
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
|
-
entry = { mode: args.trim(), model: m };
|
|
1006
|
-
} else {
|
|
1007
|
-
const selected = await ctx.ui.select(
|
|
1008
|
-
"Choose mode",
|
|
1009
|
-
MODE_LIST.map((p) => p.label)
|
|
1010
|
-
);
|
|
1011
|
-
if (!selected) return;
|
|
1012
|
-
const idx = MODE_LIST.findIndex((p) => p.label === selected);
|
|
1013
|
-
entry = MODE_LIST[idx];
|
|
1014
|
-
}
|
|
1015
|
-
if (!entry) return;
|
|
1016
|
-
const ok = await pi.setModel(toRuntimeModel(entry.model, `${KYMA_BASE_URL}/v1`));
|
|
1017
|
-
if (ok) {
|
|
1018
|
-
ctx.ui.notify(`Mode: ${entry.mode} \u2192 ${entry.model.name}`, "info");
|
|
1019
|
-
} else {
|
|
1020
|
-
ctx.ui.notify("Failed to switch model. Run /login to sign in.", "error");
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
});
|
|
1024
1060
|
pi.registerCommand("status", {
|
|
1025
1061
|
description: "Account, credits, and diagnostics",
|
|
1026
1062
|
async handler(_args, ctx) {
|
|
@@ -1028,7 +1064,7 @@ var kymaRuntimeFactory = (pi) => {
|
|
|
1028
1064
|
const currentModel = ctx.model;
|
|
1029
1065
|
const lines = ["Kyma Status", DIV, ""];
|
|
1030
1066
|
if (!apiKey) {
|
|
1031
|
-
lines.push(" Not connected. Run /
|
|
1067
|
+
lines.push(" Not connected. Run /connect to sign in.");
|
|
1032
1068
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
1033
1069
|
return;
|
|
1034
1070
|
}
|
|
@@ -1079,7 +1115,7 @@ var kymaRuntimeFactory = (pi) => {
|
|
|
1079
1115
|
async handler(_args, ctx) {
|
|
1080
1116
|
const apiKey = getKymaApiKey();
|
|
1081
1117
|
if (!apiKey) {
|
|
1082
|
-
ctx.ui.notify("Not connected. Run /
|
|
1118
|
+
ctx.ui.notify("Not connected. Run /connect to sign in.", "error");
|
|
1083
1119
|
return;
|
|
1084
1120
|
}
|
|
1085
1121
|
try {
|
|
@@ -1147,7 +1183,7 @@ var kymaRuntimeFactory = (pi) => {
|
|
|
1147
1183
|
async handler(_args, ctx) {
|
|
1148
1184
|
const apiKey = getKymaApiKey();
|
|
1149
1185
|
if (!apiKey) {
|
|
1150
|
-
ctx.ui.notify("Not connected. Run /
|
|
1186
|
+
ctx.ui.notify("Not connected. Run /connect first.", "error");
|
|
1151
1187
|
return;
|
|
1152
1188
|
}
|
|
1153
1189
|
try {
|
|
@@ -1203,9 +1239,29 @@ var kymaRuntimeFactory = (pi) => {
|
|
|
1203
1239
|
ctx.ui.notify("Opening feedback page...", "info");
|
|
1204
1240
|
}
|
|
1205
1241
|
});
|
|
1206
|
-
pi.registerCommand("
|
|
1242
|
+
pi.registerCommand("connect", {
|
|
1207
1243
|
description: "Sign in to your Kyma account",
|
|
1208
|
-
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
|
+
}
|
|
1209
1265
|
if (getKymaApiKey()) {
|
|
1210
1266
|
const email = getKymaEmail();
|
|
1211
1267
|
const reconnect = await ctx.ui.confirm(
|
|
@@ -1214,25 +1270,50 @@ var kymaRuntimeFactory = (pi) => {
|
|
|
1214
1270
|
);
|
|
1215
1271
|
if (!reconnect) return;
|
|
1216
1272
|
}
|
|
1217
|
-
|
|
1273
|
+
const SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1274
|
+
let spinnerFrame = 0;
|
|
1275
|
+
let spinnerInterval = null;
|
|
1218
1276
|
try {
|
|
1219
1277
|
const credentials = await loginKyma({
|
|
1220
1278
|
onAuth({ url, instructions }) {
|
|
1221
|
-
|
|
1222
|
-
|
|
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
|
+
);
|
|
1223
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);
|
|
1224
1296
|
},
|
|
1225
|
-
onProgress(
|
|
1226
|
-
if (msg) ctx.ui.notify(msg, "info");
|
|
1297
|
+
onProgress() {
|
|
1227
1298
|
}
|
|
1228
1299
|
});
|
|
1300
|
+
if (spinnerInterval) clearInterval(spinnerInterval);
|
|
1301
|
+
ctx.ui.setWidget?.("connect-spinner", void 0);
|
|
1229
1302
|
saveKymaCredentials(credentials);
|
|
1230
1303
|
wasLoggedIn = true;
|
|
1231
1304
|
postLoginShown = true;
|
|
1232
|
-
|
|
1233
|
-
|
|
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");
|
|
1234
1312
|
} catch (err) {
|
|
1235
|
-
|
|
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");
|
|
1236
1317
|
}
|
|
1237
1318
|
}
|
|
1238
1319
|
});
|
|
@@ -1243,10 +1324,10 @@ URL: ${url}`, "info");
|
|
|
1243
1324
|
"Kyma Commands",
|
|
1244
1325
|
DIV,
|
|
1245
1326
|
"",
|
|
1246
|
-
" /
|
|
1247
|
-
" /
|
|
1327
|
+
" /connect Sign in to Kyma",
|
|
1328
|
+
" /disconnect Sign out",
|
|
1248
1329
|
" /models Browse and switch models",
|
|
1249
|
-
"
|
|
1330
|
+
" /models coding \xB7 /models vision \xB7 /models cheap",
|
|
1250
1331
|
" /status Account, credits, diagnostics",
|
|
1251
1332
|
" /balance Credits and rate limits",
|
|
1252
1333
|
" /usage Session cost and tokens",
|
|
@@ -1275,12 +1356,12 @@ URL: ${url}`, "info");
|
|
|
1275
1356
|
process.exit(0);
|
|
1276
1357
|
}
|
|
1277
1358
|
});
|
|
1278
|
-
pi.registerCommand("
|
|
1359
|
+
pi.registerCommand("disconnect", {
|
|
1279
1360
|
description: "Sign out of Kyma",
|
|
1280
1361
|
async handler(_args, ctx) {
|
|
1281
1362
|
const apiKey = getKymaApiKey();
|
|
1282
1363
|
if (!apiKey) {
|
|
1283
|
-
ctx.ui.notify("Not signed in. Run /
|
|
1364
|
+
ctx.ui.notify("Not signed in. Run /connect to sign in.", "info");
|
|
1284
1365
|
return;
|
|
1285
1366
|
}
|
|
1286
1367
|
if (process.env.KYMA_API_KEY) {
|
|
@@ -1293,7 +1374,7 @@ URL: ${url}`, "info");
|
|
|
1293
1374
|
clearKymaCredentials();
|
|
1294
1375
|
wasLoggedIn = false;
|
|
1295
1376
|
postLoginShown = false;
|
|
1296
|
-
ctx.ui.notify("Signed out. Run /
|
|
1377
|
+
ctx.ui.notify("Signed out. Run /connect to sign in again.", "info");
|
|
1297
1378
|
}
|
|
1298
1379
|
});
|
|
1299
1380
|
pi.registerCommand("clear", {
|
|
@@ -1306,7 +1387,7 @@ URL: ${url}`, "info");
|
|
|
1306
1387
|
|
|
1307
1388
|
// src/sdk/main.ts
|
|
1308
1389
|
var PKG_DIR2 = process.env.PI_PACKAGE_DIR || dirname4(dirname4(dirname4(fileURLToPath4(import.meta.url))));
|
|
1309
|
-
var
|
|
1390
|
+
var VERSION3 = (() => {
|
|
1310
1391
|
try {
|
|
1311
1392
|
return JSON.parse(readFileSync5(join6(PKG_DIR2, "package.json"), "utf-8")).version || "0.1.0";
|
|
1312
1393
|
} catch {
|
|
@@ -1314,20 +1395,21 @@ var VERSION2 = (() => {
|
|
|
1314
1395
|
}
|
|
1315
1396
|
})();
|
|
1316
1397
|
function printHelp() {
|
|
1317
|
-
console.log(`kyma v${
|
|
1398
|
+
console.log(`kyma v${VERSION3} \u2014 one account, many models \xB7 kymaapi.com
|
|
1318
1399
|
|
|
1319
1400
|
Usage:
|
|
1320
1401
|
kyma [messages...] Interactive coding agent
|
|
1321
1402
|
kyma "prompt" Start with a message
|
|
1322
1403
|
kyma -c Continue previous session
|
|
1323
1404
|
|
|
1324
|
-
Models:
|
|
1325
|
-
qwen-3-
|
|
1405
|
+
Models (9 curated for coding):
|
|
1406
|
+
qwen-3.6-plus Flagship (default)
|
|
1326
1407
|
qwen-3-coder Code specialist
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1408
|
+
deepseek-v3 Reasoning + code
|
|
1409
|
+
minimax-m2.5 Agentic coding
|
|
1410
|
+
kimi-k2.5 Agentic + long context
|
|
1330
1411
|
deepseek-r1 Deep reasoning
|
|
1412
|
+
gpt-oss-120b Budget + fast
|
|
1331
1413
|
... run /models inside kyma for full list
|
|
1332
1414
|
|
|
1333
1415
|
Commands (inside kyma):
|
|
@@ -1402,12 +1484,12 @@ async function checkAndUpdate() {
|
|
|
1402
1484
|
if (!res.ok) return false;
|
|
1403
1485
|
const data = await res.json();
|
|
1404
1486
|
const latest = data.version;
|
|
1405
|
-
if (!latest || latest ===
|
|
1487
|
+
if (!latest || latest === VERSION3) return false;
|
|
1406
1488
|
const parse = (v) => v.split(".").map(Number);
|
|
1407
|
-
const [cM, cm, cp] = parse(
|
|
1489
|
+
const [cM, cm, cp] = parse(VERSION3);
|
|
1408
1490
|
const [lM, lm, lp] = parse(latest);
|
|
1409
1491
|
if (lM < cM || lM === cM && lm < cm || lM === cM && lm === cm && lp <= cp) return false;
|
|
1410
|
-
console.log(` ${term.dim(`Updating kyma v${
|
|
1492
|
+
console.log(` ${term.dim(`Updating kyma v${VERSION3} \u2192 v${latest}...`)}`);
|
|
1411
1493
|
execSync("npm install -g @kyma-api/agent@latest", { stdio: "ignore", timeout: 6e4 });
|
|
1412
1494
|
console.log(` ${term.dim(`Updated to v${latest}. Restarting...`)}`);
|
|
1413
1495
|
return true;
|
|
@@ -1422,7 +1504,7 @@ async function main(argv) {
|
|
|
1422
1504
|
process.exit(0);
|
|
1423
1505
|
}
|
|
1424
1506
|
if (parsed.version) {
|
|
1425
|
-
console.log(`kyma v${
|
|
1507
|
+
console.log(`kyma v${VERSION3}`);
|
|
1426
1508
|
process.exit(0);
|
|
1427
1509
|
}
|
|
1428
1510
|
if (parsed.verbose || process.env.KYMA_DEBUG === "1") {
|
|
@@ -1441,7 +1523,10 @@ async function main(argv) {
|
|
|
1441
1523
|
}
|
|
1442
1524
|
}
|
|
1443
1525
|
ensureAgentDir();
|
|
1444
|
-
await runOnboarding();
|
|
1526
|
+
const wasLoggedIn = await runOnboarding();
|
|
1527
|
+
if (wasLoggedIn && process.stdout.isTTY) {
|
|
1528
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
1529
|
+
}
|
|
1445
1530
|
if (process.env.KYMA_VERBOSE) {
|
|
1446
1531
|
const missing = ["fd", "rg"].filter((cmd) => {
|
|
1447
1532
|
try {
|
|
@@ -1461,9 +1546,6 @@ async function main(argv) {
|
|
|
1461
1546
|
initialMessage: parsed.initialMessage,
|
|
1462
1547
|
continueSession: parsed.continueSession
|
|
1463
1548
|
});
|
|
1464
|
-
if (process.stdout.isTTY) {
|
|
1465
|
-
process.stdout.write("\x1B[2J\x1B[H");
|
|
1466
|
-
}
|
|
1467
1549
|
const interactiveMode = new KymaInteractiveMode(runtime, interactiveOptions);
|
|
1468
1550
|
await interactiveMode.run();
|
|
1469
1551
|
}
|