@iola_adm/iola-cli 0.1.66 → 0.1.68
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/src/cli.js +65 -11
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -4077,18 +4077,14 @@ async function listAiModels(provider) {
|
|
|
4077
4077
|
}
|
|
4078
4078
|
|
|
4079
4079
|
const payload = await response.json();
|
|
4080
|
-
|
|
4080
|
+
const models = (payload.models || []).map((model) => ({
|
|
4081
4081
|
id: model.name,
|
|
4082
4082
|
provider: "ollama",
|
|
4083
4083
|
note: model.modified_at ? `updated ${model.modified_at}` : "local",
|
|
4084
4084
|
}));
|
|
4085
|
+
return models.length > 0 ? models : getRecommendedOllamaModels("not installed");
|
|
4085
4086
|
} catch {
|
|
4086
|
-
return
|
|
4087
|
-
{ id: "llama3.2:1b", provider: "ollama", note: "recommended low RAM" },
|
|
4088
|
-
{ id: "llama3.2:3b", provider: "ollama", note: "recommended standard" },
|
|
4089
|
-
{ id: "qwen3:4b", provider: "ollama", note: "recommended balanced" },
|
|
4090
|
-
{ id: "qwen3:8b", provider: "ollama", note: "recommended good GPU" },
|
|
4091
|
-
];
|
|
4087
|
+
return getRecommendedOllamaModels("recommended");
|
|
4092
4088
|
}
|
|
4093
4089
|
}
|
|
4094
4090
|
|
|
@@ -4139,6 +4135,15 @@ async function listAiModels(provider) {
|
|
|
4139
4135
|
];
|
|
4140
4136
|
}
|
|
4141
4137
|
|
|
4138
|
+
function getRecommendedOllamaModels(notePrefix = "recommended") {
|
|
4139
|
+
return [
|
|
4140
|
+
{ id: "llama3.2:1b", provider: "ollama", note: `${notePrefix} low RAM` },
|
|
4141
|
+
{ id: "llama3.2:3b", provider: "ollama", note: `${notePrefix} standard` },
|
|
4142
|
+
{ id: "qwen3:4b", provider: "ollama", note: `${notePrefix} balanced` },
|
|
4143
|
+
{ id: "qwen3:8b", provider: "ollama", note: `${notePrefix} good GPU` },
|
|
4144
|
+
];
|
|
4145
|
+
}
|
|
4146
|
+
|
|
4142
4147
|
async function printAiProfiles() {
|
|
4143
4148
|
const config = await loadConfig();
|
|
4144
4149
|
const active = getActiveProfileName(config);
|
|
@@ -4455,6 +4460,10 @@ async function chooseAiModel(provider) {
|
|
|
4455
4460
|
async function switchModelTarget(target, model) {
|
|
4456
4461
|
const config = await loadConfig();
|
|
4457
4462
|
const provider = target === "local" ? "ollama" : target;
|
|
4463
|
+
if (provider === "ollama") {
|
|
4464
|
+
const ready = await ensureOllamaModelAvailable(model, config);
|
|
4465
|
+
if (!ready) return;
|
|
4466
|
+
}
|
|
4458
4467
|
const profileName = provider === "ollama" ? "local" : provider;
|
|
4459
4468
|
const currentProfile = config.ai.profiles?.[profileName] || buildProfileFromOptions(provider, { model });
|
|
4460
4469
|
const profile = {
|
|
@@ -4480,6 +4489,38 @@ async function switchModelTarget(target, model) {
|
|
|
4480
4489
|
console.log(`Активная модель: ${profileName} (${provider}, ${model})`);
|
|
4481
4490
|
}
|
|
4482
4491
|
|
|
4492
|
+
async function ensureOllamaModelAvailable(model, config = null) {
|
|
4493
|
+
if (await isOllamaModelInstalled(model, config)) return true;
|
|
4494
|
+
|
|
4495
|
+
const command = await resolveOllamaCommand();
|
|
4496
|
+
if (!command) {
|
|
4497
|
+
console.log("Ollama CLI не найден в PATH, хотя локальный API может отвечать.");
|
|
4498
|
+
console.log("Откройте новый PowerShell или запустите мастер: iola ai setup ollama");
|
|
4499
|
+
return false;
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
const shouldInstall = await confirm(`Локальная модель ${model} не скачана. Скачать через "ollama pull ${model}"? [Y/n] `);
|
|
4503
|
+
if (!shouldInstall) {
|
|
4504
|
+
console.log("Переключение на локальную модель отменено.");
|
|
4505
|
+
return false;
|
|
4506
|
+
}
|
|
4507
|
+
|
|
4508
|
+
await runCommand(command, ["pull", model], { inherit: true });
|
|
4509
|
+
return true;
|
|
4510
|
+
}
|
|
4511
|
+
|
|
4512
|
+
async function isOllamaModelInstalled(model, loadedConfig = null) {
|
|
4513
|
+
try {
|
|
4514
|
+
const config = loadedConfig || await loadConfig();
|
|
4515
|
+
const response = await fetch(`${config.ai.profiles?.local?.baseUrl || "http://127.0.0.1:11434"}/api/tags`);
|
|
4516
|
+
if (!response.ok) return false;
|
|
4517
|
+
const payload = await response.json();
|
|
4518
|
+
return (payload.models || []).some((entry) => entry.name === model);
|
|
4519
|
+
} catch {
|
|
4520
|
+
return false;
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
|
|
4483
4524
|
async function askText(question) {
|
|
4484
4525
|
if (!process.stdin.isTTY) return "";
|
|
4485
4526
|
const rl = readline.createInterface({ input, output });
|
|
@@ -8771,7 +8812,8 @@ async function saveConfig(value) {
|
|
|
8771
8812
|
}
|
|
8772
8813
|
|
|
8773
8814
|
async function writeConfig(value) {
|
|
8774
|
-
const
|
|
8815
|
+
const sanitized = sanitizeConfig(value);
|
|
8816
|
+
const errors = validateConfig(sanitized);
|
|
8775
8817
|
if (errors.length > 0) {
|
|
8776
8818
|
throw new Error(`Конфигурация не сохранена: ${errors.join("; ")}`);
|
|
8777
8819
|
}
|
|
@@ -8779,7 +8821,7 @@ async function writeConfig(value) {
|
|
|
8779
8821
|
if (existsSync(CONFIG_FILE)) {
|
|
8780
8822
|
await copyFile(CONFIG_FILE, LAST_GOOD_CONFIG_FILE).catch(() => {});
|
|
8781
8823
|
}
|
|
8782
|
-
await writeFile(CONFIG_FILE, `${JSON.stringify(
|
|
8824
|
+
await writeFile(CONFIG_FILE, `${JSON.stringify(sanitized, null, 2)}\n`, "utf8");
|
|
8783
8825
|
}
|
|
8784
8826
|
|
|
8785
8827
|
async function loadConfig() {
|
|
@@ -8788,7 +8830,7 @@ async function loadConfig() {
|
|
|
8788
8830
|
const value = await readConfigLayer(layer);
|
|
8789
8831
|
if (value) config = mergeConfig(config, value);
|
|
8790
8832
|
}
|
|
8791
|
-
return config;
|
|
8833
|
+
return sanitizeConfig(config);
|
|
8792
8834
|
}
|
|
8793
8835
|
|
|
8794
8836
|
async function loadConfigLayers() {
|
|
@@ -8805,7 +8847,7 @@ async function loadConfigLayers() {
|
|
|
8805
8847
|
continue;
|
|
8806
8848
|
}
|
|
8807
8849
|
const value = await readConfigLayer(layer.file);
|
|
8808
|
-
rows.push({ ...layer, exists: Boolean(value), value, errors: value ? validateConfig(mergeConfig(DEFAULT_AI_CONFIG, value)) : [] });
|
|
8850
|
+
rows.push({ ...layer, exists: Boolean(value), value, errors: value ? validateConfig(sanitizeConfig(mergeConfig(DEFAULT_AI_CONFIG, value))) : [] });
|
|
8809
8851
|
}
|
|
8810
8852
|
rows.push({ scope: "runtime", file: "process.env", exists: true, value: { IOLA_API_BASE_URL: process.env.IOLA_API_BASE_URL || "", IOLA_MCP_BASE_URL: process.env.IOLA_MCP_BASE_URL || "" }, errors: [] });
|
|
8811
8853
|
return rows;
|
|
@@ -8900,6 +8942,18 @@ function mergeConfig(base, override) {
|
|
|
8900
8942
|
};
|
|
8901
8943
|
}
|
|
8902
8944
|
|
|
8945
|
+
function sanitizeConfig(config) {
|
|
8946
|
+
const next = JSON.parse(JSON.stringify(config || {}));
|
|
8947
|
+
if (next.permissions?.localTools && typeof next.permissions.localTools === "object") {
|
|
8948
|
+
for (const tool of Object.keys(next.permissions.localTools)) {
|
|
8949
|
+
if (!ALL_TOOL_ALIASES.includes(tool)) {
|
|
8950
|
+
delete next.permissions.localTools[tool];
|
|
8951
|
+
}
|
|
8952
|
+
}
|
|
8953
|
+
}
|
|
8954
|
+
return next;
|
|
8955
|
+
}
|
|
8956
|
+
|
|
8903
8957
|
function validateConfig(config) {
|
|
8904
8958
|
const errors = [];
|
|
8905
8959
|
if (!config || typeof config !== "object") errors.push("config must be object");
|