@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.
@@ -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 DIM = "\x1B[38;2;107;114;133m";
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 WHITE = "\x1B[38;2;232;234;242m";
182
- var DARK_GRAY = "\x1B[38;2;74;78;92m";
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
- hr: (w = 36) => `${DARK_GRAY}${"\u2501".repeat(w)}${RESET}`,
191
- /** Render a gold-bordered card. Lines are padded to uniform width. */
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 = `${GOLD} \u256D${"\u2500".repeat(innerW)}\u256E${RESET}`;
196
- const bot = `${GOLD} \u2570${"\u2500".repeat(innerW)}\u256F${RESET}`;
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 `${GOLD} \u2502${RESET} ${s}${" ".repeat(Math.max(0, pad))} ${GOLD}\u2502${RESET}`;
207
+ return `${BORDER} \u2502${RESET} ${s}${" ".repeat(Math.max(0, pad))} ${BORDER}\u2502${RESET}`;
200
208
  };
201
- const empty = `${GOLD} \u2502${RESET}${" ".repeat(innerW)}${GOLD}\u2502${RESET}`;
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-32b",
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-32b";
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 ask(question) {
416
- const rl = createInterface({ input: process.stdin, output: process.stdout });
449
+ function askHidden(question) {
417
450
  return new Promise((resolve) => {
418
- rl.question(question, (answer) => {
419
- rl.close();
420
- resolve(answer.trim());
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.dim("One account, many models.")}`);
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
- const answer = await ask(" Connect now? (Y/n) ");
441
- if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
442
- console.log(` ${term.dim("Skipped.")} Use ${term.gold("/connect")} anytime.`);
443
- console.log("");
444
- markOnboarded();
445
- return false;
446
- }
447
- try {
448
- const credentials = await loginKymaStandalone();
449
- saveKymaCredentials(credentials);
450
- const balStr = credentials.balance != null ? term.gold(`$${credentials.balance.toFixed(2)}`) : "";
451
- process.stdout.write("\x1B[2J\x1B[H");
452
- console.log("");
453
- console.log(` ${term.gold("\u25C7")} ${term.brand("Authorized")}`);
454
- console.log("");
455
- printBanner();
456
- console.log("");
457
- console.log(` ${term.brand("\u03A8")} ${term.white("You're in. Let's go.")}`);
458
- console.log("");
459
- console.log(` ${term.dim("Account")} ${term.white(credentials.email)}`);
460
- if (balStr) {
461
- console.log(` ${term.dim("Balance")} ${balStr}`);
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, writeFileSync as writeFileSync3 } from "fs";
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
- // --- Qwen ---
736
+ // --- Flagship ---
569
737
  {
570
- id: "qwen-3-32b",
571
- name: "Qwen 3 32B",
572
- tag: "fast coding",
573
- reasoning: false,
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.392, output: 0.81, cacheRead: 0.039, cacheWrite: 0 },
577
- contextWindow: 32768,
578
- maxTokens: 8192,
579
- modes: ["fast"]
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: "qwen-3.6-plus",
595
- name: "Qwen 3.6 Plus",
596
- tag: "flagship",
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.439, output: 2.633, cacheRead: 0.044, cacheWrite: 0 },
601
- contextWindow: 131072,
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
- // --- MiniMax ---
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
- // --- DeepSeek ---
797
+ // --- Agentic + Long Context ---
664
798
  {
665
- id: "deepseek-v3",
666
- name: "DeepSeek V3",
667
- tag: "reasoning + code",
668
- reasoning: true,
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.81, output: 2.295, cacheRead: 0, cacheWrite: 0 },
672
- contextWindow: 16e4,
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
- // --- Meta ---
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: "best value",
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
- // --- Moonshot ---
837
+ // --- Vision + Budget ---
714
838
  {
715
- id: "kimi-k2.5",
716
- name: "Kimi K2.5",
717
- tag: "long context",
839
+ id: "gemma-4-31b",
840
+ name: "Gemma 4 31B",
841
+ tag: "vision + budget",
718
842
  reasoning: false,
719
- vision: false,
720
- input: ["text"],
721
- cost: { input: 0.675, output: 3.78, cacheRead: 0, cacheWrite: 0 },
722
- contextWindow: 262144,
723
- maxTokens: 16384,
724
- modes: ["long"]
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
- const email = getKymaEmail();
919
+ headerEmail = getKymaEmail();
792
920
  const apiKey = getKymaApiKey();
793
- const loggedIn = !!apiKey;
794
- let balanceStr = "";
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
- balanceStr = `$${b.balance.toFixed(2)}`;
925
+ headerBalance = `$${b.balance.toFixed(2)}`;
799
926
  } catch {
800
927
  }
801
928
  }
802
- const repoName = ctx.cwd.replace(/^.*\//, "");
803
- const modelId = ctx.model?.id || "qwen-3-32b";
804
- ctx.ui.setHeader((_tui, theme) => ({
805
- render(width) {
806
- const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
807
- const truncate = (s, maxW2) => {
808
- let visible = 0;
809
- let i = 0;
810
- while (i < s.length && visible < maxW2) {
811
- if (s[i] === "\x1B") {
812
- const end = s.indexOf("m", i);
813
- if (end !== -1) {
814
- i = end + 1;
815
- continue;
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 && (loggedIn || getKymaApiKey())) {
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 /mode to switch`, "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} \u2014 /mode to switch`, "info");
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
- const star = m.id === currentId ? "* " : " ";
952
- const name = m.name.padEnd(20);
953
- const tag = m.tag.padEnd(20);
954
- const price = `$${m.cost.input.toFixed(2)}/$${m.cost.output.toFixed(2)}`;
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
- const chosen = filtered[idx];
965
- if (!chosen || chosen.id === currentId) return;
966
- const ok = await pi.setModel(toRuntimeModel(chosen, `${KYMA_BASE_URL}/v1`));
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 ${chosen.name} \u2014 ${chosen.tag}`, "info");
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(_args, ctx) {
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
- ctx.ui.notify("Opening browser to sign in...", "info");
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
- ctx.ui.notify(`${instructions}
1209
- URL: ${url}`, "info");
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(msg) {
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
- const bal = credentials.balance != null ? ` $${credentials.balance.toFixed(2)}` : "";
1220
- ctx.ui.notify(`Authorized \xB7 ${credentials.email}${bal}`, "info");
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
- ctx.ui.notify(`Login failed: ${err.message}`, "error");
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
- " /mode Switch model by task type",
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 VERSION2 = (() => {
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${VERSION2} \u2014 one account, many models \xB7 kymaapi.com
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-32b Fast coding (default)
1405
+ Models (9 curated for coding):
1406
+ qwen-3.6-plus Flagship (default)
1313
1407
  qwen-3-coder Code specialist
1314
- qwen-3.6-plus Flagship reasoning
1315
- gemma-4-31b Vision + cheap
1316
- gemini-2.5-flash Vision + 1M context
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 === VERSION2) return false;
1487
+ if (!latest || latest === VERSION3) return false;
1393
1488
  const parse = (v) => v.split(".").map(Number);
1394
- const [cM, cm, cp] = parse(VERSION2);
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${VERSION2} \u2192 v${latest}...`)}`);
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${VERSION2}`);
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.4",
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
- "open-source-models",
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);
@@ -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
- "accent": "#C9A84C",
6
- "accentLight": "#DDBF6F",
7
- "accentSoft": "#A89060",
8
- "teal": "#7DCFCF",
9
- "purple": "#B4A7D6",
10
- "green": "#00B894",
11
- "red": "#E17055",
12
- "yellow": "#FDCB6E",
13
- "gray": "#A0A6B8",
14
- "dimGray": "#6B7285",
15
- "darkGray": "#4A4E5C",
16
- "text": "#E8EAF2",
17
- "selectedBg": "#232119",
18
- "userMsgBg": "#1E1D18",
19
- "toolPendingBg": "#1B1D1E",
20
- "toolSuccessBg": "#14251D",
21
- "toolErrorBg": "#2A1C1E",
22
- "customMsgBg": "#211E18"
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": "accent",
26
- "border": "accent",
27
- "borderAccent": "accentLight",
26
+ "accent": "brand",
27
+ "border": "subtleBorder",
28
+ "borderAccent": "cyan",
28
29
  "borderMuted": "darkGray",
29
30
  "success": "green",
30
31
  "error": "red",
31
- "warning": "yellow",
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": "accentSoft",
43
+ "customMessageLabel": "gray",
43
44
  "toolPendingBg": "toolPendingBg",
44
45
  "toolSuccessBg": "toolSuccessBg",
45
46
  "toolErrorBg": "toolErrorBg",
46
47
  "toolTitle": "",
47
- "toolOutput": "#C8CCD8",
48
+ "toolOutput": "#D4D4D4",
48
49
 
49
- "mdHeading": "#F0E4A8",
50
- "mdLink": "teal",
50
+ "mdHeading": "purple",
51
+ "mdLink": "cyan",
51
52
  "mdLinkUrl": "dimGray",
52
- "mdCode": "teal",
53
- "mdCodeBlock": "gray",
54
- "mdCodeBlockBorder": "dimGray",
53
+ "mdCode": "green",
54
+ "mdCodeBlock": "text",
55
+ "mdCodeBlockBorder": "darkGray",
55
56
  "mdQuote": "gray",
56
- "mdQuoteBorder": "dimGray",
57
- "mdHr": "dimGray",
58
- "mdListBullet": "accent",
57
+ "mdQuoteBorder": "darkGray",
58
+ "mdHr": "darkGray",
59
+ "mdListBullet": "cyan",
59
60
 
60
- "toolDiffAdded": "green",
61
- "toolDiffRemoved": "red",
61
+ "toolDiffAdded": "#4FD6BE",
62
+ "toolDiffRemoved": "#C53B53",
62
63
  "toolDiffContext": "gray",
63
64
 
64
65
  "syntaxComment": "#6A9955",
65
66
  "syntaxKeyword": "purple",
66
- "syntaxFunction": "#E2C87A",
67
- "syntaxVariable": "#C8BFA0",
68
- "syntaxString": "#9BE9A8",
69
- "syntaxNumber": "#B5CEA8",
70
- "syntaxType": "teal",
71
- "syntaxOperator": "#D4D4D4",
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": "#5C5647",
77
+ "thinkingLow": "#505050",
77
78
  "thinkingMedium": "dimGray",
78
- "thinkingHigh": "#8B7A5F",
79
- "thinkingXhigh": "accentSoft",
79
+ "thinkingHigh": "#76B5BD",
80
+ "thinkingXhigh": "cyan",
80
81
 
81
82
  "bashMode": "green",
82
83
 
83
84
  "modeCode": "#5B9CF5",
84
- "modeReason": "#B4A7D6",
85
- "modeFast": "#00B894",
86
- "modeCreative": "#E17055"
85
+ "modeReason": "purple",
86
+ "modeFast": "green",
87
+ "modeCreative": "red"
87
88
  },
88
89
  "export": {
89
- "pageBg": "#13141C",
90
- "cardBg": "#1A1B26",
91
- "infoBg": "#251E14"
90
+ "pageBg": "#0A0A0A",
91
+ "cardBg": "#141414",
92
+ "infoBg": "#1A1A1A"
92
93
  }
93
94
  }