totopo 1.0.7 → 1.0.8

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/bin/totopo.js CHANGED
@@ -15,6 +15,7 @@ import { run as menu } from "../dist/commands/menu.js";
15
15
  import { run as onboard } from "../dist/commands/onboard.js";
16
16
  import { run as stop } from "../dist/commands/stop.js";
17
17
  import { run as syncDockerfile } from "../dist/commands/sync-dockerfile.js";
18
+ import { toDockerName } from "../dist/lib/docker-name.js";
18
19
 
19
20
  // ─── Guard: inside container ──────────────────────────────────────────────────
20
21
  try {
@@ -76,20 +77,21 @@ if (!doctorResult.ok) {
76
77
 
77
78
  // ─── Gather state for menu ────────────────────────────────────────────────────
78
79
  const projectName = basename(repoRoot);
80
+ const dockerName = toDockerName(projectName);
79
81
 
80
82
  const dockerResult = spawnSync("docker", ["ps", "--filter", "name=totopo-managed-", "--format", "{{.Names}}"], {
81
83
  encoding: "utf8",
82
84
  });
83
85
  const activeCount = dockerResult.stdout ? dockerResult.stdout.trim().split("\n").filter(Boolean).length : 0;
84
86
 
85
- const projectContainerResult = spawnSync("docker", ["ps", "--filter", `name=totopo-managed-${projectName}`, "--format", "{{.Names}}"], {
87
+ const projectContainerResult = spawnSync("docker", ["ps", "--filter", `name=${dockerName}`, "--format", "{{.Names}}"], {
86
88
  encoding: "utf8",
87
89
  });
88
90
  const projectRunning = (projectContainerResult.stdout ?? "")
89
91
  .trim()
90
92
  .split("\n")
91
93
  .filter(Boolean)
92
- .some((n) => n === `totopo-managed-${projectName}`);
94
+ .some((n) => n === dockerName);
93
95
 
94
96
  // ─── Interactive menu loop ────────────────────────────────────────────────────
95
97
  let showMenu = true;
@@ -7,6 +7,7 @@ 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 { toDockerName } from "../lib/docker-name.js";
10
11
  import { run as runDoctor } from "./doctor.js";
11
12
  import { run as runRebuild } from "./rebuild.js";
12
13
  import { run as runSettings } from "./settings.js";
@@ -17,7 +18,7 @@ function stopAndRemoveContainer(name) {
17
18
  }
18
19
  // ─── Clear agent memory ───────────────────────────────────────────────────────
19
20
  async function clearAgentMemory(projectName, totopoDir) {
20
- const containerName = `totopo-managed-${projectName}`;
21
+ const containerName = toDockerName(projectName);
21
22
  // Check if the container is running
22
23
  const inspectResult = spawnSync("docker", ["inspect", "--format", "{{.State.Status}}", containerName], {
23
24
  encoding: "utf8",
@@ -76,7 +77,7 @@ async function stopContainers() {
76
77
  }
77
78
  // ─── Remove images ────────────────────────────────────────────────────────────
78
79
  async function removeImages() {
79
- const listResult = spawnSync("docker", ["images", "--filter", "label=totopo.managed=true", "--format", "{{.Repository}}\t{{.ID}}"], {
80
+ const listResult = spawnSync("docker", ["images", "--filter", "label=totopo.managed=true", "--format", "{{.Repository}} {{.ID}}"], {
80
81
  encoding: "utf8",
81
82
  });
82
83
  const lines = (listResult.stdout ?? "").trim().split("\n").filter(Boolean);
@@ -85,7 +86,7 @@ async function removeImages() {
85
86
  return;
86
87
  }
87
88
  const images = lines.map((line) => {
88
- const [repo, id] = line.split("\t");
89
+ const [repo, id] = line.split(" ");
89
90
  const workspace = (repo ?? "").replace(/^totopo-managed-/, "");
90
91
  return { repo: repo ?? "", id: id ?? "", workspace };
91
92
  });
@@ -118,8 +119,9 @@ async function removeImages() {
118
119
  }
119
120
  // ─── Uninstall ────────────────────────────────────────────────────────────────
120
121
  async function uninstall(projectName, repoRoot) {
121
- const containerName = `totopo-managed-${projectName}`;
122
- const imageName = `totopo-managed-${projectName}`;
122
+ const dockerName = toDockerName(projectName);
123
+ const containerName = dockerName;
124
+ const imageName = dockerName;
123
125
  const confirmed = await confirm({
124
126
  message: `Remove .totopo/, stop containers, and delete the image for ${projectName}?`,
125
127
  });
@@ -191,7 +193,7 @@ export async function run(packageDir, projectName, repoRoot) {
191
193
  break;
192
194
  case "uninstall":
193
195
  await uninstall(projectName, repoRoot);
194
- return; // uninstall tears down .totopo — exit entirely
196
+ return;
195
197
  case "doctor":
196
198
  await runDoctor(repoRoot, true);
197
199
  break;
@@ -7,6 +7,7 @@ import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "nod
7
7
  import { homedir } from "node:os";
8
8
  import { basename, dirname, join, relative } from "node:path";
9
9
  import { cancel, confirm, groupMultiselect, isCancel, log, multiselect, note, outro, path, select } from "@clack/prompts";
10
+ import { toDockerName } from "../lib/docker-name.js";
10
11
  // ─── Prompt: scope selection ──────────────────────────────────────────────────
11
12
  async function promptScope(workspaceDir, totopoDir, cwd) {
12
13
  const cwdIsRepo = cwd === workspaceDir;
@@ -109,7 +110,7 @@ async function promptDeeperPaths(style, cwd) {
109
110
  const accumulated = [];
110
111
  while (true) {
111
112
  const addAnother = await confirm({
112
- message: accumulated.length === 0 ? `Add a nested path to ${verb} by path?` : `Add another nested path to ${verb}?`,
113
+ message: accumulated.length === 0 ? `Add a nested path to ${verb}?` : `Add another nested path to ${verb}?`,
113
114
  initialValue: false,
114
115
  });
115
116
  if (isCancel(addAnother)) {
@@ -153,16 +154,12 @@ async function promptSelectivePaths(totopoDir, cwd) {
153
154
  const style = styleChoice;
154
155
  const { dirs, files } = scanCwdDepth2(totopoDir, cwd);
155
156
  const dirNames = Object.keys(dirs);
156
- if (style === "only") {
157
- log.info("Space to select · Enter to confirm · Skip entirely with Enter to go straight to path input.");
158
- }
159
- else {
160
- log.info("All items pre-selected · Space to deselect · Enter to confirm · Add deeper exclusions in the next step.");
161
- }
157
+ log.warn("This picker shows only two directory levels. Deeper files/dirs can be selected by path in the next step.");
158
+ const selectMessage = `Choose paths (Space to toggle · Enter to continue):`;
162
159
  // ── flat fallback when there are no dirs ──────────────────────────────────
163
160
  if (dirNames.length === 0) {
164
161
  const flatSelected = await multiselect({
165
- message: "Choose paths:",
162
+ message: selectMessage,
166
163
  options: files.map((f) => ({ value: f, label: f })),
167
164
  initialValues: style === "except" ? files : [],
168
165
  required: false,
@@ -187,7 +184,7 @@ async function promptSelectivePaths(totopoDir, cwd) {
187
184
  // "except" → pre-select all depth-2 children + root files
188
185
  const initialValues = style === "except" ? [...Object.values(dirs).flat(), ...files] : [];
189
186
  const rawSelected = await groupMultiselect({
190
- message: "Choose paths:",
187
+ message: selectMessage,
191
188
  options: groupOptions,
192
189
  initialValues,
193
190
  required: false,
@@ -481,8 +478,9 @@ function runContainer(scope, containerName, imageName, workspaceDir, totopoDir,
481
478
  export async function run(_packageDir, repoRoot) {
482
479
  const cwd = process.cwd();
483
480
  const projectName = basename(repoRoot);
484
- const containerName = `totopo-managed-${projectName}`;
485
- const imageName = `totopo-managed-${projectName}`;
481
+ const dockerName = toDockerName(projectName);
482
+ const containerName = dockerName;
483
+ const imageName = dockerName;
486
484
  const totopoDir = join(repoRoot, ".totopo");
487
485
  // ─── Always prompt scope first ────────────────────────────────────────────────
488
486
  const scope = await promptScope(repoRoot, totopoDir, cwd);
@@ -4,9 +4,11 @@
4
4
  // =========================================================================================================================================
5
5
  import { spawnSync } from "node:child_process";
6
6
  import { log } from "@clack/prompts";
7
+ import { toDockerName } from "../lib/docker-name.js";
7
8
  export async function run(projectName) {
8
- const containerName = `totopo-managed-${projectName}`;
9
- const imageName = `totopo-managed-${projectName}`;
9
+ const dockerName = toDockerName(projectName);
10
+ const containerName = dockerName;
11
+ const imageName = dockerName;
10
12
  // ─── Stop container if running ────────────────────────────────────────────────
11
13
  const inspectResult = spawnSync("docker", ["inspect", "--type", "container", containerName], { encoding: "utf8" });
12
14
  if (inspectResult.status === 0) {
@@ -4,8 +4,9 @@
4
4
  // =========================================================================================================================================
5
5
  import { spawnSync } from "node:child_process";
6
6
  import { cancel, confirm, isCancel, log, outro } from "@clack/prompts";
7
+ import { toDockerName } from "../lib/docker-name.js";
7
8
  export async function run(projectName) {
8
- const containerName = `totopo-managed-${projectName}`;
9
+ const containerName = toDockerName(projectName);
9
10
  // ─── Check if container exists ────────────────────────────────────────────────
10
11
  const inspectResult = spawnSync("docker", ["inspect", "--type", "container", containerName], { encoding: "utf8" });
11
12
  if (inspectResult.status !== 0) {
@@ -0,0 +1,7 @@
1
+ export function toDockerName(projectName) {
2
+ const slug = projectName
3
+ .toLowerCase()
4
+ .replace(/[^a-z0-9._-]+/g, "-")
5
+ .replace(/^[._-]+|[._-]+$/g, "");
6
+ return `totopo-managed-${slug || "workspace"}`;
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "totopo",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "Secure AI Box — isolated dev environments for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {