totem-llm 0.3.6 → 0.5.0

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
@@ -1,113 +1,124 @@
1
- # Totem LLM - Your Private AI
1
+ <div align="center">
2
2
 
3
- Totem LLM is a sovereign AI solution, aimed at optimizing for consumer hardware and ensuring data privacy. It is designed to run efficiently on consumer-grade hardware, such as laptops and desktops, without the need for cloud-based processing. This allows users to have full control over their data and AI interactions, without relying on third-party services.
3
+ # Totem LLM
4
4
 
5
- While local AI is the core focus, Totem LLM supports both local and cloud-based LLM providers, giving users the flexibility to choose based on their needs and preferences. The system is built with a modular architecture, allowing for easy integration of new features and providers over time.
5
+ <p align="center">
6
+ <img src="images/totem_logo.png" alt="Description of image" width="100">
7
+ </p>
6
8
 
7
- ## Install Quick Start Package
8
9
 
9
- ### Prerequisites
10
+ ### Your Mind Leads
10
11
 
11
- - [Node.js](https://nodejs.org/) v18 or later
12
- - [Ollama](https://ollama.com/) (recommended) or an [OpenRouter](https://openrouter.ai/) API key
12
+ [![Node.js](https://img.shields.io/badge/Node.js-v18+-green?logo=node.js&logoColor=white)](https://nodejs.org/)
13
+ [![License](https://img.shields.io/badge/License-MIT-blue)](LICENSE)
14
+ [![Platform](https://img.shields.io/badge/Platform-Linux%20%7C%20macOS%20%7C%20Windows-lightgrey)](https://github.com/fred-terzi/totem-llm)
13
15
 
14
- ### 1. Install the package
15
16
 
16
- Search for `terminal` in your applications and open it. Then run:
17
+ **A sovereign AI solution for consumer hardware. Keep your data private, run locally, stay in control.**
17
18
 
18
- ```bash
19
- npm install -g totem-llm
20
- ```
19
+ </div>
21
20
 
22
- This downloads Totem LLM and installs all server and collector dependencies automatically.
21
+ ---
23
22
 
24
- ### 2. Start Totem LLM
23
+ ## 🚀 Key Features
25
24
 
26
- ```bash
27
- totem-llm start
28
- ```
25
+ | Feature | Description |
26
+ |---------|-------------|
27
+ | **Privacy First** | All data stays on your machine. |
28
+ | **Consumer Hardware** | Optimized for consumer-grade devices. |
29
+ | **Local & Cloud** | Choose local models via Ollama or use cloud providers (OpenRouter). |
30
+ | **Easy Setup** | One-line install with automatic dependency resolution. |
29
31
 
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.
32
+ ---
31
33
 
32
- ### 3. Install to Desktop (optional)
34
+ ## 📋 About
33
35
 
34
- In the browser, click the install icon in the address bar (or use the browser menu) to install Totem LLM as a desktop application. This creates a standalone app with its own window and taskbar/dock icon.
36
+ Totem LLM is a sovereign AI solution, aimed at optimizing for consumer hardware and ensuring data privacy. It is designed to run efficiently on consumer-grade hardware, such as laptops and desktops, without the need for cloud-based processing. This allows users to have full control over their data and AI interactions, without relying on third-party services.
35
37
 
36
- ### Options
38
+ While local AI is the core focus, Totem LLM supports both local and cloud-based LLM providers, giving users the flexibility to choose based on their needs and preferences. The system is built with a modular architecture, allowing for easy integration of new features and providers over time.
37
39
 
38
- ```
39
- totem-llm --no-collector Start without the document collector
40
- totem-llm --port=8080 Override the server port (default: 8686)
41
- totem-llm setup Run first-time setup without starting
42
- totem-llm --help Show all options
43
- ```
40
+ ## ⚙️ Requirements
44
41
 
45
- > **Storage**: All data is kept in `~/totem-llm/` by default. Override with `TOTEM_STORAGE_DIR=/your/path totem-llm start`.
42
+ Before installing Totem LLM, first install the below prerequisites:
46
43
 
47
- ### Troubleshooting
44
+ - **[Node.js](https://nodejs.org/)** v18 or later
45
+ - **[Ollama](https://ollama.com/)** (recommended for local models) *OR*
46
+ - **[OpenRouter](https://openrouter.ai/)** API key (for cloud models)
48
47
 
49
- **npm not found after node.js install**:
48
+ ---
50
49
 
51
- Common on windows, you may need to run `npm.cmd` instead of just `npm`
50
+ ## 🎯 Quick Start
52
51
 
53
- **totem-llm command not found after install**:
52
+ ### 1️⃣ Install the Package
54
53
 
55
- Run `npx.cmd totem-llm start` instead of `totem-llm start`, or add the npm global bin directory to your PATH.
54
+ Open your terminal and run:
56
55
 
57
- > **Windows (PowerShell)**: If you see `cannot be loaded because running scripts is disabled`, run this once in PowerShell then retry:
58
- > ```powershell
59
- > Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
60
- > ```
61
- > Alternatively, use **Command Prompt** (`cmd.exe`) where `totem-llm.cmd` runs without any policy change.
56
+ ```bash
57
+ npm install -g totem-llm
58
+ ```
62
59
 
63
- ---
60
+ This automatically downloads Totem LLM and installs all dependencies (server, collector, frontend).
61
+
62
+ ### 2️⃣ Start Totem LLM
64
63
 
65
- ## Quick Start (Build from Source)
64
+ ```bash
65
+ totem-llm start
66
+ ```
66
67
 
67
- ### Prerequisites
68
+ **First run:** Creates `~/totem-llm/` directory, migrates database, then launches services.
68
69
 
69
- - [Node.js](https://nodejs.org/) v18 or later
70
- - [Yarn](https://yarnpkg.com/) v1.x (`npm install -g yarn`)
71
- - [Ollama](https://ollama.com/) (recommended local LLM provider) or an [OpenRouter](https://openrouter.ai/) API key
72
- - Linux build machine - current build relies on `cp` command with `--reflink` option for efficient file copying. Building on non-Linux platforms may require additional setup (e.g. WSL on Windows).
70
+ **Access:** Open [http://localhost:8686](http://localhost:8686) in your browser.
71
+
72
+ ### 3️⃣ Optional: Install as Desktop App
73
+
74
+ In the browser UI, click the **install icon** in the address bar (or use your browser menu) to add Totem LLM to your desktop. This is a link to the web GUI, but with its own window and taskbar/dock icon for easy access.
75
+
76
+ ---
73
77
 
74
- ### 1. Clone the repository
78
+ ## 📚 Command Reference
75
79
 
76
80
  ```bash
77
- git clone https://github.com/fred-terzi/totem-llm.git
78
- cd totem-llm
81
+ totem-llm start # Start server and collector
82
+ totem-llm --no-collector # Start server only (skip document ingestion)
83
+ totem-llm --port=8080 # Use custom server port
84
+ totem-llm setup # Run first-time setup without starting
85
+ totem-llm --help # Show all available commands
79
86
  ```
80
87
 
81
- ### 2. Install dependencies and initialise the database
88
+ ### Environment Variables
82
89
 
83
90
  ```bash
84
- yarn setup
91
+ # Override default storage location (default: ~/totem-llm)
92
+ export TOTEM_STORAGE_DIR=/your/custom/path
93
+ totem-llm start
85
94
  ```
86
95
 
87
- This installs dependencies for all three packages (`server`, `collector`, `frontend`), copies the example `.env` files, generates the Prisma client, and runs database migrations.
96
+ ---
88
97
 
89
- ### 3. Configure environment variables
98
+ ## 🛠️ Troubleshooting
90
99
 
91
- Open `server/.env.development` and set at minimum:
100
+ | Issue | Solution |
101
+ |-------|----------|
102
+ | **npm not found** (Windows) | Run `npm.cmd` instead of `npm` |
103
+ | **totem-llm command not found** | Run `npx.cmd totem-llm start` or add npm global bin to PATH |
104
+ | **PowerShell script execution error** | Run `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser` once, then retry |
105
+ | **Alternative on Windows** | Use **Command Prompt** (`cmd.exe`) instead of PowerShell |
92
106
 
93
- ```env
94
- STORAGE_DIR="/absolute/path/to/your/storage"
95
- ```
96
107
 
97
- All other settings (LLM provider, embedding model, etc.) can be changed later through the UI.
108
+ ## 🙏 Acknowledgments
98
109
 
99
- ### 4. Start in development mode
110
+ Totem LLM is built on the foundation of **[AnythingLLM](https://github.com/Mintplex-Labs/anything-llm)** by Mintplex Labs. We are deeply grateful for their excellent work.
100
111
 
101
- ```bash
102
- yarn dev:all
103
- ```
112
+ ---
104
113
 
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.
114
+ ## 📄 License
106
115
 
107
- > Alternatively, run each service in its own terminal: `yarn dev:server`, `yarn dev:collector`, `yarn dev:frontend`.
116
+ Totem LLM is released under the **MIT License** — see [LICENSE](LICENSE) for details.
108
117
 
109
- ## Thanks
118
+ ## 📞 Support
110
119
 
111
- Massive thank you and appreciation to AnythingLLM. Totem LLM is a fork of AnythingLLM, and we are grateful for the foundation they have provided. We will continue to build on their work and contribute back to the community.
120
+ - 🐛 **Report Issues** → [GitHub Issues](https://github.com/fred-terzi/totem-llm/issues)
121
+ - 💬 **Discussions** → [GitHub Discussions](https://github.com/fred-terzi/totem-llm/discussions)
122
+
123
+ ---
112
124
 
113
- https://github.com/Mintplex-Labs/anything-llm
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Formatting utilities for professional, modern CLI output using ANSI color codes.
3
+ */
4
+
5
+ // ANSI color codes
6
+ const colors = {
7
+ reset: "\x1b[0m",
8
+ bold: "\x1b[1m",
9
+ dim: "\x1b[2m",
10
+ bright: "\x1b[1m",
11
+
12
+ // Foreground colors
13
+ black: "\x1b[30m",
14
+ red: "\x1b[31m",
15
+ green: "\x1b[32m",
16
+ yellow: "\x1b[33m",
17
+ blue: "\x1b[34m",
18
+ magenta: "\x1b[35m",
19
+ cyan: "\x1b[36m",
20
+ white: "\x1b[37m",
21
+ gray: "\x1b[90m",
22
+
23
+ // Bright foreground colors
24
+ brightRed: "\x1b[91m",
25
+ brightGreen: "\x1b[92m",
26
+ brightYellow: "\x1b[93m",
27
+ brightBlue: "\x1b[94m",
28
+ brightMagenta: "\x1b[95m",
29
+ brightCyan: "\x1b[96m",
30
+ };
31
+
32
+ // Symbols
33
+ const symbols = {
34
+ dot: "●",
35
+ arrow: "→",
36
+ check: "✓",
37
+ cross: "✕",
38
+ dash: "─",
39
+ pipe: "│",
40
+ corner: "└",
41
+ tee: "├",
42
+ };
43
+
44
+ /**
45
+ * Format text with color and styling
46
+ * @param {string} text - Text to format
47
+ * @param {string} color - Color key from colors object
48
+ * @param {string} style - Optional style: 'bold', 'dim'
49
+ * @returns {string}
50
+ */
51
+ export function colorize(text, color = "white", style) {
52
+ const colorCode = colors[color] || colors.white;
53
+ const styleCode = style === "bold" ? colors.bold : style === "dim" ? colors.dim : "";
54
+ return `${styleCode}${colorCode}${text}${colors.reset}`;
55
+ }
56
+
57
+ /**
58
+ * Format a section header
59
+ * @param {string} title - Header text
60
+ * @returns {string}
61
+ */
62
+ export function header(title) {
63
+ const line = symbols.dash.repeat(70);
64
+ return `\n${colorize(line, "cyan")}\n${colorize(title, "cyan", "bold")}\n${colorize(line, "cyan")}\n`;
65
+ }
66
+
67
+ /**
68
+ * Format a status line with label and value
69
+ * @param {string} label - Label text
70
+ * @param {string} value - Value text
71
+ * @param {string} valueColor - Color for value
72
+ * @returns {string}
73
+ */
74
+ export function statusLine(label, value, valueColor = "brightGreen") {
75
+ const paddedLabel = label.padEnd(25);
76
+ return ` ${colorize(paddedLabel, "gray")} ${colorize(value, valueColor)}\n`;
77
+ }
78
+
79
+ /**
80
+ * Format a section with a title and items
81
+ * @param {string} title - Section title
82
+ * @param {string[]} items - Array of items to display
83
+ * @returns {string}
84
+ */
85
+ export function section(title, items = []) {
86
+ let output = `\n${colorize(`${symbols.tee} ${title}`, "brightCyan", "bold")}\n`;
87
+ items.forEach((item, index) => {
88
+ const isLast = index === items.length - 1;
89
+ const prefix = isLast ? symbols.corner : symbols.tee;
90
+ output += ` ${colorize(prefix, "cyan")} ${item}\n`;
91
+ });
92
+ return output;
93
+ }
94
+
95
+ /**
96
+ * Format a highlighted box around text
97
+ * @param {string} text - Text to highlight
98
+ * @param {string} color - Color for the box
99
+ * @returns {string}
100
+ */
101
+ export function highlightBox(text, color = "brightYellow") {
102
+ const width = Math.max(text.split("\n").map((l) => l.length).reduce((a, b) => Math.max(a, b), 0)) + 4;
103
+ const line = colorize(symbols.dash.repeat(width), color);
104
+ const paddedText = text
105
+ .split("\n")
106
+ .map((l) => ` ${l}`)
107
+ .join("\n");
108
+
109
+ return `\n${line}\n${colorize(paddedText, color, "bold")}\n${line}\n`;
110
+ }
111
+
112
+ /**
113
+ * Format a clickable/highlighted URL
114
+ * @param {string} url - The URL
115
+ * @returns {string}
116
+ */
117
+ export function urlStyle(url) {
118
+ return colorize(url, "brightBlue", "bold");
119
+ }
120
+
121
+ /**
122
+ * Format a success message with icon
123
+ * @param {string} message - Message text
124
+ * @returns {string}
125
+ */
126
+ export function success(message) {
127
+ return `${colorize(symbols.check, "brightGreen", "bold")} ${colorize(message, "brightGreen")}`;
128
+ }
129
+
130
+ /**
131
+ * Format an info message with icon
132
+ * @param {string} message - Message text
133
+ * @returns {string}
134
+ */
135
+ export function info(message) {
136
+ return `${colorize(symbols.dot, "brightBlue")} ${colorize(message, "white")}`;
137
+ }
138
+
139
+ /**
140
+ * Format a warning message
141
+ * @param {string} message - Message text
142
+ * @returns {string}
143
+ */
144
+ export function warning(message) {
145
+ return `${colorize(symbols.cross, "brightYellow")} ${colorize(message, "brightYellow")}`;
146
+ }
147
+
148
+ /**
149
+ * Format an error message
150
+ * @param {string} message - Message text
151
+ * @returns {string}
152
+ */
153
+ export function error(message) {
154
+ return `${colorize(symbols.cross, "brightRed")} ${colorize(message, "brightRed")}`;
155
+ }
156
+
157
+ /**
158
+ * Create a formatted table row
159
+ * @param {string[]} cells - Array of cell contents
160
+ * @param {number[]} widths - Array of column widths
161
+ * @returns {string}
162
+ */
163
+ export function tableRow(cells, widths) {
164
+ return cells
165
+ .map((cell, i) => {
166
+ const width = widths[i] || 20;
167
+ return String(cell).padEnd(width);
168
+ })
169
+ .join(" ");
170
+ }
171
+
172
+ /**
173
+ * Format application startup banner
174
+ * @param {string} version - App version
175
+ * @param {string} environment - Environment name
176
+ * @returns {string}
177
+ */
178
+ export function startupBanner(version, environment) {
179
+ const banner = `
180
+ ████████╗ ██████╗ ████████╗███████╗███╗ ███╗ ██╗ ██╗ ███╗ ███╗
181
+ ╚══██╔══╝██╔═══██╗╚══██╔══╝██╔════╝████╗ ████║ ██║ ██║ ████╗ ████║
182
+ ██║ ██║ ██║ ██║ █████╗ ██╔████╔██║ ██║ ██║ ██╔████╔██║
183
+ ██║ ██║ ██║ ██║ ██╔══╝ ██║╚██╔╝██║ ██║ ██║ ██║╚██╔╝██║
184
+ ██║ ╚██████╔╝ ██║ ███████╗██║ ╚═╝ ██║ ███████╗███████╗██║ ╚═╝ ██║
185
+ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚══════╝╚══════╝╚═╝ ╚═╝
186
+ `;
187
+
188
+ return (
189
+ colorize(banner, "cyan") +
190
+ `\n ${colorize("Your Mind Leads", "brightCyan", "bold")}\n` +
191
+ ` ${colorize("v" + version, "dim")}\n`
192
+ );
193
+ }
194
+
195
+ /**
196
+ * Format a divider line
197
+ * @param {string} color - Color for the divider
198
+ * @returns {string}
199
+ */
200
+ export function divider(color = "cyan") {
201
+ return colorize(symbols.dash.repeat(80), color) + "\n";
202
+ }
203
+
204
+ export { colors, symbols };
package/lib/launcher.js CHANGED
@@ -3,6 +3,32 @@ import { join } from "path";
3
3
  import { appendFileSync, mkdirSync, writeFileSync } from "fs";
4
4
  import { getEnv, getStorageDir } from "./config.js";
5
5
  import { getPackageVersion } from "./version.js";
6
+ import {
7
+ startupBanner,
8
+ header,
9
+ statusLine,
10
+ section,
11
+ highlightBox,
12
+ urlStyle,
13
+ success,
14
+ info,
15
+ warning,
16
+ error,
17
+ divider,
18
+ } from "./formatter.js";
19
+
20
+ /**
21
+ * Create a simple progress bar for terminal output.
22
+ * @param {number} percent Progress percentage (0-100)
23
+ * @param {number} width Bar width in characters (default 30)
24
+ * @returns {string} Formatted progress bar
25
+ */
26
+ function createProgressBar(percent, width = 30) {
27
+ const filled = Math.round((width * percent) / 100);
28
+ const empty = width - filled;
29
+ const bar = "█".repeat(filled) + "░".repeat(empty);
30
+ return `[${bar}] ${percent}%`;
31
+ }
6
32
 
7
33
  /**
8
34
  * Spawn a Node.js service and prefix every log line with a fixed-width label.
@@ -69,26 +95,53 @@ export async function launch({ packageRoot, noCollector = false, port } = {}) {
69
95
  const logsDir = join(getStorageDir(), "logs");
70
96
  mkdirSync(logsDir, { recursive: true });
71
97
  const sessionLogFile = join(logsDir, "totem-session.log");
72
- writeFileSync(sessionLogFile, `${"=".repeat(80)}\nSession started: ${new Date().toISOString()}\n${"=".repeat(80)}\n`);
73
-
74
- process.stdout.write(`
75
- ████████╗ ██████╗ ████████╗███████╗███╗ ███╗ ██╗ ██╗ ███╗ ███╗
76
- ╚══██╔══╝██╔═══██╗╚══██╔══╝██╔════╝████╗ ████║ ██║ ██║ ████╗ ████║
77
- ██║ ██║ ██║ ██║ █████╗ ██╔████╔██║ ██║ ██║ ██╔████╔██║
78
- ██║ ██║ ██║ ██║ ██╔══╝ ██║╚██╔╝██║ ██║ ██║ ██║╚██╔╝██║
79
- ██║ ╚██████╔╝ ██║ ███████╗██║ ╚═╝ ██║ ███████╗███████╗██║ ╚═╝ ██║
80
- ╚═╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚══════╝╚══════╝╚═╝ ╚═╝
81
-
82
- Your Private AI
83
-
84
-
85
- Starting Totem LLM…\n
86
- ============================================================================\n
87
- Logs → ${sessionLogFile}\n
88
- LAUNCH UI HERE (ctrl + click)--> http://localhost:${serverPort}/\n
89
-
90
- Press Ctrl+C to stop.\n
91
- `);
98
+ writeFileSync(
99
+ sessionLogFile,
100
+ `${"=".repeat(80)}\nSession started: ${new Date().toISOString()}\n${"=".repeat(80)}\n`
101
+ );
102
+
103
+ // Display startup banner with professional formatting
104
+ process.stdout.write("\n");
105
+ process.stdout.write(startupBanner(appVersion, "production"));
106
+
107
+ // Display loading progress
108
+ process.stdout.write(section("Starting Services", []));
109
+ let progressPercent = 0;
110
+ const progressInterval = setInterval(() => {
111
+ if (progressPercent < 80) {
112
+ progressPercent += Math.random() * 25;
113
+ if (progressPercent > 80) progressPercent = 80;
114
+ process.stdout.write(
115
+ `\r${info("Loading")} ${createProgressBar(Math.floor(progressPercent))}`
116
+ );
117
+ }
118
+ }, 300);
119
+
120
+ // Display UI access information
121
+ process.stdout.write(
122
+ highlightBox(
123
+ `Open UI in browser:\n${urlStyle(`http://localhost:${serverPort}/`)}`,
124
+ "brightGreen"
125
+ )
126
+ );
127
+
128
+ // Display logs information
129
+ process.stdout.write(
130
+ section("Configuration", [
131
+ `Logs: ${urlStyle(sessionLogFile)}`,
132
+ `Storage: ${urlStyle(getStorageDir())}`,
133
+ ])
134
+ );
135
+
136
+ // Display help information
137
+ process.stdout.write(
138
+ section("Keyboard Shortcuts", [
139
+ `${info("Ctrl+C")} Stop all services`,
140
+ `${info("Check logs")} for errors and service status`,
141
+ ])
142
+ );
143
+
144
+ process.stdout.write(divider());
92
145
 
93
146
  const serverProc = spawnService(nodeExe, serverDir, env, "[server]", sessionLogFile);
94
147
 
@@ -97,6 +150,13 @@ export async function launch({ packageRoot, noCollector = false, port } = {}) {
97
150
  collectorProc = spawnService(nodeExe, collectorDir, env, "[collector]", sessionLogFile);
98
151
  }
99
152
 
153
+ // Complete the progress bar
154
+ progressPercent = 100;
155
+ process.stdout.write(
156
+ `\r${success("Ready")} ${createProgressBar(100)}\n\n`
157
+ );
158
+ clearInterval(progressInterval);
159
+
100
160
  // ── Graceful shutdown ────────────────────────────────────────────────────
101
161
  let shuttingDown = false;
102
162
 
@@ -104,9 +164,9 @@ export async function launch({ packageRoot, noCollector = false, port } = {}) {
104
164
  if (shuttingDown) return;
105
165
  shuttingDown = true;
106
166
 
107
- const shutdownMsg = `\nShutting down (${signal})…`;
167
+ const shutdownMsg = warning(`\nShutting down (${signal})…`);
108
168
  process.stdout.write(`${shutdownMsg}\n`);
109
- appendFileSync(sessionLogFile, `${shutdownMsg}\n`);
169
+ appendFileSync(sessionLogFile, `Shutting down (${signal})…\n`);
110
170
 
111
171
  serverProc.kill("SIGTERM");
112
172
  if (collectorProc) collectorProc.kill("SIGTERM");
@@ -127,9 +187,9 @@ export async function launch({ packageRoot, noCollector = false, port } = {}) {
127
187
  // Exit this process when the server exits unexpectedly
128
188
  serverProc.on("close", (code) => {
129
189
  if (!shuttingDown && code !== null && code !== 0) {
130
- const errMsg = `[server] exited unexpectedly with code ${code}`;
190
+ const errMsg = error(`Server exited unexpectedly with code ${code}`);
131
191
  process.stderr.write(`${errMsg}\n`);
132
- appendFileSync(sessionLogFile, `${errMsg}\n`);
192
+ appendFileSync(sessionLogFile, `Server exited unexpectedly with code ${code}\n`);
133
193
  shutdown("server-exit");
134
194
  setTimeout(() => process.exit(1), 500);
135
195
  }
@@ -138,9 +198,9 @@ export async function launch({ packageRoot, noCollector = false, port } = {}) {
138
198
  if (collectorProc) {
139
199
  collectorProc.on("close", (code) => {
140
200
  if (!shuttingDown && code !== null && code !== 0) {
141
- const errMsg = `[collector] exited with code ${code} — document ingestion unavailable`;
201
+ const errMsg = warning(`Collector exited with code ${code} — document ingestion unavailable`);
142
202
  process.stderr.write(`${errMsg}\n`);
143
- appendFileSync(sessionLogFile, `${errMsg}\n`);
203
+ appendFileSync(sessionLogFile, `Collector exited with code ${code} — document ingestion unavailable\n`);
144
204
  }
145
205
  });
146
206
  }
package/lib/setup.js CHANGED
@@ -139,7 +139,7 @@ export async function setup({ packageRoot, force = false }) {
139
139
  // 3. Feature flags config (copy default if not already present in storage dir)
140
140
  const __filename = fileURLToPath(import.meta.url);
141
141
  const __dirname = dirname(__filename);
142
- const featuresConfigDest = join(storageDir, 'totem.features.json');
142
+ const featuresConfigDest = join(storageDir, '.totem.features.json');
143
143
  const featuresConfigSrc = join(__dirname, '..', 'config', 'totem.features.json');
144
144
  if (!existsSync(featuresConfigDest) && existsSync(featuresConfigSrc)) {
145
145
  copyFileSync(featuresConfigSrc, featuresConfigDest);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "totem-llm",
3
- "version": "0.3.6",
3
+ "version": "0.5.0",
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",
@@ -1 +1 @@
1
- import{d as c,j as s,e as d,f as g,i as f,l as S,r as a,W as i,L as x}from"../index.js";import{W as h}from"./index-d948bb92.js";import{a as w}from"./index-ed14fe75.js";import"./files-40998f84.js";import"./purify-17c89583.js";/* empty css */import"./Check-7e83fb0d.js";import"./Copy-d323e545.js";import"./truncate-dd7b0c29.js";import"./mcpServers-9495fe60.js";import"./admin-04b020fe.js";import"./Database-f1a76329.js";import"./File-b92df2f3.js";import"./XCircle-edfe554c.js";import"./Trans-308f36d6.js";import"./Brain-24e386d5.js";import"./Browser-1820fad0.js";import"./index-14be6ea8.js";import"./agentFlows-e4124dd3.js";import"./index-36acf56a.js";import"./communityHub-aba61686.js";import"./WarningCircle-27467274.js";import"./index-8ba96f42.js";import"./Hammer-05b5ccc7.js";import"./react-beautiful-dnd.esm-149a7a55.js";import"./moment-49fc1b76.js";import"./Eye-9f033dc0.js";import"./index-4fb4d421.js";function Q(){const{loading:t,requiresAuth:r,mode:e}=c();return t?s.jsx(d,{}):r!==!1?s.jsx(s.Fragment,{children:r!==null&&s.jsx(g,{mode:e})}):s.jsxs("div",{className:"w-screen h-screen overflow-hidden bg-zinc-950 light:bg-slate-50 flex",children:[!f&&s.jsx(w,{}),s.jsx(j,{})]})}function j(){const{slug:t}=S(),[r,e]=a.useState(null),[n,m]=a.useState(null);return a.useEffect(()=>{async function p(){if(!t)return;const o=await i.bySlug(t);if(!o){e(null),m(t);return}const[l,{showAgentCommand:u}]=await Promise.all([i.getSuggestedMessages(t),i.agentCommandAvailable(t)]);e({...o,suggestedMessages:l,showAgentCommand:u}),m(t),localStorage.setItem(x,JSON.stringify({slug:o.slug,name:o.name}))}p()},[t]),s.jsx(h,{loading:n!==t,workspace:r})}export{Q as default};
1
+ import{d as c,j as s,e as d,f as g,i as f,l as S,r as a,W as i,L as x}from"../index.js";import{W as h}from"./index-df53cf14.js";import{a as w}from"./index-ed14fe75.js";import"./files-40998f84.js";import"./purify-17c89583.js";/* empty css */import"./Check-7e83fb0d.js";import"./Copy-d323e545.js";import"./truncate-dd7b0c29.js";import"./mcpServers-9495fe60.js";import"./admin-04b020fe.js";import"./Database-f1a76329.js";import"./File-b92df2f3.js";import"./XCircle-edfe554c.js";import"./Trans-308f36d6.js";import"./Brain-24e386d5.js";import"./Browser-1820fad0.js";import"./index-14be6ea8.js";import"./agentFlows-e4124dd3.js";import"./index-36acf56a.js";import"./communityHub-aba61686.js";import"./WarningCircle-27467274.js";import"./index-8ba96f42.js";import"./Hammer-05b5ccc7.js";import"./react-beautiful-dnd.esm-149a7a55.js";import"./moment-49fc1b76.js";import"./Eye-9f033dc0.js";import"./index-4fb4d421.js";function Q(){const{loading:t,requiresAuth:r,mode:e}=c();return t?s.jsx(d,{}):r!==!1?s.jsx(s.Fragment,{children:r!==null&&s.jsx(g,{mode:e})}):s.jsxs("div",{className:"w-screen h-screen overflow-hidden bg-zinc-950 light:bg-slate-50 flex",children:[!f&&s.jsx(w,{}),s.jsx(j,{})]})}function j(){const{slug:t}=S(),[r,e]=a.useState(null),[n,m]=a.useState(null);return a.useEffect(()=>{async function p(){if(!t)return;const o=await i.bySlug(t);if(!o){e(null),m(t);return}const[l,{showAgentCommand:u}]=await Promise.all([i.getSuggestedMessages(t),i.agentCommandAvailable(t)]);e({...o,suggestedMessages:l,showAgentCommand:u}),m(t),localStorage.setItem(x,JSON.stringify({slug:o.slug,name:o.name}))}p()},[t]),s.jsx(h,{loading:n!==t,workspace:r})}export{Q as default};