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.
- package/package.json +1 -1
- package/scripts/loren.js +116 -51
package/package.json
CHANGED
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
535
|
+
printBanner();
|
|
550
536
|
if (envStatus.migrated) {
|
|
551
|
-
console.log(
|
|
537
|
+
console.log(`${MAGENTA}Your previous settings were imported automatically.${RESET}`);
|
|
552
538
|
} else if (envStatus.created) {
|
|
553
|
-
console.log(
|
|
539
|
+
console.log(`${GREEN}A fresh config is ready.${RESET}`);
|
|
554
540
|
}
|
|
555
|
-
console.log(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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);
|