totaljs-totem 0.0.0-stage → 0.1.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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +178 -2
- package/bin/totem.js +312 -0
- package/config +66 -0
- package/controllers/api.js +93 -0
- package/controllers/default.js +36 -0
- package/definitions/00-languages.js +24 -0
- package/definitions/auth.js +75 -0
- package/definitions/db.js +8 -0
- package/definitions/func.js +158 -0
- package/definitions/init.js +37 -0
- package/definitions/providers.js +45 -0
- package/index.js +23 -0
- package/modules/agent.js +794 -0
- package/modules/agents.js +44 -0
- package/modules/attachments.js +154 -0
- package/modules/commands.js +52 -0
- package/modules/conventions.js +115 -0
- package/modules/diagnostics.js +110 -0
- package/modules/discovery.js +256 -0
- package/modules/fetcher.js +88 -0
- package/modules/formatter.js +89 -0
- package/modules/git.js +57 -0
- package/modules/instructions.js +175 -0
- package/modules/jobs.js +55 -0
- package/modules/knowledge.js +212 -0
- package/modules/mcpclient.js +376 -0
- package/modules/patch.js +165 -0
- package/modules/permissions.js +147 -0
- package/modules/provider.js +496 -0
- package/modules/schedules.js +119 -0
- package/modules/snapshots.js +412 -0
- package/modules/subagent.js +180 -0
- package/modules/terminal.js +105 -0
- package/modules/worktrees.js +128 -0
- package/package.json +63 -5
- package/plugins/chat/index.js +3 -0
- package/plugins/chat/public/index.html +1113 -0
- package/plugins/projects/index.js +3 -0
- package/plugins/projects/public/form.html +98 -0
- package/plugins/projects/public/index.html +276 -0
- package/plugins/schedules/index.js +3 -0
- package/plugins/schedules/public/index.html +161 -0
- package/plugins/settings/index.js +3 -0
- package/plugins/settings/public/form.html +149 -0
- package/plugins/settings/public/index.html +162 -0
- package/private/agents/explore.md +10 -0
- package/private/agents/general.md +10 -0
- package/private/commands/init.md +9 -0
- package/private/conventions/SOURCE.md +5 -0
- package/private/conventions/check.js +366 -0
- package/private/languages.json +5 -0
- package/private/mcp.json +5 -0
- package/private/prompts/base.txt +10 -0
- package/private/prompts/plan.txt +3 -0
- package/private/prompts/totaljs.txt +33 -0
- package/private/providers.json +22 -0
- package/private/recipes/answer.md +16 -0
- package/private/recipes/api-action.md +38 -0
- package/private/recipes/auth.md +29 -0
- package/private/recipes/component.md +28 -0
- package/private/recipes/config.md +14 -0
- package/private/recipes/cron.md +24 -0
- package/private/recipes/localization.md +14 -0
- package/private/recipes/mods.md +27 -0
- package/private/recipes/node.md +15 -0
- package/private/recipes/nosql.md +18 -0
- package/private/recipes/plugin.md +41 -0
- package/private/recipes/restbuilder.md +17 -0
- package/private/recipes/route.md +29 -0
- package/private/recipes/structure.md +34 -0
- package/private/recipes/style.md +23 -0
- package/private/recipes/uid-now.md +15 -0
- package/private/recipes/view.md +29 -0
- package/private/recipes/websocket.md +26 -0
- package/private/skills/totaljs-conventions/SKILL.md +47 -0
- package/private/terminal.py +77 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/css/default.css +119 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +18 -0
- package/public/icon-192.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/img/providers/SOURCE.md +3 -0
- package/public/img/providers/anthropic.svg +1 -0
- package/public/img/providers/cerebras.svg +1 -0
- package/public/img/providers/cohere.svg +1 -0
- package/public/img/providers/deepseek.svg +1 -0
- package/public/img/providers/fireworks.svg +1 -0
- package/public/img/providers/gemini.svg +1 -0
- package/public/img/providers/groq.svg +1 -0
- package/public/img/providers/huggingface.svg +1 -0
- package/public/img/providers/litellm.svg +1 -0
- package/public/img/providers/llamacpp.svg +1 -0
- package/public/img/providers/lmstudio.svg +1 -0
- package/public/img/providers/mistral.svg +1 -0
- package/public/img/providers/moonshot.svg +1 -0
- package/public/img/providers/nvidia.svg +1 -0
- package/public/img/providers/ollama.svg +1 -0
- package/public/img/providers/openai.svg +1 -0
- package/public/img/providers/openrouter.svg +1 -0
- package/public/img/providers/together.svg +1 -0
- package/public/img/providers/vllm.svg +1 -0
- package/public/img/providers/xai.svg +1 -0
- package/public/img/totem-dark.svg +12 -0
- package/public/img/totem-light.svg +12 -0
- package/public/js/default.js +161 -0
- package/public/site.webmanifest +11 -0
- package/public/ui-ui-17v1r9h.min.js +332 -0
- package/resources/es.resource +224 -0
- package/resources/sk.resource +224 -0
- package/schemas/account.js +37 -0
- package/schemas/folders.js +50 -0
- package/schemas/permissions.js +48 -0
- package/schemas/projects.js +124 -0
- package/schemas/providers.js +135 -0
- package/schemas/schedules.js +103 -0
- package/schemas/sessions.js +521 -0
- package/schemas/tools.js +427 -0
- package/views/index.html +72 -0
- package/views/login.html +53 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
The first version.
|
|
6
|
+
|
|
7
|
+
- **The agent:** reads, searches, edits, patches and runs commands in the folder of a project, and cannot leave it. Every file change and command asks first. Plan mode only reads; Build mode changes things. Diff of each step, undo and redo, background commands, sub-agents, `/commands` and `/init`.
|
|
8
|
+
- **Models:** Ollama, LM Studio and llama.cpp on your computer are detected; OpenRouter, OpenAI, Groq, Mistral, DeepSeek and any OpenAI-compatible server are in the list. The formats of Anthropic and Gemini are written and tested against simulators, not yet with real keys. Small models get a real context in Ollama, a low temperature, a stop to loops and a retry when they answer nothing.
|
|
9
|
+
- **Total.js knowledge:** recipes, the pages of Forge for each request, the conventions as a skill, a check of what is written (syntax, style, invented functions) and a correction of the code in the answers. An evaluation with 29 cases measures it with any model.
|
|
10
|
+
- **Interface:** projects and sessions, streaming chat, copy button on code boxes, attachments, split diff, git state, a terminal with an interactive shell (macOS and Linux), scheduled prompts, sessions in git worktrees, light and dark theme, English, Spanish and Slovak.
|
|
11
|
+
- **Protocols:** a client of MCP tools and prompts, and Totem as an MCP server (off by default).
|
|
12
|
+
- **Security:** secret files ask first, keys and the token never reach the model, a server that listens beyond this computer refuses to start without a token, strict cookie, same-origin checks and security headers.
|
|
13
|
+
- **Delivery:** the `totem` command, a Docker image that runs as a user that is not root, and the package `totaljs-totem` (the data goes to a folder of your user when it is installed from npm).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Spanish Total.js Platform
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,3 +1,179 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/totem-buttom.svg" alt="Total.js Totem" width="128" height="128">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">Total.js Totem</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center"><b>An AI coding environment written as a Total.js 5 application.</b><br>Open a project, talk to an agent that reads your code, edits files and runs commands, and review every change. You choose the model: one on your computer, a free cloud one, or a paid one.</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://github.com/totaljs-es/totem/actions/workflows/test.yml"><img src="https://github.com/totaljs-es/totem/actions/workflows/test.yml/badge.svg" alt="Tests"></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/totaljs-totem"><img src="https://img.shields.io/npm/v/totaljs-totem" alt="npm version"></a>
|
|
12
|
+
<img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license">
|
|
13
|
+
<img src="https://img.shields.io/badge/node-%E2%89%A5%2022-3C873A" alt="Node.js 22 or newer">
|
|
14
|
+
<img src="https://img.shields.io/badge/Total.js-5-3F8F2A" alt="Total.js 5">
|
|
15
|
+
<img src="https://img.shields.io/badge/jComponent-20-3F8F2A" alt="jComponent 20">
|
|
16
|
+
<img src="https://img.shields.io/badge/Docker-ready-2496ED" alt="Docker ready">
|
|
17
|
+
<img src="https://img.shields.io/badge/languages-en%20%C2%B7%20es%20%C2%B7%20sk-informational" alt="Languages: English, Spanish, Slovak">
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
Totem is one application with three ways to run: a command, a Docker container and (next) a native window. The server is plain Total.js: actions, a few controllers, NoSQL for its own data, and jComponent 20 for the interface. There is no build step.
|
|
21
|
+
|
|
22
|
+
## What you get
|
|
23
|
+
|
|
24
|
+
- **An agent with tools.** It reads, searches, edits, patches and runs commands in your project folder, and it cannot leave it (every path is resolved inside the project, symbolic links included).
|
|
25
|
+
- **You stay in control.** Every file change and command asks first, unless you allow it. **Plan mode** only reads and describes; **Build mode** changes things. See the diff of each step, undo it, redo it.
|
|
26
|
+
- **Any model.** Ollama, LM Studio and llama.cpp on your computer are detected for you; OpenRouter, OpenAI, Anthropic, Gemini, Groq, Mistral, DeepSeek and a dozen more (any OpenAI-compatible server) are in the list. Prompts go only to the providers you add.
|
|
27
|
+
- **It knows Total.js.** In a project that depends on `total5` the agent gets the recipes of Totem, the pages of [Forge](https://forge.totaljs.es) for your request, the conventions as a skill, and a check of everything it writes: syntax, style, invented functions. Even a 1.5B model on your laptop answers with real Total.js.
|
|
28
|
+
- **Work in parallel.** Sessions can run in their own **git worktree**, and **scheduled prompts** run by themselves, unattended and read-only unless you say otherwise.
|
|
29
|
+
- **A terminal that stays.** An interactive shell in the project folder that survives a page reload, and background commands the agent can start and read.
|
|
30
|
+
- **Open protocols.** A client of MCP servers (tools and prompts) for the agent, and Totem itself as an MCP server for other agents (off by default).
|
|
31
|
+
- **Careful with your machine.** Secrets never reach the model, `.env` files ask first, a shared server needs a token, and the Docker image runs without root.
|
|
32
|
+
- **Your language.** English, Spanish and Slovak, chosen in Settings.
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
You need [Node.js](https://nodejs.org) 22 or newer.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install -g totaljs-totem
|
|
40
|
+
totem
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The browser opens on <http://127.0.0.1:8000>. Installed from npm, your data (providers, projects, sessions) lives in a folder of your user (`totem config` prints it) and not in the package, so an update never touches it. To run it from the source instead:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
git clone https://github.com/totaljs-es/totem.git
|
|
47
|
+
cd totem
|
|
48
|
+
npm install
|
|
49
|
+
node index.js 8000
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Press **Settings** and add a model:
|
|
53
|
+
|
|
54
|
+
- **A model on your computer.** Install [Ollama](https://ollama.com), pull a model (`ollama pull qwen2.5-coder`), and press *Look for servers on this computer*.
|
|
55
|
+
- **A cloud model.** Add a provider, paste the key (it is stored encrypted on your computer and never shown again) and choose the models you want.
|
|
56
|
+
|
|
57
|
+
Then **Open a folder**, write what you want, and watch it work.
|
|
58
|
+
|
|
59
|
+
### The command
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
totem # starts the server and opens the browser
|
|
63
|
+
totem run "explain this project" --project ~/code/app --model ollama:qwen2.5-coder
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
(From a clone, `npm link` makes the `totem` command.)
|
|
67
|
+
|
|
68
|
+
| Command | What it does |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `totem` | Starts the server on port 8000 and opens the browser |
|
|
71
|
+
| `totem serve [port]` | Starts only the server |
|
|
72
|
+
| `totem run "prompt" [options]` | One prompt without the interface: the answer goes to stdout, the tool calls to stderr, the exit code says if it finished |
|
|
73
|
+
| `totem config` | Prints the folders that Totem uses |
|
|
74
|
+
|
|
75
|
+
Options of `run`: `--project <folder>`, `--model <provider:model>`, `--url <server>`, `--token <token>`, `--auto-accept` (file edits run without asking), `--yes` (every other question is answered "allow once"). Without `--url` a temporary local server is started. `--data <folder>` chooses where the data goes.
|
|
76
|
+
|
|
77
|
+
### Docker
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
echo "a-long-random-token" > token.txt
|
|
81
|
+
docker run --rm -p 127.0.0.1:8000:8000 \
|
|
82
|
+
-v "$PWD/token.txt:/run/secrets/totem_token:ro" \
|
|
83
|
+
-v "$PWD:/workspace/project" \
|
|
84
|
+
-v totem-data:/data ghcr.io/totaljs-es/totem
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
(To build it yourself: `docker build -t totem .` in a clone.)
|
|
88
|
+
|
|
89
|
+
The container listens on all interfaces, so it **refuses to start without an access token**. Your projects are mounted under `/workspace`, the data is a volume, and everything runs as a user that is not root, which makes it the isolated way to try untrusted models. There is a [compose example](docs/docker-compose.yml) and the [delivery notes](docs/delivery.md).
|
|
90
|
+
|
|
91
|
+
## The interface
|
|
92
|
+
|
|
93
|
+
| | |
|
|
94
|
+
|---|---|
|
|
95
|
+
| **Projects** | Folders with their sessions. Click one to continue, `+` for a new session, the menu for a named session or one in a **git worktree**. Rename with a double click. |
|
|
96
|
+
| **Chat** | Streaming answers, the thinking of the models that think, a copy button on every code box, attachments (images and text files), the `/commands` of the project and `/init` to write an `AGENTS.md`. |
|
|
97
|
+
| **Changes** | The files the agent changed, unified or split diff, **Undo** and **Redo**, the git state of the folder, and for a worktree session **Merge into the project** or **Discard**. |
|
|
98
|
+
| **Terminal** | The commands the agent ran, the ones in the background, and an interactive **Shell** (macOS and Linux). |
|
|
99
|
+
| **Scheduled prompts** | The clock next to Settings: a prompt every N minutes or every day, in a new session, unattended. |
|
|
100
|
+
| **Settings** | Theme (light, dark, auto), language, providers and models with their abilities. |
|
|
101
|
+
|
|
102
|
+
## Choose the model
|
|
103
|
+
|
|
104
|
+
Totem works with a 1.5B model and with the biggest ones. A few tips:
|
|
105
|
+
|
|
106
|
+
- **Small models on your computer** follow instructions better with a low temperature (`temperature : 0.2` in `config`, already the default for servers on your computer) and enough context: Ollama is asked for at least 16384 tokens, and for more when the request needs it, because its own default silently cuts the instructions.
|
|
107
|
+
- **A model that loops** (repeats one paragraph until the context is full) is stopped by a repeat penalty and a limit per answer (`ollamarepeat`, `ollamapredict`).
|
|
108
|
+
- **Reasoning models** get a *Reasoning* control. The Anthropic and Gemini formats are written and tested against simulators, not yet with real keys: try them and tell us.
|
|
109
|
+
|
|
110
|
+
## How it knows Total.js
|
|
111
|
+
|
|
112
|
+
When the project depends on `total5` (or is a new folder), Totem puts three things in front of the model:
|
|
113
|
+
|
|
114
|
+
1. **Recipes** ([`private/recipes/`](private/recipes)): short, exact answers for the common tasks (routes, actions, NoSQL, config, `RESTBuilder`, auth, CRON, WebSockets, views, jComponent, plugins...), chosen by the words of your request. They work offline and follow the conventions to the letter.
|
|
115
|
+
2. **Forge** ([forge.totaljs.es](https://forge.totaljs.es)): the pages of the documentation that match the request, through MCP. Totem asks before it starts the connection.
|
|
116
|
+
3. **A check of the code**: what the agent writes is checked against the [Total.js conventions](https://github.com/totaljs-es/conventions) (syntax, ES5, tabs, names with `|`, texts as `@(English)`, no invented functions) and the model fixes what is wrong. The code in an answer is checked too, and corrected once.
|
|
117
|
+
|
|
118
|
+
To measure it with any model:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
node scripts/knowledge-eval.js --model ollama:qwen2.5:1.5b --runs 3 --out result.json
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
It asks 29 questions about Total.js and jComponent in a temporary project and scores the answers: what must and must not appear, and the quality of the code. See [knowledge](docs/knowledge.md).
|
|
125
|
+
|
|
126
|
+
## Configuration
|
|
127
|
+
|
|
128
|
+
Settings live in the file [`config`](config) (never in environment variables). The ones you will touch:
|
|
129
|
+
|
|
130
|
+
| Setting | Default | What it does |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| `$ip` | `127.0.0.1` | The address it listens on. Anything else needs `token` |
|
|
133
|
+
| `token`, `tokenfile` | empty | The access token, or a file that has it (a Docker secret) |
|
|
134
|
+
| `temperature` | `0.2` | For the models on your computer |
|
|
135
|
+
| `ollamactx`, `ollamactxmax` | `16384`, `32768` | Context asked to Ollama |
|
|
136
|
+
| `maxsteps`, `bashtimeout` | `50`, `120000` | Limits of the agent loop |
|
|
137
|
+
| `totaljs` | `auto` | `always` or `never` to decide for every project |
|
|
138
|
+
| `forge`, `recipes`, `selfcheck`, `conventions` | `true` | The parts of the Total.js knowledge |
|
|
139
|
+
| `formatter`, `diagnostics` | `true` | Run the formatter and the checks that the project already has after a write |
|
|
140
|
+
| `terminalshell` | empty | The shell of the Terminal (empty: your `SHELL`) |
|
|
141
|
+
| `$mcp` | `false` | Totem as an MCP server for other agents |
|
|
142
|
+
|
|
143
|
+
A project can add a `.totem/` folder with `formatters.json`, `diagnostics.json`, `mcp.json`, `instructions.json`, and `agents/`, `commands/` and `skills/`.
|
|
144
|
+
|
|
145
|
+
## Security in a few lines
|
|
146
|
+
|
|
147
|
+
- The agent acts through tools only, each checked before it runs. Plan mode refuses writes and commands in code, not in a prompt.
|
|
148
|
+
- Files with secrets (`.env`) ask first; keys and the token are scrubbed from everything the model sees.
|
|
149
|
+
- A server with a token compares it in constant time, uses a strict cookie, refuses requests from other origins, and sends headers against framing.
|
|
150
|
+
- The shell and the commands run with your permissions: they protect against mistakes, not against a hostile model. For that, use the Docker image.
|
|
151
|
+
|
|
152
|
+
The details, including the honest limits (one token, one user), are in [security](docs/security.md).
|
|
153
|
+
|
|
154
|
+
## Help
|
|
155
|
+
|
|
156
|
+
| Problem | What to do |
|
|
157
|
+
|---|---|
|
|
158
|
+
| No model to choose | Settings → add a provider, or *Look for servers on this computer* with Ollama or LM Studio running |
|
|
159
|
+
| A small model seems to ignore the instructions | Check `ollamactx` in `config`: Ollama must get a real context. Totem asks for 16384 by default |
|
|
160
|
+
| The container does not start | It needs a token: mount a file at `/run/secrets/totem_token` (or set `token` in `config`) |
|
|
161
|
+
| "The terminal could not start" | The Shell needs Python 3 (macOS and Linux). It is in the Docker image |
|
|
162
|
+
| Docker cannot see your folder (Colima, Docker Desktop) | Share the folder with the virtual machine: your home folder, not `/tmp` |
|
|
163
|
+
| The agent asks to start Forge | It is the documentation connection. Allow it once for every project, or set `forge : false` |
|
|
164
|
+
| A worktree could not be created | The project needs git with at least one commit |
|
|
165
|
+
| Scheduled prompts do nothing | They run unattended: in Plan mode they only read, and anything that would ask is refused. Choose Build and auto-accept to let them edit |
|
|
166
|
+
|
|
167
|
+
Run the tests with `npm test` (about 50 seconds, with temporary folders: it never touches your data).
|
|
168
|
+
|
|
169
|
+
## Status
|
|
170
|
+
|
|
171
|
+
Phases 1 to 5 of the [roadmap](docs/roadmap.md) are built: the agent and its tools, the interface, the knowledge of Total.js, the terminal, diagnostics, the Docker image, scheduled prompts, worktrees and the hardening of a shared server. Version 0.1.0 is the first one published: the package [`totaljs-totem`](https://www.npmjs.com/package/totaljs-totem) on npm and the image `ghcr.io/totaljs-es/totem` for `linux/amd64` and `linux/arm64`. Still to come: native windows with signed installers (phase 6) and accounts with their own projects. See the [changelog](CHANGELOG.md).
|
|
172
|
+
|
|
173
|
+
## Documentation
|
|
174
|
+
|
|
175
|
+
[Vision](docs/vision.md) · [Architecture](docs/architecture.md) · [Agent loop](docs/agent-loop.md) · [Interface](docs/interface.md) · [Protocol](docs/protocol.md) · [Security](docs/security.md) · [Knowledge](docs/knowledge.md) · [Delivery](docs/delivery.md) · [Roadmap](docs/roadmap.md) · [Decisions](docs/decisions.md)
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
[MIT](LICENSE). Made by the Spanish Total.js Platform.
|
package/bin/totem.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// The totem command. It is a tooling script that runs with plain Node.js 22 or newer: it is not part of the Total.js application.
|
|
5
|
+
//
|
|
6
|
+
// totem starts the server and opens the browser
|
|
7
|
+
// totem serve [port] starts only the server (default port 8000)
|
|
8
|
+
// totem run "prompt" [options] runs one prompt without the interface and prints what happens
|
|
9
|
+
// totem config prints the folders that Totem uses
|
|
10
|
+
// totem --help
|
|
11
|
+
//
|
|
12
|
+
// Options of run: --project <folder> (default: the current folder), --model <provider:model>, --url <server> (default: a temporary
|
|
13
|
+
// local server), --token <token>, --auto-accept (edits run without asking), --yes (every question is answered "allow once").
|
|
14
|
+
|
|
15
|
+
var Child = require('child_process');
|
|
16
|
+
var Http = require('http');
|
|
17
|
+
var Net = require('net');
|
|
18
|
+
var Os = require('os');
|
|
19
|
+
var Path = require('path');
|
|
20
|
+
|
|
21
|
+
var ROOT = Path.join(__dirname, '..');
|
|
22
|
+
|
|
23
|
+
// Where the data goes. In a clone it stays in the folder of the application (databases/), which is what a developer expects. Installed from npm the
|
|
24
|
+
// folder of the application is not for what changes (it can be read only, and an update replaces it), so the data goes to a folder of the person.
|
|
25
|
+
function datafolder(opt) {
|
|
26
|
+
|
|
27
|
+
if (opt && opt.data && opt.data !== true)
|
|
28
|
+
return Path.resolve(opt.data);
|
|
29
|
+
|
|
30
|
+
if (Path.relative(ROOT, __dirname) !== 'bin' || ROOT.split(Path.sep).indexOf('node_modules') === -1)
|
|
31
|
+
return '';
|
|
32
|
+
|
|
33
|
+
var home = Os.homedir();
|
|
34
|
+
|
|
35
|
+
if (process.platform === 'darwin')
|
|
36
|
+
return Path.join(home, 'Library', 'Application Support', 'Totem');
|
|
37
|
+
|
|
38
|
+
if (process.platform === 'win32')
|
|
39
|
+
return Path.join(process.env.APPDATA || Path.join(home, 'AppData', 'Roaming'), 'Totem');
|
|
40
|
+
|
|
41
|
+
return Path.join(home, '.local', 'share', 'totem');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
exports.datafolder = datafolder;
|
|
45
|
+
|
|
46
|
+
function usage() {
|
|
47
|
+
console.log([
|
|
48
|
+
'Usage:',
|
|
49
|
+
' totem start the server and open the browser',
|
|
50
|
+
' totem serve [port] start only the server (default port 8000)',
|
|
51
|
+
' totem run "prompt" [options] run a prompt without the interface',
|
|
52
|
+
' totem config print the folders that Totem uses',
|
|
53
|
+
'',
|
|
54
|
+
'Options of run:',
|
|
55
|
+
' --project <folder> the project (default: the current folder)',
|
|
56
|
+
' --model <id> provider:model, as listed in Settings',
|
|
57
|
+
' --url <address> a running server (default: a temporary local server)',
|
|
58
|
+
' --token <token> the access token of the server, when it has one',
|
|
59
|
+
' --auto-accept file edits run without asking',
|
|
60
|
+
' --yes every other question is answered "allow once"',
|
|
61
|
+
'',
|
|
62
|
+
'Options of serve and run:',
|
|
63
|
+
' --data <folder> where the data goes (installed from npm: a folder of your user)'
|
|
64
|
+
].join('\n'));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function options(args) {
|
|
68
|
+
var out = { _: [] };
|
|
69
|
+
for (var i = 0; i < args.length; i++) {
|
|
70
|
+
if (args[i].indexOf('--') === 0) {
|
|
71
|
+
var key = args[i].substring(2);
|
|
72
|
+
if (key === 'yes' || key === 'auto-accept' || key === 'help')
|
|
73
|
+
out[key] = true;
|
|
74
|
+
else
|
|
75
|
+
out[key] = args[++i];
|
|
76
|
+
} else
|
|
77
|
+
out._.push(args[i]);
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function request(url, token, schema, data) {
|
|
83
|
+
return new Promise(function(resolve, reject) {
|
|
84
|
+
var u = new URL('/api/', url);
|
|
85
|
+
var body = JSON.stringify({ schema: schema, data: data });
|
|
86
|
+
var headers = { 'content-type': 'application/json', 'content-length': Buffer.byteLength(body) };
|
|
87
|
+
if (token)
|
|
88
|
+
headers['x-token'] = token;
|
|
89
|
+
var req = Http.request({ hostname: u.hostname, port: u.port, path: u.pathname, method: 'POST', headers: headers }, function(res) {
|
|
90
|
+
var text = '';
|
|
91
|
+
res.on('data', chunk => text += chunk);
|
|
92
|
+
res.on('end', function() {
|
|
93
|
+
var json;
|
|
94
|
+
try {
|
|
95
|
+
json = JSON.parse(text);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
reject(new Error('Unexpected answer from the server (' + res.statusCode + ')'));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (Array.isArray(json) && json[0] && json[0].error)
|
|
101
|
+
reject(new Error(json[0].error));
|
|
102
|
+
else
|
|
103
|
+
resolve(json);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
req.on('error', reject);
|
|
107
|
+
req.end(body);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function freeport() {
|
|
112
|
+
return new Promise(function(resolve) {
|
|
113
|
+
var server = Net.createServer();
|
|
114
|
+
server.listen(0, '127.0.0.1', function() {
|
|
115
|
+
var port = server.address().port;
|
|
116
|
+
server.close(() => resolve(port));
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function serve(port, quiet, data) {
|
|
122
|
+
var child = Child.spawn(process.execPath, [Path.join(ROOT, 'index.js'), String(port)].concat(data ? ['--data', data] : []), { cwd: ROOT, stdio: quiet ? 'ignore' : 'inherit', detached: true });
|
|
123
|
+
var stop = function() {
|
|
124
|
+
try {
|
|
125
|
+
process.kill(-child.pid);
|
|
126
|
+
} catch (e) {}
|
|
127
|
+
};
|
|
128
|
+
process.on('exit', stop);
|
|
129
|
+
process.on('SIGINT', () => process.exit(130));
|
|
130
|
+
process.on('SIGTERM', () => process.exit(143));
|
|
131
|
+
return { child: child, stop: stop };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function waitfor(url, token) {
|
|
135
|
+
for (var i = 0; i < 100; i++) {
|
|
136
|
+
try {
|
|
137
|
+
await request(url, token, 'account');
|
|
138
|
+
return true;
|
|
139
|
+
} catch (e) {
|
|
140
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function open(url) {
|
|
147
|
+
var command = process.platform === 'darwin' ? 'open' : (process.platform === 'win32' ? 'cmd' : 'xdg-open');
|
|
148
|
+
var args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
149
|
+
Child.spawn(command, args, { stdio: 'ignore', detached: true }).on('error', () => {}).unref();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function run(opt) {
|
|
153
|
+
|
|
154
|
+
var prompt = opt._.slice(1).join(' ');
|
|
155
|
+
if (!prompt) {
|
|
156
|
+
console.error('Missing prompt. Usage: totem run "prompt"');
|
|
157
|
+
return 2;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
var url = opt.url;
|
|
161
|
+
var token = opt.token || '';
|
|
162
|
+
var server = null;
|
|
163
|
+
|
|
164
|
+
if (!url) {
|
|
165
|
+
var port = await freeport();
|
|
166
|
+
url = 'http://127.0.0.1:' + port;
|
|
167
|
+
server = serve(port, true, datafolder(opt));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!await waitfor(url, token)) {
|
|
171
|
+
console.error('The server does not answer at ' + url);
|
|
172
|
+
server && server.stop();
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
var status = 1;
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
|
|
180
|
+
var project = await request(url, token, 'projects_open', { path: Path.resolve(opt.project || process.cwd()) });
|
|
181
|
+
var models = await request(url, token, 'providers_models');
|
|
182
|
+
var model = opt.model || models[0];
|
|
183
|
+
|
|
184
|
+
if (!model) {
|
|
185
|
+
console.error('No model. Add a provider in the interface (Settings) or pass --model.');
|
|
186
|
+
return 1;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
var session = await request(url, token, 'sessions_create', { projectid: project.id, model: model, autoaccept: !!opt['auto-accept'] });
|
|
190
|
+
var types = {};
|
|
191
|
+
|
|
192
|
+
status = await new Promise(function(resolve) {
|
|
193
|
+
|
|
194
|
+
var ws = new WebSocket(url.replace(/^http/, 'ws') + '/api/', { headers: token ? { 'x-token': token } : {} });
|
|
195
|
+
var line = false;
|
|
196
|
+
|
|
197
|
+
var finish = code => {
|
|
198
|
+
if (line)
|
|
199
|
+
process.stdout.write('\n');
|
|
200
|
+
resolve(code);
|
|
201
|
+
try {
|
|
202
|
+
ws.close();
|
|
203
|
+
} catch (e) {}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
ws.onerror = () => {
|
|
207
|
+
console.error('The event connection failed');
|
|
208
|
+
finish(1);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
ws.onopen = function() {
|
|
212
|
+
ws.send(JSON.stringify({ type: 'subscribe', sessionid: session.id }));
|
|
213
|
+
setTimeout(() => request(url, token, 'sessions_prompt/' + session.id, { text: prompt }).catch(function(err) {
|
|
214
|
+
console.error(err.message);
|
|
215
|
+
finish(1);
|
|
216
|
+
}), 100);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
ws.onmessage = function(e) {
|
|
220
|
+
|
|
221
|
+
var msg = JSON.parse(e.data);
|
|
222
|
+
var data = msg.data;
|
|
223
|
+
|
|
224
|
+
if (msg.sessionid !== session.id)
|
|
225
|
+
return;
|
|
226
|
+
|
|
227
|
+
if (msg.type === 'part.created')
|
|
228
|
+
types[data.part.id] = data.part.type;
|
|
229
|
+
|
|
230
|
+
if (msg.type === 'part.delta' && types[data.partid] === 'text') {
|
|
231
|
+
process.stdout.write(data.text);
|
|
232
|
+
line = true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (msg.type === 'part.updated' && data.part.type === 'tool' && (data.part.data.status === 'done' || data.part.data.status === 'error' || data.part.data.status === 'denied')) {
|
|
236
|
+
console.error((line ? '\n' : '') + '[' + data.part.data.tool + '] ' + data.part.data.status + ' ' + JSON.stringify(data.part.data.input).substring(0, 100));
|
|
237
|
+
line = false;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (msg.type === 'permission.ask') {
|
|
241
|
+
var allow = opt.yes === true;
|
|
242
|
+
console.error((line ? '\n' : '') + '[permission] ' + data.tool + ' ' + (data.patterns[0] || '') + ' -> ' + (allow ? 'allowed once' : 'rejected (use --yes or --auto-accept)'));
|
|
243
|
+
line = false;
|
|
244
|
+
request(url, token, 'permissions_reply/' + data.id, { reply: allow ? 'once' : 'reject' }).catch(() => {});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (msg.type === 'question.ask') {
|
|
248
|
+
console.error('[question] ' + data.text + ' -> no user available');
|
|
249
|
+
request(url, token, 'questions_reply/' + data.id, { answer: '(no user is available to answer: decide yourself)' }).catch(() => {});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (msg.type === 'error')
|
|
253
|
+
console.error((line ? '\n' : '') + 'error: ' + data.message + (data.retry ? ' (retrying)' : ''));
|
|
254
|
+
|
|
255
|
+
if (msg.type === 'session.status' && data.status !== 'running')
|
|
256
|
+
finish(data.status === 'idle' ? 0 : 1);
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
} catch (err) {
|
|
261
|
+
console.error(err.message);
|
|
262
|
+
status = 1;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
server && server.stop();
|
|
266
|
+
return status;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function main() {
|
|
270
|
+
|
|
271
|
+
var args = process.argv.slice(2);
|
|
272
|
+
var opt = options(args);
|
|
273
|
+
var command = opt._[0];
|
|
274
|
+
|
|
275
|
+
if (opt.help || command === 'help' || args.indexOf('-h') !== -1) {
|
|
276
|
+
usage();
|
|
277
|
+
return 0;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (command === 'config') {
|
|
281
|
+
console.log('Application: ' + ROOT);
|
|
282
|
+
console.log('Config file: ' + Path.join(ROOT, 'config'));
|
|
283
|
+
console.log('Data folder: ' + (datafolder(opt) || Path.join(ROOT, 'databases')));
|
|
284
|
+
return 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (command === 'run')
|
|
288
|
+
return await run(opt);
|
|
289
|
+
|
|
290
|
+
if (command === 'serve' || !command) {
|
|
291
|
+
var port = parseInt(opt._[1] || '8000');
|
|
292
|
+
var server = serve(port, false, datafolder(opt));
|
|
293
|
+
if (!command) {
|
|
294
|
+
var url = 'http://127.0.0.1:' + port + '/';
|
|
295
|
+
if (await waitfor(url))
|
|
296
|
+
open(url);
|
|
297
|
+
}
|
|
298
|
+
await new Promise(resolve => server.child.on('exit', resolve));
|
|
299
|
+
return 0;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
console.error('Unknown command: ' + command);
|
|
303
|
+
usage();
|
|
304
|
+
return 2;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (require.main === module) {
|
|
308
|
+
main().then(code => process.exit(code), function(err) {
|
|
309
|
+
console.error(err.message);
|
|
310
|
+
process.exit(1);
|
|
311
|
+
});
|
|
312
|
+
}
|
package/config
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
name : Total.js Totem
|
|
2
|
+
version : 0.1.0
|
|
3
|
+
$api : /api/
|
|
4
|
+
|
|
5
|
+
// The address the server listens on. Only this computer by default; anything else (0.0.0.0 in the Docker image) needs `token`, and the server refuses to start without one
|
|
6
|
+
$ip : 127.0.0.1
|
|
7
|
+
$tmsmaxsize : 0
|
|
8
|
+
|
|
9
|
+
// Totem as an MCP server (POST /$mcp/): other agents can list projects and sessions and send prompts. Off by default. With a token it asks for
|
|
10
|
+
// `Authorization: Bearer <token>`; without one only programs of this computer (localhost) are accepted, never a web page.
|
|
11
|
+
$mcp : false
|
|
12
|
+
|
|
13
|
+
// A random secret is generated and saved in the data folder on first start when empty
|
|
14
|
+
secret :
|
|
15
|
+
|
|
16
|
+
// When set, every request must send it (header x-token or the cookie created by /auth/)
|
|
17
|
+
token :
|
|
18
|
+
|
|
19
|
+
// A file with the access token (a Docker secret), read when `token` is empty
|
|
20
|
+
tokenfile :
|
|
21
|
+
|
|
22
|
+
// Temperature for the models that run on this computer (a low one keeps a small model to the instructions). Remote providers use their own.
|
|
23
|
+
temperature (Number) : 0.2
|
|
24
|
+
|
|
25
|
+
// Ollama is asked for a context of at least this many tokens, and for more when the request needs it, up to the second number or the window of the
|
|
26
|
+
// model. Its default context is 2048 or 4096 tokens, which cuts the instructions of Totem.
|
|
27
|
+
ollamactx (Number) : 16384
|
|
28
|
+
ollamactxmax (Number) : 32768
|
|
29
|
+
|
|
30
|
+
// A small model can fall into a loop and repeat a paragraph until the context is full. Ollama is asked to penalize repeating (1 is off) and to stop one
|
|
31
|
+
// answer after this many tokens (0 is no limit).
|
|
32
|
+
ollamarepeat (Number) : 1.1
|
|
33
|
+
ollamapredict (Number) : 8192
|
|
34
|
+
|
|
35
|
+
// Agent limits
|
|
36
|
+
maxsteps (Number) : 50
|
|
37
|
+
bashtimeout (Number) : 120000
|
|
38
|
+
maxoutput (Number) : 30000
|
|
39
|
+
contextlimit (Number) : 100000
|
|
40
|
+
retries (Number) : 3
|
|
41
|
+
retrybase (Number) : 1000
|
|
42
|
+
|
|
43
|
+
// Totem writes Total.js unless the project is clearly something else: auto, always or never
|
|
44
|
+
totaljs : auto
|
|
45
|
+
|
|
46
|
+
// The knowledge that Totem brings to Total.js 5 projects: the documentation of Forge (asks before it starts) and the check of the conventions
|
|
47
|
+
forge : true
|
|
48
|
+
// How many pages of Forge are read for a prompt, and how many characters of each
|
|
49
|
+
// The recipes of Totem (private/recipes) that go in front of the model for a request about Total.js 5: how many at most, and false to turn them off
|
|
50
|
+
recipes : true
|
|
51
|
+
|
|
52
|
+
// The code in the answer of the model is checked against the conventions (syntax, style, invented functions) and the model corrects it once. false turns it off.
|
|
53
|
+
selfcheck : true
|
|
54
|
+
reciperesults (Number) : 2
|
|
55
|
+
forgepages (Number) : 2
|
|
56
|
+
forgechars (Number) : 3500
|
|
57
|
+
conventions : true
|
|
58
|
+
|
|
59
|
+
// Run the formatter of the project (a local prettier, gofmt, rustfmt, ruff or .totem/formatters.json) after a file is written
|
|
60
|
+
formatter : true
|
|
61
|
+
|
|
62
|
+
// Run the checks of the project (node --check, a local tsc, go vet, cargo check, ruff or .totem/diagnostics.json) after a file is written and tell the model what is wrong
|
|
63
|
+
diagnostics : true
|
|
64
|
+
|
|
65
|
+
// The shell of the interactive terminal (empty: the SHELL of the user, or /bin/sh)
|
|
66
|
+
terminalshell :
|