totopo 3.7.0-rc-1 → 3.7.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
@@ -127,27 +127,26 @@ profiles:
127
127
  description: "Base image: Node.js, git, and AI CLIs"
128
128
  dockerfile_hook: |
129
129
  # No extras — uses the totopo base image as-is (Node.js + git + AI CLIs).
130
- extended:
131
- description: Base image + Go, Java, Rust, and Bun
132
- dockerfile_hook: |
133
- # Go
134
- RUN apt-get update && apt-get install -y --no-install-recommends golang-go && rm -rf /var/lib/apt/lists/*
135
- # Java (headless JDK — includes javac; needed for Kotlin, Scala, Android tooling)
136
- RUN apt-get update && apt-get install -y --no-install-recommends default-jdk-headless && rm -rf /var/lib/apt/lists/*
137
- # Rust (system-wide install — devuser can use cargo and rustc)
138
- ENV RUSTUP_HOME=/usr/local/rustup
139
- ENV CARGO_HOME=/usr/local/cargo
140
- ENV PATH=/usr/local/cargo/bin:$PATH
141
- RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path && chmod -R a+rx /usr/local/cargo /usr/local/rustup
142
- # Bun (fast JS runtime, bundler, and package manager)
143
- ENV BUN_INSTALL=/usr/local/bun
144
- ENV PATH=/usr/local/bun/bin:$PATH
145
- RUN curl -fsSL https://bun.sh/install | bash
130
+
131
+ # Uncomment to enable additional runtimes (Go, Java, Rust, Bun):
132
+ # extended:
133
+ # description: Base image + Go, Java, Rust, and Bun
134
+ # dockerfile_hook: |
135
+ # # Go
136
+ # RUN apt-get update && apt-get install -y --no-install-recommends golang-go && rm -rf /var/lib/apt/lists/*
137
+ # # Java (headless JDK)
138
+ # RUN apt-get update && apt-get install -y --no-install-recommends default-jdk-headless && rm -rf /var/lib/apt/lists/*
139
+ # # Rust (system-wide)
140
+ # ENV RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo PATH=/usr/local/cargo/bin:$PATH
141
+ # RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path && chmod -R a+rx /usr/local/cargo /usr/local/rustup
142
+ # # Bun
143
+ # ENV BUN_INSTALL=/usr/local/bun PATH=/usr/local/bun/bin:$PATH
144
+ # RUN curl -fsSL https://bun.sh/install | bash
146
145
  # Add more profiles here — or ask the agent inside the container to set one up for you.
147
146
 
148
147
  ```
149
148
 
150
- Two profiles are set by default. When multiple profiles are defined, totopo prompts you to pick one at session start (the choice is remembered). A profile change triggers a container rebuild on the next session.
149
+ New workspaces ship with the `default` profile active and an `extended` profile (Go, Java, Rust, Bun) included as a commented-out template — uncomment to enable. When multiple profiles are defined, totopo prompts you to pick one at session start (the choice is remembered). A profile change triggers a container rebuild on the next session.
151
150
 
152
151
  The base image is defined in [`templates/Dockerfile`](templates/Dockerfile) — inspect it to see what's already included before adding your own layers. To force a fully fresh build (no Docker layer cache), use **Manage Workspace > Clean rebuild**.
153
152
 
@@ -103,7 +103,10 @@ function stopAndRemoveContainer(containerName) {
103
103
  }
104
104
  // --- Run startup checks (AI CLI update + readiness validation) ---------------------------------------------------------------------------
105
105
  function runStartup(containerName, quiet) {
106
- const result = spawnSync("docker", ["exec", "-u", "root", containerName, "node", CONTAINER_STARTUP], {
106
+ // The SPACE-to-skip prompt in startup.mjs needs raw-mode stdin (-i) and a PTY (-t).
107
+ // Omitted when quiet so test output stays pipe-capturable.
108
+ const ttyFlags = quiet ? [] : ["-i", "-t"];
109
+ const result = spawnSync("docker", ["exec", "-u", "root", ...ttyFlags, containerName, "node", CONTAINER_STARTUP], {
107
110
  stdio: quiet ? "pipe" : "inherit",
108
111
  });
109
112
  return result.status === 0;
@@ -121,7 +121,7 @@ export async function run(cwd) {
121
121
  const workspaceId = deriveUniqueWorkspaceId(inputId, workspaceRoot);
122
122
  // Build and write totopo.yaml
123
123
  yaml = buildDefaultTotopoYaml(workspaceId);
124
- writeTotopoYaml(workspaceRoot, yaml);
124
+ writeTotopoYaml(workspaceRoot, yaml, { includeExtendedTemplate: true });
125
125
  log.success(`Created ${toTildePath(join(workspaceRoot, TOTOPO_YAML))}`);
126
126
  }
127
127
  // --- Non-git warning -----------------------------------------------------------------------------------------------------------------
@@ -197,7 +197,7 @@ async function resetTotopoYaml(ctx) {
197
197
  if (isCancel(confirmed) || !confirmed)
198
198
  return;
199
199
  const freshYaml = buildDefaultTotopoYaml(yaml.workspace_id);
200
- writeTotopoYaml(ctx.workspaceRoot, freshYaml);
200
+ writeTotopoYaml(ctx.workspaceRoot, freshYaml, { includeExtendedTemplate: true });
201
201
  log.success("totopo.yaml reset to defaults.");
202
202
  await promptStopContainer(ctx);
203
203
  }
@@ -119,7 +119,7 @@ function migrateSingleV2Workspace(v2, existingIds) {
119
119
  if (v2.shadowPaths.length > 0) {
120
120
  yaml.shadow_paths = [...new Set([...(yaml.shadow_paths ?? []), ...v2.shadowPaths])];
121
121
  }
122
- writeTotopoYaml(v2.projectRoot, yaml);
122
+ writeTotopoYaml(v2.projectRoot, yaml, { includeExtendedTemplate: true });
123
123
  log.info(`Created totopo.yaml for "${v2.displayName}" (workspace_id: ${workspaceId})`);
124
124
  }
125
125
  const newDir = join(getWorkspacesBaseDir(), workspaceId);
@@ -89,7 +89,7 @@ const YAML_COMMENTS = {
89
89
  "# When multiple profiles exist, totopo prompts you to pick one on session start.",
90
90
  };
91
91
  /** Write totopo.yaml to a directory with schema header and inline comments. */
92
- export function writeTotopoYaml(dir, config) {
92
+ export function writeTotopoYaml(dir, config, opts = {}) {
93
93
  const filePath = join(dir, TOTOPO_YAML);
94
94
  const yamlContent = dumpYaml(config, {
95
95
  lineWidth: -1,
@@ -109,11 +109,14 @@ export function writeTotopoYaml(dir, config) {
109
109
  output.push(line);
110
110
  }
111
111
  const body = output.join("\n").trimEnd();
112
- writeFileSync(filePath, `${body}\n${PROFILES_FOOTER_COMMENT}\n`);
112
+ const extendedBlock = opts.includeExtendedTemplate ? `\n\n${EXTENDED_PROFILE_TEMPLATE_PROMPT}\n${renderExtendedAsCommented()}` : "";
113
+ writeFileSync(filePath, `${body}${extendedBlock}\n${PROFILES_FOOTER_COMMENT}\n`);
113
114
  }
114
115
  // --- Defaults ----------------------------------------------------------------------------------------------------------------------------
116
+ const DEFAULT_PROFILE_DESCRIPTION = "Base image: Node.js, git, and AI CLIs";
115
117
  const DEFAULT_PROFILE_HOOK = `# No extras — uses the totopo base image as-is (Node.js + git + AI CLIs).
116
118
  `;
119
+ const EXTENDED_PROFILE_DESCRIPTION = "Base image + Go, Java, Rust, and Bun";
117
120
  const EXTENDED_PROFILE_HOOK = `# Go
118
121
  RUN apt-get update && apt-get install -y --no-install-recommends golang-go && rm -rf /var/lib/apt/lists/*
119
122
 
@@ -133,6 +136,20 @@ RUN curl -fsSL https://bun.sh/install | bash
133
136
  `;
134
137
  // Appended after the last profile to hint at adding more
135
138
  const PROFILES_FOOTER_COMMENT = " # Add more profiles here — or ask the agent inside the container to set one up for you.";
139
+ const EXTENDED_PROFILE_TEMPLATE_PROMPT = " # Uncomment to enable additional runtimes (Go, Java, Rust, Bun):";
140
+ /**
141
+ * Render the `extended` profile as commented-out YAML, indented to live under `profiles:`.
142
+ * Generated via the same dumpYaml call as the live config so uncommenting (strip leading `# `
143
+ * from each line) yields YAML the parser/schema accepts without drift.
144
+ */
145
+ function renderExtendedAsCommented() {
146
+ const dumped = dumpYaml({ extended: { description: EXTENDED_PROFILE_DESCRIPTION, dockerfile_hook: EXTENDED_PROFILE_HOOK } }, { lineWidth: -1, quotingType: '"', forceQuotes: false });
147
+ return dumped
148
+ .split("\n")
149
+ .map((line) => (line.length === 0 ? "" : ` # ${line}`))
150
+ .join("\n")
151
+ .trimEnd();
152
+ }
136
153
  /** Create a default TotopoYamlConfig with sane defaults. */
137
154
  export function buildDefaultTotopoYaml(workspaceId) {
138
155
  return {
@@ -141,13 +158,9 @@ export function buildDefaultTotopoYaml(workspaceId) {
141
158
  shadow_paths: [...DEFAULT_SHADOW_PATHS],
142
159
  profiles: {
143
160
  default: {
144
- description: "Base image: Node.js, git, and AI CLIs",
161
+ description: DEFAULT_PROFILE_DESCRIPTION,
145
162
  dockerfile_hook: DEFAULT_PROFILE_HOOK,
146
163
  },
147
- extended: {
148
- description: "Base image + Go, Java, Rust, and Bun",
149
- dockerfile_hook: EXTENDED_PROFILE_HOOK,
150
- },
151
164
  },
152
165
  };
153
166
  }
@@ -201,7 +214,7 @@ export function repairTotopoYaml(dir) {
201
214
  return { repairedYaml: null, error: `${TOTOPO_YAML} could not be repaired: ${formatValidationErrors(validate.errors)}` };
202
215
  }
203
216
  const yaml = obj;
204
- writeTotopoYaml(dir, yaml);
217
+ writeTotopoYaml(dir, yaml, { includeExtendedTemplate: fixes.includes("added default profiles") });
205
218
  return { repairedYaml: yaml, message: `Repaired ${TOTOPO_YAML}: ${fixes.join(", ")}` };
206
219
  }
207
220
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "totopo",
3
- "version": "3.7.0-rc-1",
3
+ "version": "3.7.0",
4
4
  "description": "Run AI coding agents safely in your local codebase",
5
5
  "type": "module",
6
6
  "bin": {
@@ -45,8 +45,10 @@ function removeWrapperIfPresent() {
45
45
  }
46
46
 
47
47
  function applyAsRoot(gitMode, protocolValue, fail) {
48
+ // Use absolute path to bypass /usr/local/bin/git (which may already be the read-only
49
+ // wrapper from a prior strict-mode session and would block this legitimate setup write).
48
50
  try {
49
- execSync(`git config --system protocol.allow ${protocolValue}`, { stdio: "pipe" });
51
+ execSync(`/usr/bin/git config --system protocol.allow ${protocolValue}`, { stdio: "pipe" });
50
52
  } catch {
51
53
  fail("git mode", `failed to set protocol.allow=${protocolValue}`);
52
54
  }