Totem

Delivery

One application, three ways to run it. Nothing is rewritten between them.

Command line#

npm install
node index.js 8000

bin/totem.js (bin of package.json, Node.js 22 or newer) wraps it for users:

CommandDoes
totem [port]Starts the server on port 8000, or the one you give (totem 4524), in the background, shows the banner and the address, opens the browser and gives the terminal back. The process, the port and the log are in totem-server.json and totem.log of the data folder
totem start [port]The same, without opening the browser
totem stop, totem status, totem restartStop the server that totem started, say where it runs, or both
totem serve [port]Runs the server in this terminal until Ctrl+C. --verbose shows the whole output
totem run "prompt" [--project path] [--model name] [--url server] [--token t] [--auto-accept] [--yes]Headless: creates a session, subscribes to its events, sends the prompt, prints the answer on stdout and tool calls on stderr, exits 0 when the run ends idle and 1 otherwise. Without --url it starts a temporary local server. Permission questions are rejected unless --yes (allow once) or --auto-accept (file edits) is given
totem configPrints the path of the config and data folders

Today the data folder is databases/ inside the application folder (totem config prints it). Moving it to a per-user folder is an open decision. A project can carry .totem/ for skills.

Docker#

The image is the application and nothing else (Dockerfile, .dockerignore):

  • Base: the maintained Node.js LTS on Alpine, pinned by digest (Dependabot opens a pull request with the new digest when Node.js publishes a release), plus python3 for the interactive shell and git for the Git panel.
  • Projects are bind-mounted (/workspace), data is a volume (/data, linked to databases/), the access token is a Docker secret (/run/secrets/totem_token, read when token in the config is empty).
  • Runs as the non-root user node, in release mode (--release: without the watcher). The config of the image is the one of the application with $ip : 0.0.0.0 and tokenfile set at build time. The server refuses to start (exit code 1) when it listens beyond this computer without an access token: that rule is in the application, so it also holds outside Docker.
  • Because commands run inside the container, this is also the isolated mode recommended for untrusted models (security).
  • docs/docker-compose.yml is the example for the common case. The registry and the tags per version belong to the release workflow (not built yet).
docker build -t totem .
echo "a-long-random-token" > token.txt
docker run --rm -p 127.0.0.1:8000:8000 -v "$PWD/token.txt:/run/secrets/totem_token:ro" -v "$PWD:/workspace/project" -v totem-data:/data totem

node scripts/docker-smoke.js [image] [folder] is the smoke test: no token means no start; with a token it serves, refuses a request without it, opens the mounted project, lists its files and runs a command in the shell as a user that is not root. CI runs it (.github/workflows/test.yml) next to the unit tests. On Colima and Docker Desktop the folder for the temporary files must be shared with the virtual machine (the home folder, not /tmp).

Native windows#

A native app is a thin shell: it starts the same server on a random local port with a one-time token, loads http://127.0.0.1:<port> in a web view, and stops the server when the window closes. It adds only what a web page cannot do: a folder chooser, menu bar, notifications, file association, auto-start of the server, and update checks against the project's own release feed.

Shell technology is an open decision (decisions). Whatever it is, it must keep these properties:

  • The server inside is the unmodified application (bundled Node.js runtime).
  • macOS builds are signed and notarized; Windows and Linux artifacts are checksummed.
  • The release workflow builds from a tag, per operating system and architecture, and publishes checksums with the installers.

Releases#

  • One version number for the application, the image and the native builds: package.json, the config file and a heading in CHANGELOG.md say the same.
  • A release published on GitHub with the tag v<version> starts .github/workflows/release.yml; nothing publishes from a branch.
  • The workflow checks the release (node scripts/release-check.js v<version>: version, tag, changelog, exact total5, and a README that tells how to install it), runs the unit tests and the smoke test of the image, and then publishes two things:

    • The package totaljs-totem on npm, with trusted publishing and the provenance of the package: no secret. The package ships the bundle of components (npm run bundle), so the installed application does not download anything or write in its own folder. Installed from npm, the data goes to a folder of the user (~/Library/Application Support/Totem, ~/.local/share/totem, %APPDATA%\Totem; totem config prints it, --data <folder> changes it).
    • The image ghcr.io/totaljs-es/totem for linux/amd64 and linux/arm64, tagged latest, <version> and <major>.<minor>. It uses the GITHUB_TOKEN of the repository.
  • Release notes list user-visible changes only.

Releasing#

  1. Change the version in package.json and config, add its heading to CHANGELOG.md, update the README (how to install it, the Status) and wait for the continuous integration to pass.
  2. The first time only, npm needs a way in. A trusted publisher is configured on a package that exists, and totaljs-totem does not exist until its first version is published. Either:

    • add the secret NPM_TOKEN to the repository (or to the organization) with a token that can publish packages: the first release publishes from GitHub, with the provenance of the package; or
    • publish 0.1.0 by hand from your computer: npm run release-check, npm publish --access public.

    Then, in the settings of the package on npmjs.com, add the trusted publisher: GitHub Actions, organization totaljs-es, repository totem, workflow release.yml, no environment. From then on the workflow publishes without a token, and the secret can be deleted.

  3. Create a release on GitHub with the tag v<version>. The workflow skips the version that npm already has (the first one), and publishes the image.
  4. The first image only: the package totem appears in the packages of the organization as private. Make it public in its settings (Change visibility), or docker pull asks for a login.
  5. From then on, every release publishes both without anything by hand.

The website#

https://totaljs-es.github.io/totem/ is made from two places, so that the application and its website stay apart.

  • The branch site has the website: the templates, the styles and images (the look of the Spanish Total.js websites: the styles of the CDN and css/corp.css, the header and the footer of those websites, Toto), the screenshots, and the scripts that build it (scripts/build-site.js), take the screenshots (scripts/screenshots.js) and make the card for the social networks (scripts/og.js). It has no history in common with main, and nothing of it goes in the npm package or in the Docker image.
  • The branch main keeps the text: every page of docs/, the changelog and the version are the pages of the website. They live next to the code that they describe, and the website is built from them.

The workflow .github/workflows/pages.yml of main checks out both branches, runs node scripts/build-site.js --app <main> of the branch site and publishes the result. It runs when the documentation, the README, the changelog or the version change, and when the workflow of the branch site asks for it (a push to that branch). A link, an image or a section that leads nowhere, an icon that the font of the websites does not have, or a placeholder that was not filled stops the build. Set up once: Settings of the repository, Pages, Source: GitHub Actions.

To work on the website: git worktree add ../totem-site site, then node scripts/build-site.js --app ../totem in that folder writes dist/.

Edit this page on GitHub