lovecode-ai 0.1.2 → 0.1.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/dist/chunk-3AHNSXQX.js +86 -0
- package/dist/{chunk-G7VQGYJW.js → chunk-7CT3XDH6.js} +5 -352
- package/dist/chunk-LJ7HTOIK.js +154 -0
- package/dist/chunk-Y3HADLWO.js +366 -0
- package/dist/config-FJNTTKR3.js +16 -0
- package/dist/env-HJQWWL6N.js +14 -0
- package/dist/{git-TBOGPTY4.js → git-FZPRJVFI.js} +2 -1
- package/dist/index.js +531 -639
- package/dist/registry-ADSIKXA4.js +22 -0
- package/package.json +1 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// src/config/env.ts
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
var ENV_FILENAME = ".env";
|
|
6
|
+
var ENV_EXAMPLE = ".env.example";
|
|
7
|
+
var KNOWN_ENV_VARS = [
|
|
8
|
+
{ key: "GROQ_API_KEY", value: "", description: "Groq API key for LLM access", required: false },
|
|
9
|
+
{ key: "OPENROUTER_API_KEY", value: "", description: "OpenRouter API key for LLM access", required: false },
|
|
10
|
+
{ key: "TOGETHER_API_KEY", value: "", description: "Together AI API key", required: false },
|
|
11
|
+
{ key: "HUGGINGFACE_API_KEY", value: "", description: "HuggingFace API key", required: false },
|
|
12
|
+
{ key: "OPENAI_API_KEY", value: "", description: "OpenAI API key (fallback)", required: false },
|
|
13
|
+
{ key: "OLLAMA_URL", value: "http://localhost:11434", description: "Ollama server URL", required: false },
|
|
14
|
+
{ key: "LOVECODE_LOG_LEVEL", value: "info", description: "Log level (debug|info|warn|error)", required: false },
|
|
15
|
+
{ key: "LOVECODE_MODEL", value: "", description: "Override default model", required: false },
|
|
16
|
+
{ key: "LOVECODE_THEME", value: "", description: "Override default theme", required: false },
|
|
17
|
+
{ key: "LOVECODE_APPROVAL_MODE", value: "", description: "Override approval mode (smart|strict|permissive)", required: false },
|
|
18
|
+
{ key: "LOVECODE_TELEMETRY", value: "", description: "Enable telemetry (true|false)", required: false }
|
|
19
|
+
];
|
|
20
|
+
function loadEnv(rootDir) {
|
|
21
|
+
const envPath = path.resolve(rootDir || process.cwd(), ENV_FILENAME);
|
|
22
|
+
const vars = {};
|
|
23
|
+
if (fs.existsSync(envPath)) {
|
|
24
|
+
const content = fs.readFileSync(envPath, "utf-8");
|
|
25
|
+
for (const line of content.split("\n")) {
|
|
26
|
+
const trimmed = line.trim();
|
|
27
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
28
|
+
const eqIdx = trimmed.indexOf("=");
|
|
29
|
+
if (eqIdx > 0) {
|
|
30
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
31
|
+
let value = trimmed.slice(eqIdx + 1).trim();
|
|
32
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
33
|
+
value = value.slice(1, -1);
|
|
34
|
+
}
|
|
35
|
+
vars[key] = value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
for (const v of KNOWN_ENV_VARS) {
|
|
40
|
+
const envVal = process.env[v.key];
|
|
41
|
+
if (envVal) vars[v.key] = envVal;
|
|
42
|
+
}
|
|
43
|
+
return vars;
|
|
44
|
+
}
|
|
45
|
+
function saveEnv(vars, rootDir) {
|
|
46
|
+
const envPath = path.resolve(rootDir || process.cwd(), ENV_FILENAME);
|
|
47
|
+
const dir = path.dirname(envPath);
|
|
48
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
49
|
+
const lines = ["# LoveCode AI - Environment Configuration", `# Created: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`, ""];
|
|
50
|
+
for (const v of KNOWN_ENV_VARS) {
|
|
51
|
+
const value = vars[v.key] !== void 0 ? vars[v.key] : v.value;
|
|
52
|
+
lines.push(`# ${v.description}`);
|
|
53
|
+
lines.push(`${v.key}=${value}`);
|
|
54
|
+
lines.push("");
|
|
55
|
+
}
|
|
56
|
+
fs.writeFileSync(envPath, lines.join("\n"), "utf-8");
|
|
57
|
+
}
|
|
58
|
+
function saveEnvExample(rootDir) {
|
|
59
|
+
const exPath = path.resolve(rootDir || process.cwd(), ENV_EXAMPLE);
|
|
60
|
+
const lines = ["# LoveCode AI - Environment Configuration Example", "# Copy this file to .env and fill in your values", ""];
|
|
61
|
+
for (const v of KNOWN_ENV_VARS) {
|
|
62
|
+
lines.push(`# ${v.description}`);
|
|
63
|
+
lines.push(v.required ? `${v.key}=` : `# ${v.key}=`);
|
|
64
|
+
lines.push("");
|
|
65
|
+
}
|
|
66
|
+
fs.writeFileSync(exPath, lines.join("\n"), "utf-8");
|
|
67
|
+
}
|
|
68
|
+
function formatEnvStatus(rootDir) {
|
|
69
|
+
const vars = loadEnv(rootDir);
|
|
70
|
+
const lines = [chalk.bold("\n Environment Variables")];
|
|
71
|
+
for (const v of KNOWN_ENV_VARS) {
|
|
72
|
+
const val = vars[v.key] || process.env[v.key] || "";
|
|
73
|
+
const set = val.length > 0;
|
|
74
|
+
const masked = set && v.key.includes("KEY") ? val.slice(0, 8) + "*".repeat(Math.min(val.length - 8, 20)) : val || "(not set)";
|
|
75
|
+
lines.push(` ${set ? chalk.green("\u2713") : chalk.dim("\u25CB")} ${chalk.dim(v.key.padEnd(25))} ${masked}`);
|
|
76
|
+
}
|
|
77
|
+
return lines.join("\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
KNOWN_ENV_VARS,
|
|
82
|
+
loadEnv,
|
|
83
|
+
saveEnv,
|
|
84
|
+
saveEnvExample,
|
|
85
|
+
formatEnvStatus
|
|
86
|
+
};
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getDefaultModel,
|
|
3
|
+
resolveModel
|
|
4
|
+
} from "./chunk-Y3HADLWO.js";
|
|
5
|
+
|
|
1
6
|
// src/git/commands.ts
|
|
2
7
|
import { execSync } from "child_process";
|
|
3
8
|
import * as fs from "fs";
|
|
@@ -279,354 +284,6 @@ function abbreviateDiff(diff, maxLines = 100) {
|
|
|
279
284
|
... (${lines.length - maxLines} more lines)`;
|
|
280
285
|
}
|
|
281
286
|
|
|
282
|
-
// src/ai/registry.ts
|
|
283
|
-
import chalk2 from "chalk";
|
|
284
|
-
|
|
285
|
-
// src/utils/logger.ts
|
|
286
|
-
import chalk from "chalk";
|
|
287
|
-
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
288
|
-
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
289
|
-
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
290
|
-
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
291
|
-
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
292
|
-
return LogLevel2;
|
|
293
|
-
})(LogLevel || {});
|
|
294
|
-
var LOG_LEVEL = (process.env.LOVECODE_LOG_LEVEL || "info").toUpperCase();
|
|
295
|
-
var currentLevel = LogLevel[LOG_LEVEL] ?? 1 /* INFO */;
|
|
296
|
-
var Logger = class {
|
|
297
|
-
static debug(...args) {
|
|
298
|
-
if (currentLevel <= 0 /* DEBUG */) {
|
|
299
|
-
console.error(chalk.dim("[debug]"), ...args);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
static info(...args) {
|
|
303
|
-
if (currentLevel <= 1 /* INFO */) {
|
|
304
|
-
console.error(chalk.blue("[info]"), ...args);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
static warn(...args) {
|
|
308
|
-
if (currentLevel <= 2 /* WARN */) {
|
|
309
|
-
console.error(chalk.yellow("[warn]"), ...args);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
static error(...args) {
|
|
313
|
-
if (currentLevel <= 3 /* ERROR */) {
|
|
314
|
-
console.error(chalk.red("[error]"), ...args);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
// src/ai/ollama.ts
|
|
320
|
-
var OllamaProvider = class {
|
|
321
|
-
name = "ollama";
|
|
322
|
-
async chat(messages, config) {
|
|
323
|
-
const baseUrl = config.baseUrl || "http://localhost:11434";
|
|
324
|
-
Logger.debug(`Ollama chat: ${config.model} (${messages.length} messages)`);
|
|
325
|
-
const response = await fetch(`${baseUrl}/api/chat`, {
|
|
326
|
-
method: "POST",
|
|
327
|
-
headers: { "Content-Type": "application/json" },
|
|
328
|
-
body: JSON.stringify({
|
|
329
|
-
model: config.model,
|
|
330
|
-
messages,
|
|
331
|
-
stream: false,
|
|
332
|
-
options: {
|
|
333
|
-
temperature: config.temperature ?? 0.2,
|
|
334
|
-
num_predict: config.maxTokens ?? 4096
|
|
335
|
-
}
|
|
336
|
-
})
|
|
337
|
-
});
|
|
338
|
-
if (!response.ok) {
|
|
339
|
-
throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
|
|
340
|
-
}
|
|
341
|
-
const data = await response.json();
|
|
342
|
-
return data.message?.content ?? "";
|
|
343
|
-
}
|
|
344
|
-
async *stream(messages, config) {
|
|
345
|
-
const baseUrl = config.baseUrl || "http://localhost:11434";
|
|
346
|
-
const response = await fetch(`${baseUrl}/api/chat`, {
|
|
347
|
-
method: "POST",
|
|
348
|
-
headers: { "Content-Type": "application/json" },
|
|
349
|
-
body: JSON.stringify({
|
|
350
|
-
model: config.model,
|
|
351
|
-
messages,
|
|
352
|
-
stream: true,
|
|
353
|
-
options: {
|
|
354
|
-
temperature: config.temperature ?? 0.2,
|
|
355
|
-
num_predict: config.maxTokens ?? 4096
|
|
356
|
-
}
|
|
357
|
-
})
|
|
358
|
-
});
|
|
359
|
-
if (!response.ok) {
|
|
360
|
-
throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
|
|
361
|
-
}
|
|
362
|
-
const reader = response.body?.getReader();
|
|
363
|
-
if (!reader) return;
|
|
364
|
-
const decoder = new TextDecoder();
|
|
365
|
-
let buffer = "";
|
|
366
|
-
while (true) {
|
|
367
|
-
const { done, value } = await reader.read();
|
|
368
|
-
if (done) break;
|
|
369
|
-
buffer += decoder.decode(value, { stream: true });
|
|
370
|
-
const lines = buffer.split("\n");
|
|
371
|
-
buffer = lines.pop() ?? "";
|
|
372
|
-
for (const line of lines) {
|
|
373
|
-
if (!line.trim()) continue;
|
|
374
|
-
try {
|
|
375
|
-
const parsed = JSON.parse(line);
|
|
376
|
-
if (parsed.done) return;
|
|
377
|
-
if (parsed.message?.content) {
|
|
378
|
-
yield parsed.message.content;
|
|
379
|
-
}
|
|
380
|
-
} catch {
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
async isAvailable(baseUrl) {
|
|
386
|
-
try {
|
|
387
|
-
const res = await fetch(`${baseUrl || "http://localhost:11434"}/api/tags`);
|
|
388
|
-
return res.ok;
|
|
389
|
-
} catch {
|
|
390
|
-
return false;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
// src/ai/openai-like.ts
|
|
396
|
-
var OpenAILikeProvider = class {
|
|
397
|
-
name;
|
|
398
|
-
baseUrl;
|
|
399
|
-
constructor(name, baseUrl) {
|
|
400
|
-
this.name = name;
|
|
401
|
-
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
402
|
-
}
|
|
403
|
-
async chat(messages, config) {
|
|
404
|
-
const url = `${this.baseUrl}/chat/completions`;
|
|
405
|
-
Logger.debug(`[${this.name}] chat: ${config.model} (${messages.length} messages)`);
|
|
406
|
-
const apiKey = this.getApiKey();
|
|
407
|
-
const response = await fetch(url, {
|
|
408
|
-
method: "POST",
|
|
409
|
-
headers: {
|
|
410
|
-
"Content-Type": "application/json",
|
|
411
|
-
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
|
|
412
|
-
},
|
|
413
|
-
body: JSON.stringify({
|
|
414
|
-
model: config.model,
|
|
415
|
-
messages,
|
|
416
|
-
temperature: config.temperature ?? 0.2,
|
|
417
|
-
max_tokens: config.maxTokens ?? 4096,
|
|
418
|
-
stream: false
|
|
419
|
-
})
|
|
420
|
-
});
|
|
421
|
-
if (!response.ok) {
|
|
422
|
-
const body = await response.text().catch(() => "");
|
|
423
|
-
throw new Error(`[${this.name}] API error ${response.status}: ${body.slice(0, 200)}`);
|
|
424
|
-
}
|
|
425
|
-
const data = await response.json();
|
|
426
|
-
return data.choices?.[0]?.message?.content ?? "";
|
|
427
|
-
}
|
|
428
|
-
async *stream(messages, config) {
|
|
429
|
-
const url = `${this.baseUrl}/chat/completions`;
|
|
430
|
-
const apiKey = this.getApiKey();
|
|
431
|
-
const response = await fetch(url, {
|
|
432
|
-
method: "POST",
|
|
433
|
-
headers: {
|
|
434
|
-
"Content-Type": "application/json",
|
|
435
|
-
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
|
|
436
|
-
},
|
|
437
|
-
body: JSON.stringify({
|
|
438
|
-
model: config.model,
|
|
439
|
-
messages,
|
|
440
|
-
temperature: config.temperature ?? 0.2,
|
|
441
|
-
max_tokens: config.maxTokens ?? 4096,
|
|
442
|
-
stream: true
|
|
443
|
-
})
|
|
444
|
-
});
|
|
445
|
-
if (!response.ok) {
|
|
446
|
-
const body = await response.text().catch(() => "");
|
|
447
|
-
throw new Error(`[${this.name}] API error ${response.status}: ${body.slice(0, 200)}`);
|
|
448
|
-
}
|
|
449
|
-
const reader = response.body?.getReader();
|
|
450
|
-
if (!reader) return;
|
|
451
|
-
const decoder = new TextDecoder();
|
|
452
|
-
let buffer = "";
|
|
453
|
-
while (true) {
|
|
454
|
-
const { done, value } = await reader.read();
|
|
455
|
-
if (done) break;
|
|
456
|
-
buffer += decoder.decode(value, { stream: true });
|
|
457
|
-
const lines = buffer.split("\n");
|
|
458
|
-
buffer = lines.pop() ?? "";
|
|
459
|
-
for (const line of lines) {
|
|
460
|
-
const trimmed = line.trim();
|
|
461
|
-
if (!trimmed || !trimmed.startsWith("data: ")) continue;
|
|
462
|
-
const jsonStr = trimmed.slice(6).trim();
|
|
463
|
-
if (jsonStr === "[DONE]") return;
|
|
464
|
-
try {
|
|
465
|
-
const parsed = JSON.parse(jsonStr);
|
|
466
|
-
const content = parsed.choices?.[0]?.delta?.content;
|
|
467
|
-
if (content) yield content;
|
|
468
|
-
} catch {
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
getApiKey() {
|
|
474
|
-
const envVar = this.name.toUpperCase();
|
|
475
|
-
return process.env[`${envVar}_API_KEY`] || process.env.OPENAI_API_KEY;
|
|
476
|
-
}
|
|
477
|
-
async isAvailable() {
|
|
478
|
-
try {
|
|
479
|
-
const res = await fetch(`${this.baseUrl}/models`, {
|
|
480
|
-
headers: this.getApiKey() ? { Authorization: `Bearer ${this.getApiKey()}` } : {}
|
|
481
|
-
});
|
|
482
|
-
return res.ok;
|
|
483
|
-
} catch {
|
|
484
|
-
return false;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
|
|
489
|
-
// src/ai/registry.ts
|
|
490
|
-
var registry = [
|
|
491
|
-
{
|
|
492
|
-
name: "ollama",
|
|
493
|
-
provider: new OllamaProvider(),
|
|
494
|
-
models: ["codellama", "deepseek-coder", "llama3.2", "llama3.1", "mistral", "mixtral", "phi3", "qwen2.5-coder"],
|
|
495
|
-
local: true,
|
|
496
|
-
priority: 10,
|
|
497
|
-
defaultModel: "codellama",
|
|
498
|
-
getConfig: (model) => ({
|
|
499
|
-
model,
|
|
500
|
-
baseUrl: "http://localhost:11434",
|
|
501
|
-
temperature: 0.2,
|
|
502
|
-
maxTokens: 8192
|
|
503
|
-
})
|
|
504
|
-
},
|
|
505
|
-
{
|
|
506
|
-
name: "groq",
|
|
507
|
-
provider: new OpenAILikeProvider("groq", "https://api.groq.com/openai/v1"),
|
|
508
|
-
models: ["llama3-70b-8192", "llama3-8b-8192", "mixtral-8x7b-32768", "gemma2-9b-it", "deepseek-r1-distill-llama-70b"],
|
|
509
|
-
local: false,
|
|
510
|
-
priority: 30,
|
|
511
|
-
defaultModel: "llama3-70b-8192",
|
|
512
|
-
getConfig: (model) => ({
|
|
513
|
-
model,
|
|
514
|
-
baseUrl: "https://api.groq.com/openai/v1",
|
|
515
|
-
temperature: 0.2,
|
|
516
|
-
maxTokens: 8192
|
|
517
|
-
})
|
|
518
|
-
},
|
|
519
|
-
{
|
|
520
|
-
name: "openrouter",
|
|
521
|
-
provider: new OpenAILikeProvider("openrouter", "https://openrouter.ai/api/v1"),
|
|
522
|
-
models: [
|
|
523
|
-
"google/gemini-2.0-flash-001",
|
|
524
|
-
"google/gemini-2.0-flash-lite-preview",
|
|
525
|
-
"mistralai/mistral-7b-instruct",
|
|
526
|
-
"meta-llama/llama-3.2-3b-instruct",
|
|
527
|
-
"deepseek/deepseek-chat",
|
|
528
|
-
"qwen/qwen-2.5-7b-instruct"
|
|
529
|
-
],
|
|
530
|
-
local: false,
|
|
531
|
-
priority: 40,
|
|
532
|
-
defaultModel: "google/gemini-2.0-flash-001",
|
|
533
|
-
getConfig: (model) => ({
|
|
534
|
-
model,
|
|
535
|
-
baseUrl: "https://openrouter.ai/api/v1",
|
|
536
|
-
temperature: 0.2,
|
|
537
|
-
maxTokens: 8192
|
|
538
|
-
})
|
|
539
|
-
},
|
|
540
|
-
{
|
|
541
|
-
name: "together",
|
|
542
|
-
provider: new OpenAILikeProvider("together", "https://api.together.xyz/v1"),
|
|
543
|
-
models: [
|
|
544
|
-
"mistralai/Mixtral-8x22B-Instruct-v0.1",
|
|
545
|
-
"mistralai/Mistral-7B-Instruct-v0.3",
|
|
546
|
-
"meta-llama/Llama-3.2-3B-Instruct-Turbo",
|
|
547
|
-
"meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo",
|
|
548
|
-
"deepseek-ai/deepseek-coder-33b-instruct",
|
|
549
|
-
"Qwen/Qwen2.5-7B-Instruct-Turbo"
|
|
550
|
-
],
|
|
551
|
-
local: false,
|
|
552
|
-
priority: 50,
|
|
553
|
-
defaultModel: "mistralai/Mixtral-8x22B-Instruct-v0.1",
|
|
554
|
-
getConfig: (model) => ({
|
|
555
|
-
model,
|
|
556
|
-
baseUrl: "https://api.together.xyz/v1",
|
|
557
|
-
temperature: 0.2,
|
|
558
|
-
maxTokens: 8192
|
|
559
|
-
})
|
|
560
|
-
},
|
|
561
|
-
{
|
|
562
|
-
name: "huggingface",
|
|
563
|
-
provider: new OpenAILikeProvider("huggingface", "https://api-inference.huggingface.co/v1"),
|
|
564
|
-
models: [
|
|
565
|
-
"HuggingFaceH4/zephyr-7b-beta",
|
|
566
|
-
"mistralai/Mistral-7B-Instruct-v0.3",
|
|
567
|
-
"meta-llama/Meta-Llama-3-8B-Instruct",
|
|
568
|
-
"google/gemma-2-9b-it"
|
|
569
|
-
],
|
|
570
|
-
local: false,
|
|
571
|
-
priority: 60,
|
|
572
|
-
defaultModel: "HuggingFaceH4/zephyr-7b-beta",
|
|
573
|
-
getConfig: (model) => ({
|
|
574
|
-
model,
|
|
575
|
-
baseUrl: "https://api-inference.huggingface.co/v1",
|
|
576
|
-
temperature: 0.2,
|
|
577
|
-
maxTokens: 4096
|
|
578
|
-
})
|
|
579
|
-
}
|
|
580
|
-
];
|
|
581
|
-
function getProvider(name) {
|
|
582
|
-
return registry.find((p) => p.name === name);
|
|
583
|
-
}
|
|
584
|
-
function getProviderForModel(model) {
|
|
585
|
-
return registry.find((p) => p.models.includes(model) || p.defaultModel === model);
|
|
586
|
-
}
|
|
587
|
-
function getLocalProviders() {
|
|
588
|
-
return registry.filter((p) => p.local);
|
|
589
|
-
}
|
|
590
|
-
function resolveModel(modelOrProvider) {
|
|
591
|
-
const byName = getProvider(modelOrProvider);
|
|
592
|
-
if (byName) {
|
|
593
|
-
return { entry: byName, model: byName.defaultModel };
|
|
594
|
-
}
|
|
595
|
-
const byModel = getProviderForModel(modelOrProvider);
|
|
596
|
-
if (byModel) {
|
|
597
|
-
return { entry: byModel, model: modelOrProvider };
|
|
598
|
-
}
|
|
599
|
-
const local = getLocalProviders()[0];
|
|
600
|
-
return { entry: local, model: modelOrProvider };
|
|
601
|
-
}
|
|
602
|
-
function printProviders() {
|
|
603
|
-
const lines = [chalk2.bold("\n Available Providers")];
|
|
604
|
-
for (const entry of registry) {
|
|
605
|
-
const tag = entry.local ? chalk2.green(" LOCAL ") : chalk2.blue(" CLOUD ");
|
|
606
|
-
const defaultModel = chalk2.dim(`(default: ${entry.defaultModel})`);
|
|
607
|
-
const models = entry.models.slice(0, 4).join(", ");
|
|
608
|
-
const more = entry.models.length > 4 ? chalk2.dim(` +${entry.models.length - 4} more`) : "";
|
|
609
|
-
lines.push(`
|
|
610
|
-
${tag} ${chalk2.cyan(entry.name.padEnd(12))} ${defaultModel}`);
|
|
611
|
-
lines.push(` ${chalk2.dim(models)}${more}`);
|
|
612
|
-
}
|
|
613
|
-
lines.push("");
|
|
614
|
-
return lines.join("\n");
|
|
615
|
-
}
|
|
616
|
-
function setDefaultModel(model) {
|
|
617
|
-
const resolved = resolveModel(model);
|
|
618
|
-
resolved.entry.defaultModel = resolved.model;
|
|
619
|
-
return { provider: resolved.entry.name, model: resolved.model };
|
|
620
|
-
}
|
|
621
|
-
function getDefaultModel() {
|
|
622
|
-
const highest = [...registry].sort((a, b) => {
|
|
623
|
-
if (a.local && !b.local) return -1;
|
|
624
|
-
if (!a.local && b.local) return 1;
|
|
625
|
-
return a.priority - b.priority;
|
|
626
|
-
})[0];
|
|
627
|
-
return { provider: highest.name, model: highest.defaultModel };
|
|
628
|
-
}
|
|
629
|
-
|
|
630
287
|
// src/git/message.ts
|
|
631
288
|
function getDefaultProvider() {
|
|
632
289
|
const defaultModel = getDefaultModel();
|
|
@@ -818,7 +475,6 @@ function formatResolutionSuggestions(suggestions) {
|
|
|
818
475
|
}
|
|
819
476
|
|
|
820
477
|
export {
|
|
821
|
-
OllamaProvider,
|
|
822
478
|
isGitAvailable,
|
|
823
479
|
getGitRoot,
|
|
824
480
|
isRepo,
|
|
@@ -846,9 +502,6 @@ export {
|
|
|
846
502
|
formatBranches,
|
|
847
503
|
formatLog,
|
|
848
504
|
abbreviateDiff,
|
|
849
|
-
getProvider,
|
|
850
|
-
printProviders,
|
|
851
|
-
setDefaultModel,
|
|
852
505
|
generateCommitMessage,
|
|
853
506
|
generatePRSummary,
|
|
854
507
|
detectConflicts,
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// src/config/config.ts
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as YAML from "js-yaml";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
var DEFAULT_CONFIG = {
|
|
7
|
+
model: "deepseek",
|
|
8
|
+
theme: "neon",
|
|
9
|
+
approval_mode: "smart",
|
|
10
|
+
performance: {
|
|
11
|
+
lazy_load: true,
|
|
12
|
+
cache_ttl: 3e5,
|
|
13
|
+
max_memory_mb: 200
|
|
14
|
+
},
|
|
15
|
+
termux: {
|
|
16
|
+
low_ram: false,
|
|
17
|
+
touch_optimized: false
|
|
18
|
+
},
|
|
19
|
+
telemetry: {
|
|
20
|
+
enabled: false,
|
|
21
|
+
crash_reports: false
|
|
22
|
+
},
|
|
23
|
+
security: {
|
|
24
|
+
profile: "standard",
|
|
25
|
+
scan_secrets: true
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var cachedConfig = null;
|
|
29
|
+
function configDir(rootDir) {
|
|
30
|
+
return path.resolve(rootDir || process.cwd(), ".lovecode");
|
|
31
|
+
}
|
|
32
|
+
function yamlPath(rootDir) {
|
|
33
|
+
return path.resolve(configDir(rootDir), "config.yaml");
|
|
34
|
+
}
|
|
35
|
+
function jsonPath(rootDir) {
|
|
36
|
+
return path.resolve(configDir(rootDir), "config.json");
|
|
37
|
+
}
|
|
38
|
+
function loadConfig(rootDir) {
|
|
39
|
+
if (cachedConfig) return cachedConfig;
|
|
40
|
+
const merged = { ...DEFAULT_CONFIG };
|
|
41
|
+
const yml = yamlPath(rootDir);
|
|
42
|
+
const json = jsonPath(rootDir);
|
|
43
|
+
let fileConfig = {};
|
|
44
|
+
if (fs.existsSync(yml)) {
|
|
45
|
+
try {
|
|
46
|
+
const raw = fs.readFileSync(yml, "utf-8");
|
|
47
|
+
const parsed = YAML.load(raw);
|
|
48
|
+
if (parsed) fileConfig = parsed;
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error(chalk.yellow(`Warning: Failed to parse ${yml}: ${err}`));
|
|
51
|
+
}
|
|
52
|
+
} else if (fs.existsSync(json)) {
|
|
53
|
+
try {
|
|
54
|
+
fileConfig = JSON.parse(fs.readFileSync(json, "utf-8"));
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
Object.assign(merged, fileConfig);
|
|
59
|
+
if (process.env.LOVECODE_MODEL) merged.model = process.env.LOVECODE_MODEL;
|
|
60
|
+
if (process.env.LOVECODE_THEME) merged.theme = process.env.LOVECODE_THEME;
|
|
61
|
+
if (process.env.LOVECODE_APPROVAL_MODE) merged.approval_mode = process.env.LOVECODE_APPROVAL_MODE;
|
|
62
|
+
cachedConfig = merged;
|
|
63
|
+
return merged;
|
|
64
|
+
}
|
|
65
|
+
function saveConfig(config, rootDir) {
|
|
66
|
+
const dir = configDir(rootDir);
|
|
67
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
68
|
+
const yml = {
|
|
69
|
+
model: config.model,
|
|
70
|
+
theme: config.theme,
|
|
71
|
+
approval_mode: config.approval_mode,
|
|
72
|
+
...config.provider ? { provider: config.provider } : {},
|
|
73
|
+
...config.model_params ? { model_params: config.model_params } : {},
|
|
74
|
+
...config.api ? { api: config.api } : {},
|
|
75
|
+
...config.performance ? { performance: config.performance } : {},
|
|
76
|
+
...config.termux ? { termux: config.termux } : {},
|
|
77
|
+
...config.telemetry ? { telemetry: config.telemetry } : {},
|
|
78
|
+
...config.security ? { security: config.security } : {}
|
|
79
|
+
};
|
|
80
|
+
fs.writeFileSync(yamlPath(rootDir), YAML.dump(yml, { indent: 2, lineWidth: 120 }), "utf-8");
|
|
81
|
+
cachedConfig = config;
|
|
82
|
+
}
|
|
83
|
+
function getDefaults() {
|
|
84
|
+
return { ...DEFAULT_CONFIG };
|
|
85
|
+
}
|
|
86
|
+
function resetConfig(rootDir) {
|
|
87
|
+
const yml = yamlPath(rootDir);
|
|
88
|
+
if (fs.existsSync(yml)) fs.unlinkSync(yml);
|
|
89
|
+
const json = jsonPath(rootDir);
|
|
90
|
+
if (fs.existsSync(json)) fs.unlinkSync(json);
|
|
91
|
+
cachedConfig = null;
|
|
92
|
+
}
|
|
93
|
+
function formatConfig(config) {
|
|
94
|
+
const lines = [chalk.bold("\n LoveCode Configuration")];
|
|
95
|
+
lines.push(`
|
|
96
|
+
${chalk.dim("Model:")} ${chalk.cyan(config.model)}`);
|
|
97
|
+
lines.push(` ${chalk.dim("Theme:")} ${chalk.magenta(config.theme)}`);
|
|
98
|
+
lines.push(` ${chalk.dim("Approval Mode:")} ${approvalColor(config.approval_mode)}`);
|
|
99
|
+
if (config.provider) lines.push(` ${chalk.dim("Provider:")} ${config.provider}`);
|
|
100
|
+
if (config.model_params) {
|
|
101
|
+
lines.push(`
|
|
102
|
+
${chalk.bold("Model Params:")}`);
|
|
103
|
+
if (config.model_params.temperature !== void 0) lines.push(` temperature: ${config.model_params.temperature}`);
|
|
104
|
+
if (config.model_params.top_p !== void 0) lines.push(` top_p: ${config.model_params.top_p}`);
|
|
105
|
+
if (config.model_params.max_tokens !== void 0) lines.push(` max_tokens: ${config.model_params.max_tokens}`);
|
|
106
|
+
}
|
|
107
|
+
if (config.performance) {
|
|
108
|
+
lines.push(`
|
|
109
|
+
${chalk.bold("Performance:")}`);
|
|
110
|
+
lines.push(` lazy_load: ${config.performance.lazy_load ? chalk.green("\u2713") : chalk.red("\u2717")}`);
|
|
111
|
+
lines.push(` cache_ttl: ${config.performance.cache_ttl}ms`);
|
|
112
|
+
lines.push(` max_memory: ${config.performance.max_memory_mb}MB`);
|
|
113
|
+
}
|
|
114
|
+
if (config.telemetry) {
|
|
115
|
+
lines.push(`
|
|
116
|
+
${chalk.bold("Telemetry:")}`);
|
|
117
|
+
lines.push(` enabled: ${config.telemetry.enabled ? chalk.yellow("ON") : chalk.green("OFF")}`);
|
|
118
|
+
lines.push(` crash_reports:${config.telemetry.crash_reports ? chalk.yellow("ON") : chalk.green("OFF")}`);
|
|
119
|
+
}
|
|
120
|
+
if (config.security) {
|
|
121
|
+
lines.push(`
|
|
122
|
+
${chalk.bold("Security:")}`);
|
|
123
|
+
lines.push(` profile: ${config.security.profile}`);
|
|
124
|
+
lines.push(` scan_secrets: ${config.security.scan_secrets ? chalk.green("\u2713") : chalk.red("\u2717")}`);
|
|
125
|
+
}
|
|
126
|
+
if (config.termux) {
|
|
127
|
+
lines.push(`
|
|
128
|
+
${chalk.bold("Termux:")}`);
|
|
129
|
+
lines.push(` low_ram: ${config.termux.low_ram ? chalk.yellow("\u2713") : chalk.red("\u2717")}`);
|
|
130
|
+
lines.push(` touch_opt: ${config.termux.touch_optimized ? chalk.green("\u2713") : chalk.red("\u2717")}`);
|
|
131
|
+
}
|
|
132
|
+
return lines.join("\n");
|
|
133
|
+
}
|
|
134
|
+
function approvalColor(mode) {
|
|
135
|
+
switch (mode) {
|
|
136
|
+
case "strict":
|
|
137
|
+
return chalk.red(mode);
|
|
138
|
+
case "smart":
|
|
139
|
+
return chalk.yellow(mode);
|
|
140
|
+
case "permissive":
|
|
141
|
+
return chalk.green(mode);
|
|
142
|
+
default:
|
|
143
|
+
return mode;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export {
|
|
148
|
+
configDir,
|
|
149
|
+
loadConfig,
|
|
150
|
+
saveConfig,
|
|
151
|
+
getDefaults,
|
|
152
|
+
resetConfig,
|
|
153
|
+
formatConfig
|
|
154
|
+
};
|