loren-code 0.2.3 → 0.2.4

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/scripts/loren.js +116 -51
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loren-code",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Ollama Cloud Model Manager - Dynamic model switching, API key rotation, and real-time configuration updates",
5
5
  "author": "lorenzune",
6
6
  "license": "MIT",
package/scripts/loren.js CHANGED
@@ -25,22 +25,28 @@ process.chdir(projectRoot);
25
25
  ensureRuntimeDir();
26
26
  const envStatus = ensureEnvLocal(projectRoot, { logger: { warn() {} } });
27
27
 
28
- const ASCII_BANNER = `
29
- ██╗ ██████╗ ██████╗ ███████╗███╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗
30
- ██║ ██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ██╔════╝██╔═══██╗██╔══██╗██╔════╝
31
- ██║ ██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ██║ ██║ ██║██║ ██║█████╗
32
- ██║ ██║ ██║██╔══██╗██╔══╝ ██║╚██╗██║ ██║ ██║ ██║██║ ██║██╔══╝
33
- ███████╗╚██████╔╝██║ ██║███████╗██║ ╚████║ ╚██████╗╚██████╔╝██████╔╝███████╗
34
- ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
35
- `;
36
-
37
- const BANNER = `${ASCII_BANNER}
38
- LOREN CODE
39
- Smarter bridge, fewer rituals.
40
- `;
28
+ const ASCII_BANNER_LINES = [
29
+ "██╗ ██████╗ ██████╗ ███████╗███╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗",
30
+ "██║ ██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ██╔════╝██╔═══██╗██╔══██╗██╔════╝",
31
+ "██║ ██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ██║ ██║ ██║██║ ██║█████╗",
32
+ "██║ ██║ ██║██╔══██╗██╔══╝ ██║╚██╗██║ ██║ ██║ ██║██║ ██║██╔══╝",
33
+ "███████╗╚██████╔╝██║ ██║███████╗██║ ╚████║ ╚██████╗╚██████╔╝██████╔╝███████╗",
34
+ "╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝",
35
+ ];
41
36
  const GREEN = "\x1b[32m";
42
37
  const YELLOW = "\x1b[33m";
38
+ const RED = "\x1b[31m";
39
+ const CYAN = "\x1b[36m";
40
+ const MAGENTA = "\x1b[35m";
43
41
  const RESET = "\x1b[0m";
42
+ const BANNER_COLORS = [
43
+ "\x1b[38;2;198;218;255m",
44
+ "\x1b[38;2;190;212;255m",
45
+ "\x1b[38;2;181;206;255m",
46
+ "\x1b[38;2;188;201;255m",
47
+ "\x1b[38;2;196;197;255m",
48
+ "\x1b[38;2;205;193;255m",
49
+ ];
44
50
 
45
51
  const COMMANDS = {
46
52
  model: {
@@ -114,25 +120,8 @@ async function main() {
114
120
  }
115
121
 
116
122
  async function listModels() {
117
- const config = loadConfig();
118
-
119
123
  try {
120
- const response = await fetch(`${config.upstreamBaseUrl}/api/tags`, {
121
- headers: { accept: "application/json" },
122
- });
123
-
124
- if (!response.ok) {
125
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
126
- }
127
-
128
- const data = await response.json();
129
- let models = Array.isArray(data.models) ? data.models : [];
130
-
131
- models = models.sort((a, b) => {
132
- const dateA = a.modified_at ? new Date(a.modified_at).getTime() : 0;
133
- const dateB = b.modified_at ? new Date(b.modified_at).getTime() : 0;
134
- return dateB - dateA;
135
- });
124
+ const { config, models } = await fetchAvailableModels();
136
125
 
137
126
  console.log("\nAvailable models from Ollama Cloud:");
138
127
  console.log("─".repeat(70));
@@ -215,12 +204,7 @@ function setModel(args) {
215
204
  saveEnvFile(envFilePath, envVars);
216
205
  syncClaudeSelectedModel(requestedModel);
217
206
 
218
- console.log(`\nDefault model set to ${requestedModel}.`);
219
- console.log("Fresh requests will use it right away.");
220
- if (fs.existsSync(claudeSettingsPath)) {
221
- console.log("Claude Code settings were updated too.");
222
- }
223
- console.log("");
207
+ return requestedModel;
224
208
  }
225
209
 
226
210
  function showCurrentModel() {
@@ -521,7 +505,7 @@ async function runSetupWizard(config) {
521
505
  console.log("");
522
506
 
523
507
  if (process.platform === "win32") {
524
- const installClaude = (await rl.question("Install Claude Code integration too? [Y/n] ")).trim().toLowerCase();
508
+ const installClaude = (await askQuestion(rl, "Install Claude Code integration too? [Y/n] ")).trim().toLowerCase();
525
509
  if (installClaude === "" || installClaude === "y" || installClaude === "yes") {
526
510
  installClaudeIntegration();
527
511
  console.log(`${GREEN}✓ Claude Code integration installed.${RESET}`);
@@ -531,14 +515,16 @@ async function runSetupWizard(config) {
531
515
  }
532
516
  }
533
517
 
534
- const startNow = (await rl.question("Start the bridge now? [Y/n] ")).trim().toLowerCase();
518
+ await promptForModelSelection(rl);
519
+
520
+ const startNow = (await askQuestion(rl, "Start the bridge now? [Y/n] ")).trim().toLowerCase();
535
521
  if (startNow === "" || startNow === "y" || startNow === "yes") {
536
522
  startServer({ quiet: true });
537
523
  console.log(`${GREEN}✓ Bridge started.${RESET}`);
538
524
  console.log("");
539
525
  }
540
526
 
541
- console.log("Setup complete. Fewer steps, fewer goblins.");
527
+ console.log(`${GREEN}Setup complete. Fewer steps, fewer goblins.${RESET}`);
542
528
  console.log("");
543
529
  } finally {
544
530
  rl.close();
@@ -546,21 +532,20 @@ async function runSetupWizard(config) {
546
532
  }
547
533
 
548
534
  function printWizardIntro() {
549
- console.log(BANNER);
535
+ printBanner();
550
536
  if (envStatus.migrated) {
551
- console.log("Your previous settings were imported automatically.");
537
+ console.log(`${MAGENTA}Your previous settings were imported automatically.${RESET}`);
552
538
  } else if (envStatus.created) {
553
- console.log("A fresh config is ready.");
539
+ console.log(`${GREEN}A fresh config is ready.${RESET}`);
554
540
  }
555
- console.log(`Welcome${displayName ? `, ${displayName}` : ""}.`);
541
+ console.log(`${CYAN}Welcome${displayName ? `, ${displayName}` : ""}.${RESET}`);
556
542
  console.log(`${YELLOW}Run \`loren\` in an interactive terminal to finish setup.${RESET}`);
557
- console.log("Let's get Loren ready in one quick pass.");
543
+ console.log(`${GREEN}Let's get Loren ready in one quick pass.${RESET}`);
558
544
  console.log("");
559
- printCommandSummary();
560
545
  }
561
546
 
562
547
  function printWelcomeBack(config) {
563
- console.log(BANNER);
548
+ printBanner();
564
549
  console.log(`Welcome back${displayName ? `, ${displayName}` : ""}.`);
565
550
  console.log(`${config.apiKeys.length} key(s) loaded.`);
566
551
  console.log(`Current default model: ${config.defaultModel}`);
@@ -575,7 +560,7 @@ function printQuickSetup(config) {
575
560
  return;
576
561
  }
577
562
 
578
- console.log(BANNER);
563
+ printBanner();
579
564
  console.log(`Welcome${displayName ? `, ${displayName}` : ""}.`);
580
565
  console.log(`${YELLOW}Run \`loren\` in an interactive terminal to finish setup.${RESET}`);
581
566
  console.log("");
@@ -601,20 +586,88 @@ function installClaudeIntegration() {
601
586
 
602
587
  async function promptForApiKeys(rl) {
603
588
  while (true) {
604
- const rawKeys = (await rl.question("Paste your Ollama API key(s), separated by commas: ")).trim();
589
+ const rawKeys = (await askQuestion(rl, "Paste your Ollama API key(s), separated by commas: ")).trim();
605
590
  const keys = splitKeyList(rawKeys);
606
591
 
607
592
  if (keys.length > 0) {
608
593
  return keys;
609
594
  }
610
595
 
611
- console.log("At least one API key is required to continue.");
596
+ console.log(`${RED}At least one API key is required to continue.${RESET}`);
597
+ console.log("");
598
+ }
599
+ }
600
+
601
+ async function promptForModelSelection(rl) {
602
+ try {
603
+ const { models } = await fetchAvailableModels();
604
+
605
+ console.log("Available models:");
606
+ console.log("─".repeat(70));
607
+ for (const model of models) {
608
+ const modelId = model.model || model.name;
609
+ const size = formatSize(model.size);
610
+ const modified = model.modified_at ? new Date(model.modified_at).toLocaleDateString() : "unknown";
611
+ console.log(`${modelId.padEnd(30)}${size.padStart(12)}${modified.padStart(12)}`);
612
+ }
613
+ console.log("");
614
+
615
+ while (true) {
616
+ const requestedModel = (await askQuestion(rl, "Choose the default model: ")).trim();
617
+ const match = models.find((model) => (model.model || model.name) === requestedModel);
618
+
619
+ if (!requestedModel) {
620
+ console.log(`${RED}Please choose a model from the list above.${RESET}`);
621
+ console.log("");
622
+ continue;
623
+ }
624
+
625
+ if (!match) {
626
+ console.log(`${RED}That model is not in the current list.${RESET}`);
627
+ console.log("");
628
+ continue;
629
+ }
630
+
631
+ const model = setModel([requestedModel]);
632
+ console.log(`${GREEN}✓ Default model set to ${model}.${RESET}`);
633
+ console.log("");
634
+ return;
635
+ }
636
+ } catch (error) {
637
+ console.log(`${RED}Couldn't load models right now: ${error.message}${RESET}`);
638
+ console.log(`${RED}Please fix your keys and run \`loren model:list\` after setup.${RESET}`);
612
639
  console.log("");
640
+ throw error;
613
641
  }
614
642
  }
615
643
 
644
+ async function fetchAvailableModels() {
645
+ const config = loadConfig();
646
+ const response = await fetch(`${config.upstreamBaseUrl}/api/tags`, {
647
+ headers: { accept: "application/json" },
648
+ });
649
+
650
+ if (!response.ok) {
651
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
652
+ }
653
+
654
+ const data = await response.json();
655
+ let models = Array.isArray(data.models) ? data.models : [];
656
+ models = models.sort((a, b) => {
657
+ const dateA = a.modified_at ? new Date(a.modified_at).getTime() : 0;
658
+ const dateB = b.modified_at ? new Date(b.modified_at).getTime() : 0;
659
+ return dateB - dateA;
660
+ });
661
+
662
+ return { config, models };
663
+ }
664
+
665
+ function askQuestion(rl, prompt) {
666
+ return rl.question(`${CYAN}${prompt}${RESET}`);
667
+ }
668
+
616
669
  function printHelp() {
617
- console.log(BANNER);
670
+ printBanner();
618
671
  printCommandSummary();
619
672
  }
620
673
 
@@ -652,6 +705,18 @@ function getDisplayName() {
652
705
  return baseName || "";
653
706
  }
654
707
 
708
+ function printBanner() {
709
+ const coloredBanner = ASCII_BANNER_LINES
710
+ .map((line, index) => `${BANNER_COLORS[index] || ""}${line}${RESET}`)
711
+ .join("\n");
712
+
713
+ console.log(coloredBanner);
714
+ console.log("");
715
+ console.log(`${CYAN}LOREN CODE${RESET}`);
716
+ console.log("Smarter bridge, fewer rituals.");
717
+ console.log("");
718
+ }
719
+
655
720
  main().catch((error) => {
656
721
  console.error(error instanceof Error ? error.message : String(error));
657
722
  process.exit(1);