totopo 1.0.6 → 1.0.7

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  <img src=".github/assets/logo.png" alt="totopo" width="100%" />
4
4
 
5
- A simple CLI to spin up a sandboxed environment for AI coding agents.
5
+ A simple CLI to use AI coding agents safely in your local codebase.
6
6
 
7
7
  ![npm version](https://img.shields.io/npm/v/totopo)
8
8
  ![npm downloads](https://img.shields.io/npm/dm/totopo)
@@ -12,14 +12,26 @@ A simple CLI to spin up a sandboxed environment for AI coding agents.
12
12
 
13
13
  Here's the thing about AI agents: they're probabilistic. They occasionally misinterpret instructions, take unexpected shortcuts, or simply get it wrong. Most of the time they're fine. But "most of the time" isn't a great argument for giving them unrestricted access to your machine, your credentials, and your remote repositories.
14
14
 
15
- totopo draws a simple boundary: agents get a full, capable environment to work in — they just can't touch anything outside the project, and they can't reach your remote. That's it. No domain whitelisting, no paranoia, no compromise on what the agent can actually do.
16
- Reasonable containment for non-deterministic tools. Nothing more, nothing less.
15
+ totopo draws a simple boundary: agents get a full, capable environment to work in — they just can't touch anything outside the project, and they can't reach your remote. That's it. No domain whitelisting, no paranoia, no compromise on what the agent can actually do. Just a reasonable containment for non-deterministic tools.
17
16
 
18
17
  Note: no sandbox substitutes for good judgment. Consider keeping any sensitive secrets or privileged scripts away from your agents.
19
18
 
19
+ ## How totopo Works
20
+
21
+ - Unified, simple DX: run `npx totopo` from anywhere inside a local git repo.
22
+ - Installed once per git repository in a `.totopo/` directory at the repo root.
23
+ - Manages one Docker container per repository.
24
+
25
+ To start a development session, run `npx totopo`, choose `Open session` and confirm the desired scope: the full repo, the current directory, or selected files and folders.
26
+
27
+ ### Concurrent Sessions
28
+ totopo uses one container per repository (not per session). This keeps resource usage bounded and makes reconnections fast - you can open as many sessions as you need — they all share the same running container.
29
+
30
+ The tradeoff is that only one scope can be active at a time: if you reopen a session with a different scope, totopo recreates the container to match the new mounts, which would terminate any active sessions connected to the previous container.
31
+
20
32
  ## Requirements
21
33
 
22
- - [Docker](https://www.docker.com/products/docker-desktop/) - used to build and run the sandboxed environment
34
+ - [Docker](https://www.docker.com/products/docker-desktop/) - used to build and run the dev container
23
35
  - [git](https://git-scm.com/) - safeguard to ensure agents only run in projects with version control in place
24
36
 
25
37
  ## Quick Start
@@ -32,47 +44,57 @@ npx totopo
32
44
  First-time setup — running `npx totopo` in a fresh repo, selecting a runtime mode, and waiting for the Docker image to build for the first time:
33
45
  ![First-time setup](.github/assets/demo-onboarding.gif)
34
46
 
35
- Opening a session when totopo is already initialized is quick. The agent is aware of the sandbox environment:
47
+ Opening a session when totopo is already initialized is quick. The agent is aware of its scope and sandbox constraints:
36
48
  ![Quick start](.github/assets/demo-quickstart.gif)
37
49
 
38
50
  ## Core features at a glance
39
51
 
40
- - **Sandboxed Docker container** — your code runs in an isolated environment with strict filesystem and privilege boundaries
52
+ - **Docker isolation** — AI agents run in a container with strict filesystem and privilege boundaries
41
53
  - **Agents can't reach remote** — push, pull, fetch, and clone are blocked inside the container, preventing agents from accidentally affecting your remote repositories
42
- - **AI CLIs with persistent sessions** — OpenCode, Claude Code and Codex pre-installed, with conversation history that survives restarts and rebuilds
43
- - **Host-mirror or generic runtime** — use a standard dev container, or let totopo match the container environment to your host so the agent works in the exact same setup as your codebase
44
- - **Agents are sandbox aware** — agents are informed of their sandbox constraints at session start, so they can factor that into how they work.
45
- - **Scoped mounts** — expose only the files and directories the agent needs
46
-
47
- ---
54
+ - **AI CLIs with persistent sessions** — OpenCode, Claude Code, and Codex are pre-installed, with conversation history that survives restarts and rebuilds
55
+ - **Host-mirror or full runtime** — either match the container environment to your host, or use a standard dev container with the latest stable tools
56
+ - **Agents are scope-aware** — agents are informed of the mounted files and constraints at session start, so they can factor that into how they work
57
+ - **Scoped access** — expose only the files and directories the agent needs
48
58
 
49
59
  ## Features in Detail
50
60
 
51
- ### Sandboxed dev container
61
+ ### Container isolation
52
62
 
53
- Every session runs inside a Docker container. Your code is bind-mounted from the host edits are immediately visible in your editor. The container enforces several isolation boundaries:
63
+ Every session runs inside a Docker container. Your code is bind-mounted from the host, so edits are immediately visible in your editor. The container enforces several isolation boundaries:
54
64
 
55
65
  | Control | Implementation |
56
66
  | --- | --- |
57
- | Non-root user | All processes run as `devuser` (uid 1001) cannot modify system-level config |
58
- | Filesystem isolation | Only the repo is mounted host filesystem is not visible |
59
- | Git remote block | `protocol.allow = never` in `/etc/gitconfig` — push, pull, fetch, and clone are all refused; requires root to override |
67
+ | Non-root user | All processes run as `devuser` (uid 1001) and cannot modify system-level config |
68
+ | Filesystem isolation | Only the selected project paths are mounted; the rest of the host filesystem is not visible |
69
+ | Git remote block | `protocol.allow = never` in `/etc/gitconfig` — push, pull, fetch, and clone are all refused and require root to override |
60
70
  | No host credentials forwarded | Host git credentials are never copied into the container |
61
- | Secrets never in image | API keys loaded at runtime from `~/.totopo/.env` — never baked into the image, never mounted into the container |
71
+ | Secrets never in image | API keys are loaded at runtime from `~/.totopo/.env` — never baked into the image, never mounted into the container |
62
72
  | No privilege escalation | `no-new-privileges:true` prevents any process from gaining elevated permissions |
63
73
 
64
74
  Remote git operations are blocked inside the container. Push from your host terminal instead.
65
75
 
66
- ### Scoped sandboxing
76
+ ### Session scope
67
77
 
68
- Mount only the files and directories you need into the container rather than the full repository. Two scoped modes are available: `cwd` (current directory only) and `selective` (hand-pick individual files and folders).
78
+ When you open a session, totopo asks what part of the repository to mount into the container:
69
79
 
70
- In both scoped modes, `.git` is intentionally not mounted. Mounting `.git` would expose the full commit history of every repository file — including files outside the mounted paths — which defeats the point of scoped access. As a result, git is unavailable inside a scoped session and the agent operates without repository history. The agent is instructed to surface these limitations at session start.
80
+ - `Repo root` the full repository
81
+ - `Current directory` — only the current directory
82
+ - `Selective` — specific files and folders chosen interactively
83
+
84
+ The selected scope is stored on the container and checked on every later `Open session`.
85
+
86
+ - If the requested scope matches the existing container, totopo connects directly to it if running, or resumes it if stopped.
87
+ - If the requested scope is different, totopo recreates the container so the mounted paths match the new scope.
88
+ - Parallel terminals on the same scope are fine. totopo connects with `docker exec`, so any concurrency limits are just the normal limits of sharing one running container.
89
+
90
+ This is an intentional tradeoff: you get predictable resource usage and quick reconnects, but only one active mounted view per repository at a time.
91
+
92
+ In `Current directory` and `Selective` scopes, `.git` is intentionally not mounted. Mounting `.git` would expose the full commit history of every repository file, including files outside the mounted paths, which defeats the point of scoped access. As a result, git is unavailable inside those scoped sessions and the agent operates without repository history. The agent is instructed to surface these limitations at session start.
71
93
 
72
94
  Scoped sessions are well-suited for focused tasks where you want to give the agent a narrow, explicit view of your codebase.
73
95
 
74
- Example showcasing agent awareness of scope limitations:
75
- ![Scoped sandboxing](.github/assets/demo-scoped.gif)
96
+ Example showcasing agent awareness of selective scope limitations:
97
+ ![Scoped access](.github/assets/demo-scoped.gif)
76
98
 
77
99
  ### AI CLIs with persistent sessions
78
100
 
@@ -84,20 +106,20 @@ claude # Claude Code (Anthropic)
84
106
  codex # Codex (OpenAI)
85
107
  ```
86
108
 
87
- Agent session data is scoped per project — each repository gets its own isolated history, so agents don't bleed context between projects. To clear memory, run `npx totopo` and navigate to Advanced > Clear agent memory. This stops the container if running and removes the .totopo/agents/ directory.
109
+ Agent session data is isolated per repository, so agents do not bleed context between projects. To clear memory, run `npx totopo` and navigate to `Advanced > Clear agent memory`. This stops the container if running and removes the `.totopo/agents/` directory.
88
110
 
89
111
  ### Dev container runtime
90
112
 
91
113
  Choose between two modes:
92
114
 
93
115
  - **Host-mirror** — the container runtime matches your host Node.js version and selected tools, keeping the environment consistent with your local setup.
94
- - **Generic** — a full dev container with the latest stable versions of all tools. Good default if you don't need version parity with your host.
116
+ - **Full** — a full dev container with the latest stable versions of all tools. Good default if you do not need version parity with your host.
95
117
 
96
118
  Either way, basic dev tools and all three AI CLIs are always included.
97
119
 
98
120
  ## What gets created in your project
99
121
 
100
- ```
122
+ ```text
101
123
  your-project/
102
124
  └── .totopo/
103
125
  ├── Dockerfile # container image definition
@@ -105,14 +127,14 @@ your-project/
105
127
  ├── settings.json # runtime mode + selected tools (committed with project)
106
128
  ├── README.md # .totopo reference
107
129
  └── agents/ # agent session data — gitignored, created on first session start
108
- ├── claude/ # mounted as ~/.claude/
109
- ├── opencode/ # mounted as ~/.config/opencode/ + ~/.local/share/opencode/
110
- └── codex/ # mounted as ~/.codex/
130
+ ├── claude/ # mounted as ~/.claude/
131
+ ├── opencode/ # mounted as ~/.config/opencode/ + ~/.local/share/opencode/
132
+ └── codex/ # mounted as ~/.codex/
111
133
 
112
134
  ~/.totopo/.env # API keys — global, outside all repos, never mounted into container
113
135
  ```
114
136
 
115
- Agent session history and conversation data are persisted in the `agents` directory across container rebuilds and restarts. This directory is gitignored so session data stays local to your machine.
137
+ totopo is initialized at the repository root, and `.totopo/` lives there regardless of which directory you later open a session from. Agent session history and conversation data are persisted in `.totopo/agents/` across container rebuilds and restarts. This directory is gitignored so session data stays local to your machine.
116
138
 
117
139
  ## Limitations
118
140
 
@@ -120,4 +142,4 @@ Agent session history and conversation data are persisted in the `agents` direct
120
142
 
121
143
  ## Disclaimer
122
144
 
123
- MIT licensed and fully open source. Fork it, adapt it, make it yours. Issues are welcome — no promises on response time. Use at your own risk.
145
+ MIT licensed and fully open source. Fork it, adapt it, make it yours. Issues are welcome — no promises on response time. Use at your own risk.
package/bin/totopo.js CHANGED
@@ -8,6 +8,13 @@ import { execSync, spawnSync } from "node:child_process";
8
8
  import { existsSync } from "node:fs";
9
9
  import { basename, dirname } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
+ import { run as advanced } from "../dist/commands/advanced.js";
12
+ import { run as dev } from "../dist/commands/dev.js";
13
+ import { run as doctor } from "../dist/commands/doctor.js";
14
+ import { run as menu } from "../dist/commands/menu.js";
15
+ import { run as onboard } from "../dist/commands/onboard.js";
16
+ import { run as stop } from "../dist/commands/stop.js";
17
+ import { run as syncDockerfile } from "../dist/commands/sync-dockerfile.js";
11
18
 
12
19
  // ─── Guard: inside container ──────────────────────────────────────────────────
13
20
  try {
@@ -50,15 +57,6 @@ if (!existsSync(new URL("../dist/commands/sync-dockerfile.js", import.meta.url))
50
57
  process.exit(1);
51
58
  }
52
59
 
53
- // ─── Import compiled commands ─────────────────────────────────────────────────
54
- const { run: syncDockerfile } = await import("../dist/commands/sync-dockerfile.js");
55
- const { run: doctor } = await import("../dist/commands/doctor.js");
56
- const { run: onboard } = await import("../dist/commands/onboard.js");
57
- const { run: menu } = await import("../dist/commands/menu.js");
58
- const { run: dev } = await import("../dist/commands/dev.js");
59
- const { run: stop } = await import("../dist/commands/stop.js");
60
- const { run: advanced } = await import("../dist/commands/advanced.js");
61
-
62
60
  // ─── Onboarding ───────────────────────────────────────────────────────────────
63
61
  if (!existsSync(`${repoRoot}/.totopo/Dockerfile`)) {
64
62
  const completed = await onboard(packageDir, repoRoot);
@@ -110,6 +108,7 @@ while (showMenu) {
110
108
  case "advanced": {
111
109
  const result = await advanced(packageDir, projectName, repoRoot);
112
110
  if (result === "back") showMenu = true;
111
+ if (result === "rebuild") await dev(packageDir, repoRoot);
113
112
  break;
114
113
  }
115
114
  default:
@@ -7,6 +7,9 @@ import { cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
7
7
  import { homedir } from "node:os";
8
8
  import { join } from "node:path";
9
9
  import { cancel, confirm, isCancel, log, multiselect, outro, select } from "@clack/prompts";
10
+ import { run as runDoctor } from "./doctor.js";
11
+ import { run as runRebuild } from "./rebuild.js";
12
+ import { run as runSettings } from "./settings.js";
10
13
  // ─── Helpers ──────────────────────────────────────────────────────────────────
11
14
  function stopAndRemoveContainer(name) {
12
15
  spawnSync("docker", ["stop", name], { stdio: "inherit" });
@@ -154,10 +157,6 @@ async function resetApiKeys(packageDir) {
154
157
  }
155
158
  // ─── Advanced submenu ─────────────────────────────────────────────────────────
156
159
  export async function run(packageDir, projectName, repoRoot) {
157
- // Dynamic imports to avoid circular deps — same pattern as bin/totopo.js
158
- const { run: runSettings } = await import("./settings.js");
159
- const { run: runRebuild } = await import("./rebuild.js");
160
- const { run: runDoctor } = await import("./doctor.js");
161
160
  const totopoDir = join(repoRoot, ".totopo");
162
161
  while (true) {
163
162
  const action = await select({
@@ -183,7 +182,7 @@ export async function run(packageDir, projectName, repoRoot) {
183
182
  break;
184
183
  case "rebuild":
185
184
  await runRebuild(projectName);
186
- break;
185
+ return "rebuild";
187
186
  case "clear-memory":
188
187
  await clearAgentMemory(projectName, totopoDir);
189
188
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "totopo",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "Secure AI Box — isolated dev environments for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {