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 +77 -66
- package/lib/formatter.js +204 -0
- package/lib/launcher.js +86 -26
- package/lib/setup.js +1 -1
- package/package.json +1 -1
- package/server/public/assets/{index-f6b98bbf.js → index-396db6a9.js} +1 -1
- package/server/public/assets/{index-e28b4d22.js → index-55518467.js} +1 -1
- package/server/public/assets/{index-d948bb92.js → index-df53cf14.js} +1 -1
- package/server/public/index.js +3 -3
- package/totem.features.cjs +1 -1
package/README.md
CHANGED
|
@@ -1,113 +1,124 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
Totem LLM
|
|
3
|
+
# Totem LLM
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
###
|
|
10
|
+
### Your Mind Leads
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
[](https://nodejs.org/)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
[](https://github.com/fred-terzi/totem-llm)
|
|
13
15
|
|
|
14
|
-
### 1. Install the package
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
**A sovereign AI solution for consumer hardware. Keep your data private, run locally, stay in control.**
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
npm install -g totem-llm
|
|
20
|
-
```
|
|
19
|
+
</div>
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
---
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
## 🚀 Key Features
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
32
|
+
---
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
## 📋 About
|
|
33
35
|
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
+
Before installing Totem LLM, first install the below prerequisites:
|
|
46
43
|
|
|
47
|
-
|
|
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
|
-
|
|
48
|
+
---
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
## 🎯 Quick Start
|
|
52
51
|
|
|
53
|
-
|
|
52
|
+
### 1️⃣ Install the Package
|
|
54
53
|
|
|
55
|
-
|
|
54
|
+
Open your terminal and run:
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
64
|
+
```bash
|
|
65
|
+
totem-llm start
|
|
66
|
+
```
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
**First run:** Creates `~/totem-llm/` directory, migrates database, then launches services.
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
78
|
+
## 📚 Command Reference
|
|
75
79
|
|
|
76
80
|
```bash
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
###
|
|
88
|
+
### Environment Variables
|
|
82
89
|
|
|
83
90
|
```bash
|
|
84
|
-
|
|
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
|
-
|
|
96
|
+
---
|
|
88
97
|
|
|
89
|
-
|
|
98
|
+
## 🛠️ Troubleshooting
|
|
90
99
|
|
|
91
|
-
|
|
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
|
-
|
|
108
|
+
## 🙏 Acknowledgments
|
|
98
109
|
|
|
99
|
-
|
|
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
|
-
|
|
102
|
-
yarn dev:all
|
|
103
|
-
```
|
|
112
|
+
---
|
|
104
113
|
|
|
105
|
-
|
|
114
|
+
## 📄 License
|
|
106
115
|
|
|
107
|
-
|
|
116
|
+
Totem LLM is released under the **MIT License** — see [LICENSE](LICENSE) for details.
|
|
108
117
|
|
|
109
|
-
##
|
|
118
|
+
## 📞 Support
|
|
110
119
|
|
|
111
|
-
|
|
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
|
package/lib/formatter.js
ADDED
|
@@ -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(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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,
|
|
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 = `
|
|
190
|
+
const errMsg = error(`Server exited unexpectedly with code ${code}`);
|
|
131
191
|
process.stderr.write(`${errMsg}\n`);
|
|
132
|
-
appendFileSync(sessionLogFile,
|
|
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 = `
|
|
201
|
+
const errMsg = warning(`Collector exited with code ${code} — document ingestion unavailable`);
|
|
142
202
|
process.stderr.write(`${errMsg}\n`);
|
|
143
|
-
appendFileSync(sessionLogFile,
|
|
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 +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-
|
|
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};
|