totem-llm 0.3.2 → 0.3.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/README.md CHANGED
@@ -27,7 +27,7 @@ This downloads Totem LLM and installs all server and collector dependencies auto
27
27
  totem-llm start
28
28
  ```
29
29
 
30
- On first run this performs one-time setup (creates `~/totem-llm/`, migrates the database) then launches the server and document collector. Open [http://localhost:3001](http://localhost:3001) to access the UI.
30
+ On first run this performs one-time setup (creates `~/totem-llm/`, migrates the database) then launches the server and document collector. Open [http://localhost:8686](http://localhost:8686) to access the UI.
31
31
 
32
32
  ### 3. Install to Desktop (optional)
33
33
 
@@ -37,7 +37,7 @@ In the browser, click the install icon in the address bar (or use the browser me
37
37
 
38
38
  ```
39
39
  totem-llm --no-collector Start without the document collector
40
- totem-llm --port=8080 Override the server port (default: 3001)
40
+ totem-llm --port=8080 Override the server port (default: 8686)
41
41
  totem-llm setup Run first-time setup without starting
42
42
  totem-llm --help Show all options
43
43
  ```
@@ -102,7 +102,7 @@ All other settings (LLM provider, embedding model, etc.) can be changed later th
102
102
  yarn dev:all
103
103
  ```
104
104
 
105
- This starts the server (port `3001`), collector (port `8888`), and frontend dev server (port `5173`) concurrently. Open [http://localhost:5173](http://localhost:5173) to access the UI.
105
+ This starts the server (port `8686`), collector (port `8888`), and frontend dev server (port `5173`) concurrently. Open [http://localhost:5173](http://localhost:5173) to access the UI.
106
106
 
107
107
  > Alternatively, run each service in its own terminal: `yarn dev:server`, `yarn dev:collector`, `yarn dev:frontend`.
108
108
 
package/bin/totem-llm.js CHANGED
@@ -75,7 +75,7 @@ Commands:
75
75
 
76
76
  Options:
77
77
  --no-collector Start without the document collector (skips Puppeteer/ffmpeg)
78
- --port=<port> Override the server port (default: 3001)
78
+ --port=<port> Override the server port (default: 8686)
79
79
  --version, -v Print Totem LLM version and exit
80
80
  --force (setup only) Regenerate configuration even if it already exists
81
81
 
package/lib/launcher.js CHANGED
@@ -1,18 +1,21 @@
1
1
  import { spawn } from "child_process";
2
2
  import { join } from "path";
3
+ import { appendFileSync, mkdirSync, writeFileSync } from "fs";
3
4
  import { getEnv } from "./config.js";
4
5
  import { getPackageVersion } from "./version.js";
5
6
 
6
7
  /**
7
8
  * Spawn a Node.js service and prefix every log line with a fixed-width label.
9
+ * Also writes all logs to a session log file.
8
10
  *
9
11
  * @param {string} nodeExe Absolute path to the Node binary (process.execPath)
10
12
  * @param {string} cwd Working directory for the child process
11
13
  * @param {object} env Environment variables to pass to the child
12
14
  * @param {string} label Log prefix, e.g. "[server] "
15
+ * @param {string} logFile Path to the session log file
13
16
  * @returns {{ process: import('child_process').ChildProcess }}
14
17
  */
15
- function spawnService(nodeExe, cwd, env, label) {
18
+ function spawnService(nodeExe, cwd, env, label, logFile) {
16
19
  const child = spawn(nodeExe, ["index.js"], {
17
20
  cwd,
18
21
  env,
@@ -22,17 +25,19 @@ function spawnService(nodeExe, cwd, env, label) {
22
25
  const pad = label.padEnd(13);
23
26
  let buf = "";
24
27
 
25
- function flush(stream, chunk) {
28
+ function flush(chunk) {
26
29
  buf += chunk.toString();
27
30
  const lines = buf.split("\n");
28
31
  buf = lines.pop(); // keep any incomplete trailing line
29
32
  for (const line of lines) {
30
- if (line.trim()) stream.write(`${pad} ${line}\n`);
33
+ if (line.trim()) {
34
+ appendFileSync(logFile, `${pad} ${line}\n`);
35
+ }
31
36
  }
32
37
  }
33
38
 
34
- child.stdout.on("data", (c) => flush(process.stdout, c));
35
- child.stderr.on("data", (c) => flush(process.stderr, c));
39
+ child.stdout.on("data", (c) => flush(c));
40
+ child.stderr.on("data", (c) => flush(c));
36
41
 
37
42
  return child;
38
43
  }
@@ -52,27 +57,43 @@ export async function launch({ packageRoot, noCollector = false, port } = {}) {
52
57
  appVersion = getPackageVersion(packageRoot) ?? "unknown";
53
58
  } catch {}
54
59
 
55
- const serverPort = env.SERVER_PORT ?? "3001";
60
+ const serverPort = env.SERVER_PORT ?? "8686";
56
61
  const collectorPort = env.COLLECTOR_PORT ?? "8888";
57
62
  const nodeExe = process.execPath; // cross-platform: always the right Node binary
58
63
 
59
64
  const serverDir = join(packageRoot, "server");
60
65
  const collectorDir = join(packageRoot, "collector");
61
66
 
62
- console.log("Starting Totem LLM…");
63
- console.log(` Version → ${appVersion}`);
64
- console.log(` Server → http://localhost:${serverPort}`);
65
- if (!noCollector) {
66
- console.log(` Collector → http://localhost:${collectorPort}`);
67
- }
68
- console.log(` Storage → ${env.STORAGE_DIR}`);
69
- console.log("\nPress Ctrl+C to stop.\n");
70
-
71
- const serverProc = spawnService(nodeExe, serverDir, env, "[server]");
67
+ // Initialize session log file - clear on each startup
68
+ const logsDir = join(serverDir, "storage", "logs");
69
+ mkdirSync(logsDir, { recursive: true });
70
+ const sessionLogFile = join(logsDir, "totem-session.log");
71
+ writeFileSync(sessionLogFile, `${"=".repeat(80)}\nSession started: ${new Date().toISOString()}\n${"=".repeat(80)}\n`);
72
+
73
+ process.stdout.write(`
74
+ ████████╗ ██████╗ ████████╗███████╗███╗ ███╗ ██╗ ██╗ ███╗ ███╗
75
+ ╚══██╔══╝██╔═══██╗╚══██╔══╝██╔════╝████╗ ████║ ██║ ██║ ████╗ ████║
76
+ ██║ ██║ ██║ ██║ █████╗ ██╔████╔██║ ██║ ██║ ██╔████╔██║
77
+ ██║ ██║ ██║ ██║ ██╔══╝ ██║╚██╔╝██║ ██║ ██║ ██║╚██╔╝██║
78
+ ██║ ╚██████╔╝ ██║ ███████╗██║ ╚═╝ ██║ ███████╗███████╗██║ ╚═╝ ██║
79
+ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚══════╝╚══════╝╚═╝ ╚═╝
80
+
81
+ Your Private AI
82
+
83
+
84
+ Starting Totem LLM…\n
85
+ ============================================================================\n
86
+ Logs → ${sessionLogFile}\n
87
+ LAUNCH UI HERE (ctrl + click)--> http://localhost:${serverPort}/\n
88
+
89
+ Press Ctrl+C to stop.\n
90
+ `);
91
+
92
+ const serverProc = spawnService(nodeExe, serverDir, env, "[server]", sessionLogFile);
72
93
 
73
94
  let collectorProc = null;
74
95
  if (!noCollector) {
75
- collectorProc = spawnService(nodeExe, collectorDir, env, "[collector]");
96
+ collectorProc = spawnService(nodeExe, collectorDir, env, "[collector]", sessionLogFile);
76
97
  }
77
98
 
78
99
  // ── Graceful shutdown ────────────────────────────────────────────────────
@@ -82,7 +103,10 @@ export async function launch({ packageRoot, noCollector = false, port } = {}) {
82
103
  if (shuttingDown) return;
83
104
  shuttingDown = true;
84
105
 
85
- console.log(`\nShutting down (${signal})…`);
106
+ const shutdownMsg = `\nShutting down (${signal})…`;
107
+ process.stdout.write(`${shutdownMsg}\n`);
108
+ appendFileSync(sessionLogFile, `${shutdownMsg}\n`);
109
+
86
110
  serverProc.kill("SIGTERM");
87
111
  if (collectorProc) collectorProc.kill("SIGTERM");
88
112
 
@@ -102,7 +126,9 @@ export async function launch({ packageRoot, noCollector = false, port } = {}) {
102
126
  // Exit this process when the server exits unexpectedly
103
127
  serverProc.on("close", (code) => {
104
128
  if (!shuttingDown && code !== null && code !== 0) {
105
- console.error(`[server] exited unexpectedly with code ${code}`);
129
+ const errMsg = `[server] exited unexpectedly with code ${code}`;
130
+ process.stderr.write(`${errMsg}\n`);
131
+ appendFileSync(sessionLogFile, `${errMsg}\n`);
106
132
  shutdown("server-exit");
107
133
  setTimeout(() => process.exit(1), 500);
108
134
  }
@@ -111,9 +137,9 @@ export async function launch({ packageRoot, noCollector = false, port } = {}) {
111
137
  if (collectorProc) {
112
138
  collectorProc.on("close", (code) => {
113
139
  if (!shuttingDown && code !== null && code !== 0) {
114
- console.error(
115
- `[collector] exited with code ${code} — document ingestion unavailable`
116
- );
140
+ const errMsg = `[collector] exited with code ${code} — document ingestion unavailable`;
141
+ process.stderr.write(`${errMsg}\n`);
142
+ appendFileSync(sessionLogFile, `${errMsg}\n`);
117
143
  }
118
144
  });
119
145
  }
package/lib/setup.js CHANGED
@@ -28,7 +28,7 @@ function buildEnvFile(storageDir) {
28
28
  "# Edit this file to configure your instance, then restart.",
29
29
  "",
30
30
  `STORAGE_DIR="${storageDir}"`,
31
- "SERVER_PORT=3001",
31
+ "SERVER_PORT=8686",
32
32
  "COLLECTOR_PORT=8888",
33
33
  "",
34
34
  "# Security secrets (auto-generated — do not share these)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "totem-llm",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Totem LLM – Your Private AI. Run a self-hosted AI assistant locally on Linux, macOS, or Windows.",
5
5
  "main": "bin/totem-llm.js",
6
6
  "type": "module",
package/server/.env CHANGED
@@ -1,4 +1,4 @@
1
- SERVER_PORT=3001
1
+ SERVER_PORT=8686
2
2
  DATABASE_URL="file:../storage/anythingllm.db"
3
3
  JWT_SECRET="my-random-string-for-seeding" # Please generate random string at least 12 chars long.
4
4
  # JWT_EXPIRY="30d" # (optional) https://docs.anythingllm.com/configuration#custom-ttl-for-sessions
@@ -0,0 +1,22 @@
1
+ const { resolveFeatures } = require("../../totem.features.cjs");
2
+
3
+ function featureEndpoints(app) {
4
+ if (!app) return;
5
+
6
+ /**
7
+ * GET /api/features
8
+ * Returns the resolved feature flags for the current build profile.
9
+ * This is the runtime source of truth for the frontend.
10
+ */
11
+ app.get("/features", (_, response) => {
12
+ try {
13
+ const features = resolveFeatures();
14
+ return response.status(200).json({ features });
15
+ } catch (e) {
16
+ console.error("[features] Failed to resolve features:", e.message);
17
+ return response.status(500).json({ features: {}, error: e.message });
18
+ }
19
+ });
20
+ }
21
+
22
+ module.exports = { featureEndpoints };
package/server/index.js CHANGED
@@ -54,6 +54,7 @@ const {
54
54
  googleAgentSkillEndpoints,
55
55
  } = require("./endpoints/utils/googleAgentSkillEndpoints");
56
56
  const { memoryEndpoints } = require("./endpoints/memory");
57
+ const { featureEndpoints } = require("./endpoints/features");
57
58
  const { httpLogger } = require("./middleware/httpLogger");
58
59
  const app = express();
59
60
  const apiRouter = express.Router();
@@ -81,7 +82,7 @@ app.use(
81
82
  );
82
83
 
83
84
  if (!!process.env.ENABLE_HTTPS) {
84
- bootSSL(app, process.env.SERVER_PORT || 3001);
85
+ bootSSL(app, process.env.SERVER_PORT || 8686);
85
86
  } else {
86
87
  require("@mintplex-labs/express-ws").default(app); // load WebSockets in non-SSL mode.
87
88
  }
@@ -113,6 +114,7 @@ scheduledJobEndpoints(apiRouter);
113
114
  outlookAgentEndpoints(apiRouter);
114
115
  googleAgentSkillEndpoints(apiRouter);
115
116
  memoryEndpoints(apiRouter);
117
+ featureEndpoints(apiRouter);
116
118
  // Externally facing embedder endpoints
117
119
  embeddedEndpoints(apiRouter);
118
120
 
@@ -185,4 +187,4 @@ app.all("*", function (_, response) {
185
187
 
186
188
  // In non-https mode we need to boot at the end since the server has not yet
187
189
  // started and is `.listen`ing.
188
- if (!process.env.ENABLE_HTTPS) bootHTTP(app, process.env.SERVER_PORT || 3001);
190
+ if (!process.env.ENABLE_HTTPS) bootHTTP(app, process.env.SERVER_PORT || 8686);
@@ -100,7 +100,7 @@ const MobileDevice = {
100
100
  let baseUrl = "/api/mobile";
101
101
  if (process.env.NODE_ENV === "production") baseUrl = "/api/mobile";
102
102
  else
103
- baseUrl = `http://${ip.address()}:${process.env.SERVER_PORT || 3001}/api/mobile`;
103
+ baseUrl = `http://${ip.address()}:${process.env.SERVER_PORT || 8686}/api/mobile`;
104
104
 
105
105
  const tempToken = this.registerTempToken(user);
106
106
  baseUrl = `${baseUrl}?t=${tempToken}`;
@@ -36,7 +36,7 @@ function mergeStringField(target, source, fieldName, validator = null) {
36
36
  const SystemSettings = {
37
37
  /** A default system prompt that is used when no other system prompt is set or available to the function caller. */
38
38
  saneDefaultSystemPrompt:
39
- "You are Totem LLM. You are an AI that prioritizes consistent behavior with your history.",
39
+ "You are Totem LLM. You are an AI that prioritizes consistent behavior with your history. The current date and time is {datetime}.",
40
40
  protectedFields: ["multi_user_mode", "hub_api_key", "onboarding_complete"],
41
41
  publicFields: [
42
42
  "footer_data",