@krmxd/onegpt 0.1.8-fix-beta → 0.1.9-beta

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/bin/ogpt.js CHANGED
@@ -133,6 +133,23 @@ Options:
133
133
 
134
134
  if (noStream) cfg.set("ui.stream", false);
135
135
 
136
+ // Local-engine bootstrap: install/start the engine and make sure a model
137
+ // exists BEFORE chatting, so users never see raw ECONNREFUSED errors.
138
+ if ((cfg.get("active_provider", "ollama") || "ollama") === "ollama"
139
+ && args[0] !== "--help") {
140
+ const { ensureLocalEngine } = require("../src/ollama-setup");
141
+ const res = await ensureLocalEngine({
142
+ host: cfg.get("providers.ollama.host"),
143
+ model: cfg.activeModel ? cfg.activeModel() : cfg.get("active_model"),
144
+ });
145
+ if (res.ok && res.model && res.model !== cfg.get("active_model")) {
146
+ cfg.set("active_model", res.model);
147
+ }
148
+ if (!res.ok && res.reason) {
149
+ console.log("\n" + res.reason + "\n");
150
+ }
151
+ }
152
+
136
153
  const cli = new CLI({ config: cfg });
137
154
 
138
155
  if (autoApprove) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krmxd/onegpt",
3
- "version": "0.1.8-fix-beta",
3
+ "version": "0.1.9-beta",
4
4
  "description": "OGPT - AI coding assistant for the terminal with a built-in local engine",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+
3
+ // Local-engine bootstrap (parity with ogpt/utils/ollama_setup.py).
4
+ // Makes `ogpt` work right after install: installs the engine if missing,
5
+ // starts the server daemon, picks a model that fits the RAM, and pulls it -
6
+ // all installer output hidden behind short status lines.
7
+
8
+ const { spawn, spawnSync } = require("child_process");
9
+ const os = require("os");
10
+
11
+ function isInstalled() {
12
+ try {
13
+ return spawnSync("ollama", ["--version"],
14
+ { stdio: "ignore", timeout: 15000 }).status === 0;
15
+ } catch { return false; }
16
+ }
17
+
18
+ function _getJson(host, path, timeoutMs) {
19
+ return new Promise((resolve) => {
20
+ const uri = new URL(path, host);
21
+ const mod = uri.protocol === "https:" ? require("https") : require("http");
22
+ const req = mod.request(uri, { timeout: timeoutMs }, (res) => {
23
+ let data = "";
24
+ res.on("data", (c) => { data += c; });
25
+ res.on("end", () => {
26
+ try { resolve({ ok: res.statusCode === 200, json: JSON.parse(data) }); }
27
+ catch { resolve({ ok: false }); }
28
+ });
29
+ });
30
+ req.on("timeout", () => { req.destroy(); resolve({ ok: false }); });
31
+ req.on("error", () => resolve({ ok: false }));
32
+ req.end();
33
+ });
34
+ }
35
+
36
+ async function serverUp(host, timeoutMs = 2500) {
37
+ const r = await _getJson(host, "/api/tags", timeoutMs);
38
+ return r.ok;
39
+ }
40
+
41
+ async function startServer(host, waitSec = 20) {
42
+ try {
43
+ const child = spawn("ollama", ["serve"],
44
+ { stdio: "ignore", detached: true });
45
+ child.unref();
46
+ } catch { return false; }
47
+ const deadline = Date.now() + waitSec * 1000;
48
+ while (Date.now() < deadline) {
49
+ await sleep(500);
50
+ if (await serverUp(host)) return true;
51
+ }
52
+ return false;
53
+ }
54
+
55
+ function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
56
+
57
+ async function listModels(host) {
58
+ const r = await _getJson(host, "/api/tags", 4000);
59
+ if (!r.ok || !r.json || !Array.isArray(r.json.models)) return [];
60
+ return r.json.models.map((m) => m.name || "").filter(Boolean);
61
+ }
62
+
63
+ async function pullModel(host, model, timeoutSec = 1800) {
64
+ console.log("Installing packages...");
65
+ try {
66
+ spawnSync("ollama", ["pull", model],
67
+ { stdio: "ignore", timeout: timeoutSec * 1000 });
68
+ } catch {}
69
+ const installed = await listModels(host);
70
+ const base = model.split(":")[0];
71
+ return installed.includes(model)
72
+ || installed.some((m) => m.startsWith(base + ":"));
73
+ }
74
+
75
+ function ramGB() {
76
+ return Math.round(os.totalmem() / (1024 * 1024 * 1024));
77
+ }
78
+
79
+ async function pickModel(host, configuredModel) {
80
+ const installed = await listModels(host);
81
+ if (installed.includes(configuredModel)) return configuredModel;
82
+ let fit = null;
83
+ try {
84
+ const { recommendForRam } = require("./catalog");
85
+ fit = recommendForRam(ramGB());
86
+ } catch {}
87
+ if (fit && installed.includes(fit)) return fit;
88
+ if (fit && !installed.length) return fit; // fresh machine: pull best fit
89
+ if (installed.length) return installed[0]; // something already there
90
+ return fit || configuredModel;
91
+ }
92
+
93
+ /**
94
+ * Ensure the local engine is installed, running and has a usable model.
95
+ * Returns {ok, model, reason} - never throws.
96
+ */
97
+ async function ensureLocalEngine({ host, model } = {}) {
98
+ host = host || "http://127.0.0.1:11434";
99
+ model = model || "qwen2.5-coder:1.5b";
100
+ try {
101
+ if (!isInstalled()) {
102
+ console.log("Installing packages...");
103
+ const { installOllama } = require("./bootstrap");
104
+ if (!installOllama()) {
105
+ return { ok: false, reason:
106
+ "Local engine could not be installed automatically. "
107
+ + "Grab it from https://ollama.com/download, or pick another "
108
+ + "provider with /provider." };
109
+ }
110
+ }
111
+ let up = await serverUp(host);
112
+ if (!up) {
113
+ console.log("Starting local engine...");
114
+ up = await startServer(host);
115
+ if (!up) {
116
+ return { ok: false, reason:
117
+ `Local engine did not respond on ${host} within 20s. `
118
+ + "Try 'ollama serve' manually or check 'journalctl -u ollama'." };
119
+ }
120
+ }
121
+ const chosen = await pickModel(host, model);
122
+ const installed = await listModels(host);
123
+ const base = chosen.split(":")[0];
124
+ const have = installed.includes(chosen)
125
+ || installed.some((m) => m.startsWith(base + ":"));
126
+ if (!have) {
127
+ const okPull = await pullModel(host, chosen);
128
+ if (!okPull) {
129
+ return { ok: false, reason:
130
+ `Model ${chosen} could not be downloaded (offline?). `
131
+ + "Check your connection or /model to pick an installed one." };
132
+ }
133
+ }
134
+ return { ok: true, model: chosen };
135
+ } catch (e) {
136
+ return { ok: false, reason: e.message };
137
+ }
138
+ }
139
+
140
+ module.exports = { ensureLocalEngine, serverUp, startServer, listModels,
141
+ pullModel, pickModel, isInstalled };
package/src/ollama.js CHANGED
@@ -348,7 +348,16 @@ class OllamaProvider {
348
348
  });
349
349
  }
350
350
  );
351
- req.on("error", reject);
351
+ req.on("error", (e) => {
352
+ if (e && (e.code === "ECONNREFUSED" || /ECONNREFUSED/.test(e.message || ""))) {
353
+ reject(new Error(
354
+ `Local engine is not running at ${this.host}. `
355
+ + "Start it with 'ollama serve' (OGPT normally does this "
356
+ + "automatically at launch), or switch providers with /provider."));
357
+ } else {
358
+ reject(e);
359
+ }
360
+ });
352
361
  req.on("timeout", () => { req.destroy(); reject(new Error("Timeout")); });
353
362
  if (body) req.write(JSON.stringify(body));
354
363
  req.end();
@@ -394,7 +403,16 @@ class OllamaProvider {
394
403
  req.destroy(new Error(`No data from the local engine for ${Math.round(this.idleTimeout / 1000)}s`));
395
404
  }, this.idleTimeout);
396
405
  };
397
- req.on("error", (e) => { clearIdle(); reject(e); });
406
+ req.on("error", (e) => {
407
+ clearIdle();
408
+ if (e && e.code === "ECONNREFUSED") {
409
+ reject(new Error(
410
+ `Local engine is not running at ${this.host}. `
411
+ + "Start it with 'ollama serve' or switch providers with /provider."));
412
+ } else {
413
+ reject(e);
414
+ }
415
+ });
398
416
  req.on("close", clearIdle);
399
417
  req.write(JSON.stringify(body));
400
418
  req.end();