kentutai-app 1.0.6 → 1.0.8

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.
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Postinstall: warm-up SQLite deps into ~/.kentutai/runtime so the first
4
+ // `kentutai` start doesn't need network. Failure here is non-fatal —
5
+ // cli.js will retry at runtime if anything is missing.
6
+ const { ensureSqliteRuntime } = require("./sqliteRuntime");
7
+ const { ensureTrayRuntime } = require("./trayRuntime");
8
+
9
+ try {
10
+ ensureSqliteRuntime({ silent: false });
11
+ console.log("[kentutai] runtime SQLite deps ready");
12
+ } catch (e) {
13
+ console.warn(`[kentutai] runtime warm-up skipped: ${e.message}`);
14
+ }
15
+
16
+ try {
17
+ ensureTrayRuntime({ silent: false });
18
+ } catch (e) {
19
+ console.warn(`[kentutai] tray runtime skipped: ${e.message}`);
20
+ }
21
+
22
+ process.exit(0);
@@ -0,0 +1,139 @@
1
+ // Ensure better-sqlite3 is installed in USER_DATA_DIR/runtime/node_modules
2
+ // (user-writable, avoids Windows EBUSY locks during npm i -g updates).
3
+ // sql.js is bundled in bin/app already; node:sqlite / bun:sqlite are built-in.
4
+ const { execSync, spawnSync } = require("child_process");
5
+ const fs = require("fs");
6
+ const os = require("os");
7
+ const path = require("path");
8
+
9
+ const BETTER_SQLITE3_VERSION = "12.6.2";
10
+
11
+ function getDataDir() {
12
+ if (process.env.DATA_DIR) return process.env.DATA_DIR;
13
+ return process.platform === "win32"
14
+ ? path.join(process.env.APPDATA || os.homedir(), "kentutai")
15
+ : path.join(os.homedir(), ".kentutai");
16
+ }
17
+
18
+ function getRuntimeDir() {
19
+ return path.join(getDataDir(), "runtime");
20
+ }
21
+
22
+ function getRuntimeNodeModules() {
23
+ return path.join(getRuntimeDir(), "node_modules");
24
+ }
25
+
26
+ function ensureRuntimeDir() {
27
+ const dir = getRuntimeDir();
28
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
29
+
30
+ // Minimal package.json so npm treats it as a project root
31
+ const pkgPath = path.join(dir, "package.json");
32
+ if (!fs.existsSync(pkgPath)) {
33
+ fs.writeFileSync(pkgPath, JSON.stringify({
34
+ name: "kentutai-runtime",
35
+ version: "1.0.0",
36
+ private: true,
37
+ description: "User-writable runtime deps for kentutai (better-sqlite3 native binary)",
38
+ }, null, 2));
39
+ }
40
+ return dir;
41
+ }
42
+
43
+ function hasModule(name) {
44
+ return fs.existsSync(path.join(getRuntimeNodeModules(), name, "package.json"));
45
+ }
46
+
47
+ function isBetterSqliteBinaryValid() {
48
+ const binary = path.join(getRuntimeNodeModules(), "better-sqlite3", "build", "Release", "better_sqlite3.node");
49
+ if (!fs.existsSync(binary)) return false;
50
+ try {
51
+ const fd = fs.openSync(binary, "r");
52
+ const buf = Buffer.alloc(4);
53
+ fs.readSync(fd, buf, 0, 4, 0);
54
+ fs.closeSync(fd);
55
+ const magic = buf.toString("hex");
56
+ if (process.platform === "linux") return magic.startsWith("7f454c46");
57
+ if (process.platform === "darwin") return magic.startsWith("cffaedfe") || magic.startsWith("cefaedfe");
58
+ if (process.platform === "win32") return magic.startsWith("4d5a");
59
+ return true;
60
+ } catch { return false; }
61
+ }
62
+
63
+ // Extract a short, user-friendly reason from npm stderr.
64
+ function summarizeNpmError(stderr = "") {
65
+ const text = String(stderr);
66
+ if (/ENOTFOUND|ETIMEDOUT|EAI_AGAIN|network|getaddrinfo/i.test(text)) return "No internet connection or registry unreachable";
67
+ if (/EACCES|EPERM|permission denied/i.test(text)) return "Permission denied (check folder permissions)";
68
+ if (/ENOSPC|no space/i.test(text)) return "Not enough disk space";
69
+ if (/node-gyp|gyp ERR|python|MSBuild|Visual Studio|Xcode/i.test(text)) return "Missing build tools (Xcode CLT / Python / VS Build Tools)";
70
+ if (/ETARGET|version.*not found/i.test(text)) return "Package version not found on registry";
71
+ const m = text.match(/npm ERR! (.+)/);
72
+ if (m) return m[1].slice(0, 200);
73
+ const lastLine = text.trim().split(/\r?\n/).filter(Boolean).pop();
74
+ return lastLine ? lastLine.slice(0, 200) : "Unknown error";
75
+ }
76
+
77
+ function runNpmInstall({ cwd, pkgs, extraArgs = [], timeout = 180000 }) {
78
+ const args = ["install", ...pkgs, "--no-audit", "--no-fund", "--prefer-online", ...extraArgs];
79
+ const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
80
+ const res = spawnSync(npmCmd, args, {
81
+ cwd,
82
+ stdio: ["ignore", "pipe", "pipe"],
83
+ timeout,
84
+ shell: process.platform === "win32",
85
+ encoding: "utf8",
86
+ });
87
+ return { ok: res.status === 0, code: res.status, stderr: res.stderr || "", stdout: res.stdout || "" };
88
+ }
89
+
90
+ function npmInstall(pkgs, opts = {}) {
91
+ const cwd = ensureRuntimeDir();
92
+ const extra = opts.optional ? ["--no-save"] : [];
93
+ if (!opts.silent) console.log("⏳ Installing SQLite engine (first run)...");
94
+ const res = runNpmInstall({ cwd, pkgs, extraArgs: extra, timeout: opts.timeout || 180000 });
95
+ if (!res.ok && !opts.silent) {
96
+ const reason = summarizeNpmError(res.stderr);
97
+ console.warn("⚠️ SQLite engine install failed — using fallback");
98
+ console.warn(` Reason: ${reason}`);
99
+ console.warn(` Retry: cd "${cwd}" && npm install ${pkgs.join(" ")}`);
100
+ }
101
+ return res.ok;
102
+ }
103
+
104
+ // Public: ensure better-sqlite3 native module is installed in user-writable
105
+ // runtime dir. sql.js is bundled in bin/app already; node:sqlite is built-in.
106
+ // This is purely a *speed optimization* — app works without it via fallbacks.
107
+ function ensureSqliteRuntime({ silent = false } = {}) {
108
+ ensureRuntimeDir();
109
+
110
+ const needBetterSqlite = !hasModule("better-sqlite3") || !isBetterSqliteBinaryValid();
111
+ if (!needBetterSqlite) {
112
+ if (!silent) console.log("✅ SQLite engine ready");
113
+ return { betterSqlite: true };
114
+ }
115
+
116
+ const ok = npmInstall([`better-sqlite3@${BETTER_SQLITE3_VERSION}`], { optional: true, silent });
117
+ return {
118
+ betterSqlite: ok && hasModule("better-sqlite3") && isBetterSqliteBinaryValid(),
119
+ };
120
+ }
121
+
122
+ // Inject runtime + bundled node_modules into NODE_PATH so child Node processes
123
+ // resolve sql.js (bundled in bin/app/node_modules) and better-sqlite3 (runtime).
124
+ function buildEnvWithRuntime(baseEnv = process.env) {
125
+ const runtimeNm = getRuntimeNodeModules();
126
+ const bundledNm = path.join(__dirname, "..", "app", "node_modules");
127
+ const existing = baseEnv.NODE_PATH || "";
128
+ const NODE_PATH = [runtimeNm, bundledNm, existing].filter(Boolean).join(path.delimiter);
129
+ return { ...baseEnv, NODE_PATH };
130
+ }
131
+
132
+ module.exports = {
133
+ ensureSqliteRuntime,
134
+ buildEnvWithRuntime,
135
+ getRuntimeDir,
136
+ getRuntimeNodeModules,
137
+ runNpmInstall,
138
+ summarizeNpmError,
139
+ };
@@ -0,0 +1,107 @@
1
+ // Lazy install systray2 for macOS/Linux into USER_DATA_DIR/runtime/node_modules.
2
+ // Windows uses PowerShell NotifyIcon (no binary) → no systray needed.
3
+ // This keeps the published npm tarball free of unsigned Go binaries that
4
+ // trigger antivirus false positives (e.g. Kaspersky flagging tray_windows.exe).
5
+ //
6
+ // We use the maintained `systray2` fork. The original `systray@1.0.5` package
7
+ // bundles a 2017 x86_64 Go binary whose Mach-O headers are rejected by modern
8
+ // dyld (macOS 14+), so the tray silently fails to register on Apple Silicon.
9
+ const { spawnSync } = require("child_process");
10
+ const fs = require("fs");
11
+ const path = require("path");
12
+ const { getRuntimeDir, getRuntimeNodeModules, runNpmInstall, summarizeNpmError } = require("./sqliteRuntime");
13
+
14
+ const SYSTRAY_PKG = "systray2";
15
+ const SYSTRAY_VERSION = "2.1.4";
16
+ const LEGACY_SYSTRAY_PKG = "systray";
17
+
18
+ function hasSystray() {
19
+ return fs.existsSync(path.join(getRuntimeNodeModules(), SYSTRAY_PKG, "package.json"));
20
+ }
21
+
22
+ // Remove the legacy `systray` package from all known locations.
23
+ // On Windows it was an AV false-positive risk; on macOS/Linux its bundled
24
+ // binary is broken on modern OS versions.
25
+ function cleanupLegacySystray({ silent = false } = {}) {
26
+ // 1) Runtime dir: ~/.kentutai/runtime/node_modules/systray (or %APPDATA% on Win)
27
+ // 2) npm global nested: <npm_prefix>/node_modules/kentutai/node_modules/systray
28
+ // __dirname here = <pkg root>/hooks → up 1 = pkg root
29
+ const targets = [
30
+ path.join(getRuntimeNodeModules(), LEGACY_SYSTRAY_PKG),
31
+ path.join(__dirname, "..", "node_modules", LEGACY_SYSTRAY_PKG)
32
+ ];
33
+ for (const dir of targets) {
34
+ if (fs.existsSync(dir)) {
35
+ try {
36
+ fs.rmSync(dir, { recursive: true, force: true });
37
+ if (!silent) console.log(`[kentutai][runtime] removed legacy systray: ${dir}`);
38
+ } catch (e) {
39
+ if (!silent) console.warn(`[kentutai][runtime] failed to remove ${dir}: ${e.message}`);
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ // systray2's npm tarball sometimes ships the bundled Go binary without the
46
+ // executable bit set on macOS, causing spawn() to fail with EACCES. Set +x
47
+ // best-effort so the tray actually starts.
48
+ function chmodSystrayBin({ silent = false } = {}) {
49
+ if (process.platform === "win32") return;
50
+ const binName = process.platform === "darwin" ? "tray_darwin_release" : "tray_linux_release";
51
+ const binPath = path.join(getRuntimeNodeModules(), SYSTRAY_PKG, "traybin", binName);
52
+ if (!fs.existsSync(binPath)) return;
53
+ try {
54
+ fs.chmodSync(binPath, 0o755);
55
+ } catch (e) {
56
+ if (!silent) console.warn(`[kentutai][runtime] chmod tray bin failed: ${e.message}`);
57
+ }
58
+ }
59
+
60
+ function ensureRuntimeDir() {
61
+ const dir = getRuntimeDir();
62
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
63
+ const pkgPath = path.join(dir, "package.json");
64
+ if (!fs.existsSync(pkgPath)) {
65
+ fs.writeFileSync(pkgPath, JSON.stringify({
66
+ name: "kentutai-runtime",
67
+ version: "1.0.0",
68
+ private: true
69
+ }, null, 2));
70
+ }
71
+ return dir;
72
+ }
73
+
74
+ function npmInstall(pkgs, { silent = false } = {}) {
75
+ const cwd = ensureRuntimeDir();
76
+ if (!silent) console.log("⏳ Installing system tray (first run)...");
77
+ const res = runNpmInstall({ cwd, pkgs, extraArgs: ["--no-save"], timeout: 120000 });
78
+ if (!res.ok && !silent) {
79
+ const reason = summarizeNpmError(res.stderr);
80
+ console.warn("⚠️ System tray install failed — tray disabled");
81
+ console.warn(` Reason: ${reason}`);
82
+ console.warn(` Retry: cd "${cwd}" && npm install ${pkgs.join(" ")}`);
83
+ }
84
+ return res.ok;
85
+ }
86
+
87
+ // Public: ensure systray2 is installed on macOS/Linux only.
88
+ // Windows skips entirely (uses PowerShell tray).
89
+ function ensureTrayRuntime({ silent = false } = {}) {
90
+ // Always evict the legacy `systray` package — its binary is broken on
91
+ // modern macOS and an AV false-positive on Windows.
92
+ cleanupLegacySystray({ silent });
93
+
94
+ if (process.platform === "win32") {
95
+ return { systray: false, skipped: true };
96
+ }
97
+ if (hasSystray()) {
98
+ chmodSystrayBin({ silent });
99
+ if (!silent) console.log("✅ System tray ready");
100
+ return { systray: true };
101
+ }
102
+ const ok = npmInstall([`${SYSTRAY_PKG}@${SYSTRAY_VERSION}`], { silent });
103
+ if (ok) chmodSystrayBin({ silent });
104
+ return { systray: ok && hasSystray() };
105
+ }
106
+
107
+ module.exports = { ensureTrayRuntime };
package/cli/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kentutai",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "KentutAI - AI Router CLI",
5
5
  "private": false,
6
6
  "bin": {
@@ -8,8 +8,9 @@
8
8
  },
9
9
  "files": [
10
10
  "cli.js",
11
- "src",
12
- "app",
11
+ "src/",
12
+ "hooks/",
13
+ "app/",
13
14
  "README.md",
14
15
  "LICENSE"
15
16
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kentutai-app",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "KentutAI web dashboard",
5
5
  "private": false,
6
6
  "bin": {
package/cli/app/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2024-2026 decolua and contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.