agent-dev-env 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.
Files changed (101) hide show
  1. package/LICENSE +21 -0
  2. package/dist/assets/bridge/bridge.js +202 -0
  3. package/dist/assets/guest/guest-agent-mac.js +586 -0
  4. package/dist/assets/guest/guest-agent-ubuntu.js +481 -0
  5. package/dist/assets/guest/guest-agent-windows.js +466 -0
  6. package/dist/assets/images/mac/CHANGELOG.md +265 -0
  7. package/dist/assets/images/mac/README.md +63 -0
  8. package/dist/assets/images/mac/sandbox.pkr.hcl +468 -0
  9. package/dist/assets/images/mac/vars/sandbox-macos-tahoe.pkrvars.hcl +39 -0
  10. package/dist/assets/images/ubuntu-arm64-vmware/CHANGELOG.md +239 -0
  11. package/dist/assets/images/ubuntu-arm64-vmware/README.md +177 -0
  12. package/dist/assets/images/ubuntu-arm64-vmware/autoinstall/meta-data +2 -0
  13. package/dist/assets/images/ubuntu-arm64-vmware/autoinstall/user-data +34 -0
  14. package/dist/assets/images/ubuntu-arm64-vmware/sandbox.pkr.hcl +776 -0
  15. package/dist/assets/images/ubuntu-arm64-vmware/vars/sandbox-ubuntu-24-04-arm64-vmware.pkrvars.hcl +71 -0
  16. package/dist/assets/images/windows-arm64-qemu/CHANGELOG.md +209 -0
  17. package/dist/assets/images/windows-arm64-qemu/README.md +142 -0
  18. package/dist/assets/images/windows-arm64-qemu/autounattend.xml +325 -0
  19. package/dist/assets/images/windows-arm64-qemu/qemu-with-tpm.sh +170 -0
  20. package/dist/assets/images/windows-arm64-qemu/sandbox.pkr.hcl +1012 -0
  21. package/dist/assets/images/windows-arm64-qemu/vars/sandbox-windows-11-arm64-qemu.pkrvars.hcl +83 -0
  22. package/dist/assets/images/windows-arm64-vmware/CHANGELOG.md +270 -0
  23. package/dist/assets/images/windows-arm64-vmware/README.md +171 -0
  24. package/dist/assets/images/windows-arm64-vmware/autounattend.xml +311 -0
  25. package/dist/assets/images/windows-arm64-vmware/sandbox.pkr.hcl +1036 -0
  26. package/dist/assets/images/windows-arm64-vmware/vars/sandbox-windows-11-arm64-vmware.pkrvars.hcl +83 -0
  27. package/dist/assets/rules/agent-rules-linux.md +68 -0
  28. package/dist/assets/rules/agent-rules.md +71 -0
  29. package/dist/assets/watchdog/watch-build-ocr.swift +40 -0
  30. package/dist/assets/watchdog/watch-build.py +275 -0
  31. package/dist/cli.js +52 -0
  32. package/dist/commands/delete.js +179 -0
  33. package/dist/commands/doctor.js +165 -0
  34. package/dist/commands/list.js +38 -0
  35. package/dist/commands/not-yet.js +15 -0
  36. package/dist/commands/register.js +150 -0
  37. package/dist/commands/run.js +65 -0
  38. package/dist/commands/status.js +142 -0
  39. package/dist/commands/stop.js +186 -0
  40. package/dist/commands/sync.js +98 -0
  41. package/dist/lib/exec.js +250 -0
  42. package/dist/lib/ghcr.js +82 -0
  43. package/dist/lib/git.js +100 -0
  44. package/dist/lib/logger.js +87 -0
  45. package/dist/lib/network.js +63 -0
  46. package/dist/lib/paths.js +116 -0
  47. package/dist/lib/platform.js +87 -0
  48. package/dist/lib/prompt.js +66 -0
  49. package/dist/lib/qemu.js +373 -0
  50. package/dist/lib/regex.js +9 -0
  51. package/dist/lib/ssh.js +230 -0
  52. package/dist/lib/tart.js +210 -0
  53. package/dist/lib/template.js +35 -0
  54. package/dist/lib/vars.js +99 -0
  55. package/dist/lib/vmrun.js +334 -0
  56. package/dist/lifecycle/build-macos.js +23 -0
  57. package/dist/lifecycle/build-qemu.js +204 -0
  58. package/dist/lifecycle/build-shared.js +355 -0
  59. package/dist/lifecycle/build-ubuntu.js +184 -0
  60. package/dist/lifecycle/build-watchdog.js +146 -0
  61. package/dist/lifecycle/build-windows-vmware.js +112 -0
  62. package/dist/lifecycle/build.js +55 -0
  63. package/dist/lifecycle/catalog.js +165 -0
  64. package/dist/lifecycle/deploy.js +161 -0
  65. package/dist/lifecycle/tag.js +77 -0
  66. package/dist/lifecycle/watch-build.js +64 -0
  67. package/dist/runners/bridges.js +153 -0
  68. package/dist/runners/framework.js +66 -0
  69. package/dist/runners/macos-bridges.js +143 -0
  70. package/dist/runners/macos-guest.js +76 -0
  71. package/dist/runners/macos-rules.js +77 -0
  72. package/dist/runners/macos-summary.js +145 -0
  73. package/dist/runners/macos.js +258 -0
  74. package/dist/runners/openchamber.js +60 -0
  75. package/dist/runners/options.js +56 -0
  76. package/dist/runners/qemu-image.js +183 -0
  77. package/dist/runners/rules.js +49 -0
  78. package/dist/runners/ubuntu-bridges.js +154 -0
  79. package/dist/runners/ubuntu-guest.js +136 -0
  80. package/dist/runners/ubuntu-image.js +26 -0
  81. package/dist/runners/ubuntu-rules.js +86 -0
  82. package/dist/runners/ubuntu-shared.js +65 -0
  83. package/dist/runners/ubuntu-summary.js +141 -0
  84. package/dist/runners/ubuntu.js +117 -0
  85. package/dist/runners/vmware-common.js +158 -0
  86. package/dist/runners/vmware-image.js +242 -0
  87. package/dist/runners/windows-autologon.js +119 -0
  88. package/dist/runners/windows-bridges.js +136 -0
  89. package/dist/runners/windows-guest.js +213 -0
  90. package/dist/runners/windows-image.js +23 -0
  91. package/dist/runners/windows-qemu-summary.js +85 -0
  92. package/dist/runners/windows-qemu.js +189 -0
  93. package/dist/runners/windows-shared.js +68 -0
  94. package/dist/runners/windows-summary.js +104 -0
  95. package/dist/runners/windows.js +96 -0
  96. package/dist/settings/common.js +107 -0
  97. package/dist/settings/macos-copy.js +223 -0
  98. package/dist/settings/macos.js +41 -0
  99. package/dist/settings/ubuntu-copy.js +223 -0
  100. package/dist/settings/ubuntu.js +68 -0
  101. package/package.json +52 -0
@@ -0,0 +1,481 @@
1
+ #!/usr/bin/env node
2
+
3
+ // packages/guest-agent-ubuntu/src/index.ts
4
+ import { execFileSync } from "node:child_process";
5
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import { join as join2 } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ // packages/bridge-core/src/index.ts
11
+ import { rmSync, writeFileSync } from "node:fs";
12
+
13
+ // packages/bridge-core/src/endpoints.ts
14
+ var PIPE_PREFIX = "\\\\.\\pipe\\";
15
+ function parseEndpoint(spec) {
16
+ const sep = spec.indexOf(":");
17
+ if (sep <= 0) {
18
+ throw new Error(`invalid endpoint '${spec}' (expected unix:|pipe:|tcp:)`);
19
+ }
20
+ const scheme = spec.slice(0, sep);
21
+ const rest = spec.slice(sep + 1);
22
+ switch (scheme) {
23
+ case "unix": {
24
+ if (!rest) {
25
+ throw new Error(`invalid unix endpoint '${spec}': missing path`);
26
+ }
27
+ return { kind: "unix", path: rest };
28
+ }
29
+ case "pipe": {
30
+ if (!rest) {
31
+ throw new Error(`invalid pipe endpoint '${spec}': missing name`);
32
+ }
33
+ const name = rest.startsWith(PIPE_PREFIX) ? rest : `${PIPE_PREFIX}${rest}`;
34
+ return { kind: "pipe", name };
35
+ }
36
+ case "tcp": {
37
+ const m = /^([^:]+):(\d+)$/.exec(rest);
38
+ const port = m ? Number(m[2]) : NaN;
39
+ if (!m || !Number.isInteger(port) || port < 1 || port > 65535) {
40
+ throw new Error(`invalid tcp endpoint '${spec}' (expected tcp:host:port, port 1-65535)`);
41
+ }
42
+ return { kind: "tcp", host: m[1], port };
43
+ }
44
+ default:
45
+ throw new Error(
46
+ `unknown endpoint scheme '${scheme}' in '${spec}' (expected unix:|pipe:|tcp:)`
47
+ );
48
+ }
49
+ }
50
+ function formatEndpoint(endpoint) {
51
+ switch (endpoint.kind) {
52
+ case "unix":
53
+ return `unix:${endpoint.path}`;
54
+ case "pipe":
55
+ return `pipe:${endpoint.name}`;
56
+ case "tcp":
57
+ return `tcp:${endpoint.host}:${endpoint.port}`;
58
+ }
59
+ }
60
+
61
+ // packages/bridge-core/src/forwarder.ts
62
+ import net from "node:net";
63
+ import { existsSync, unlinkSync } from "node:fs";
64
+ function startForwarder(options) {
65
+ const sockets = /* @__PURE__ */ new Set();
66
+ const server = net.createServer((client) => {
67
+ sockets.add(client);
68
+ client.on("close", () => sockets.delete(client));
69
+ forwardConnection(client, options.forward, sockets);
70
+ });
71
+ return bind(server, options.listen).then(() => {
72
+ options.log?.(
73
+ `listening on ${formatEndpoint(options.listen)} \u2192 ${formatEndpoint(options.forward)}`
74
+ );
75
+ return {
76
+ close: () => closeForwarder(server, sockets, options.listen, options.log)
77
+ };
78
+ });
79
+ }
80
+ function forwardConnection(client, target, sockets) {
81
+ const upstream = connectTo(target);
82
+ sockets.add(upstream);
83
+ upstream.on("close", () => sockets.delete(upstream));
84
+ upstream.once("connect", () => {
85
+ client.pipe(upstream);
86
+ upstream.pipe(client);
87
+ });
88
+ const tearDown = () => {
89
+ client.destroy();
90
+ upstream.destroy();
91
+ };
92
+ upstream.once("error", tearDown);
93
+ client.once("error", () => {
94
+ upstream.destroy();
95
+ });
96
+ }
97
+ async function bind(server, endpoint) {
98
+ switch (endpoint.kind) {
99
+ case "unix":
100
+ if (existsSync(endpoint.path)) {
101
+ unlinkSync(endpoint.path);
102
+ }
103
+ await listen(server, endpoint.path);
104
+ break;
105
+ case "pipe":
106
+ await listen(server, endpoint.name);
107
+ break;
108
+ case "tcp":
109
+ await listen(server, { port: endpoint.port, host: endpoint.host });
110
+ break;
111
+ }
112
+ }
113
+ function listen(server, spec) {
114
+ return new Promise((resolve, reject) => {
115
+ server.once("error", reject);
116
+ server.listen(spec, () => {
117
+ server.removeListener("error", reject);
118
+ resolve();
119
+ });
120
+ });
121
+ }
122
+ function connectTo(endpoint) {
123
+ switch (endpoint.kind) {
124
+ case "unix":
125
+ return net.connect({ path: endpoint.path });
126
+ case "pipe":
127
+ return net.connect({ path: endpoint.name });
128
+ case "tcp":
129
+ return net.connect({ port: endpoint.port, host: endpoint.host });
130
+ }
131
+ }
132
+ function closeForwarder(server, sockets, listen2, log) {
133
+ return new Promise((resolve) => {
134
+ for (const socket of sockets) {
135
+ socket.destroy();
136
+ }
137
+ server.close(() => {
138
+ if (listen2.kind === "unix" && existsSync(listen2.path)) {
139
+ unlinkSync(listen2.path);
140
+ }
141
+ log?.("closed");
142
+ resolve();
143
+ });
144
+ });
145
+ }
146
+
147
+ // packages/bridge-core/src/index.ts
148
+ async function runBridge(options) {
149
+ const running = await startForwarder(options);
150
+ if (options.pidfile) {
151
+ writeFileSync(options.pidfile, `${process.pid}
152
+ `);
153
+ }
154
+ await new Promise((resolve) => {
155
+ const stop = () => {
156
+ void running.close().then(() => {
157
+ if (options.pidfile) {
158
+ rmSync(options.pidfile, { force: true });
159
+ }
160
+ resolve();
161
+ });
162
+ };
163
+ process.on("SIGINT", stop);
164
+ process.on("SIGTERM", stop);
165
+ });
166
+ return 0;
167
+ }
168
+
169
+ // packages/bridge-core/src/probe.ts
170
+ import net2 from "node:net";
171
+ function canConnect(endpoint, timeoutMs = 500) {
172
+ return new Promise((resolve) => {
173
+ const socket = endpoint.kind === "tcp" ? net2.connect({ port: endpoint.port, host: endpoint.host }) : net2.connect({ path: endpoint.kind === "unix" ? endpoint.path : endpoint.name });
174
+ const done = (ok) => {
175
+ socket.destroy();
176
+ resolve(ok);
177
+ };
178
+ socket.once("connect", () => done(true));
179
+ socket.once("error", () => done(false));
180
+ setTimeout(() => done(false), timeoutMs);
181
+ });
182
+ }
183
+
184
+ // packages/guest-rules/src/rules.ts
185
+ import { createHash } from "node:crypto";
186
+ import { mkdirSync, readFileSync, writeFileSync as writeFileSync2 } from "node:fs";
187
+ import { join } from "node:path";
188
+ function rulesPaths(home) {
189
+ return {
190
+ marker: join(home, ".config", "agent-dev-env", "agent-rules.sha256"),
191
+ targets: [
192
+ join(home, ".config", "opencode", "AGENTS.md"),
193
+ join(home, ".copilot", "copilot-instructions.md")
194
+ ]
195
+ };
196
+ }
197
+ function sha256(text) {
198
+ return createHash("sha256").update(text, "utf8").digest("hex");
199
+ }
200
+ function readMaybe(path) {
201
+ try {
202
+ return readFileSync(path, "utf8");
203
+ } catch {
204
+ return void 0;
205
+ }
206
+ }
207
+ function rulesAction(home, content) {
208
+ const { marker, targets } = rulesPaths(home);
209
+ const newSha = sha256(content);
210
+ const prevSha = readMaybe(marker)?.trim();
211
+ let conflict = false;
212
+ let install2 = false;
213
+ let update = false;
214
+ for (const target of targets) {
215
+ const current = readMaybe(target);
216
+ if (current === void 0) {
217
+ install2 = true;
218
+ } else if (sha256(current) === newSha) {
219
+ } else if (prevSha !== void 0 && sha256(current) === prevSha) {
220
+ update = true;
221
+ } else {
222
+ conflict = true;
223
+ }
224
+ }
225
+ if (conflict) return "conflict";
226
+ if (install2) return "install";
227
+ if (update) return "update";
228
+ return "uptodate";
229
+ }
230
+ function applyRules(home, content) {
231
+ const { marker, targets } = rulesPaths(home);
232
+ for (const target of targets) {
233
+ mkdirSync(join(target, ".."), { recursive: true });
234
+ writeFileSync2(target, content);
235
+ }
236
+ mkdirSync(join(marker, ".."), { recursive: true });
237
+ writeFileSync2(marker, `${sha256(content)}
238
+ `);
239
+ }
240
+
241
+ // packages/guest-agent-ubuntu/src/systemd.ts
242
+ var UBUNTU_SOCKETS = {
243
+ "ssh-agent": "/tmp/ssh-agent.sock",
244
+ docker: "/tmp/docker.sock"
245
+ };
246
+ var PROFILE_D_PATH = "/etc/profile.d/agent-sandbox.sh";
247
+ function systemdUnit(bridge) {
248
+ const args = [
249
+ "bridge",
250
+ bridge.role,
251
+ "--port",
252
+ String(bridge.port),
253
+ "--host-alias",
254
+ bridge.hostAlias,
255
+ "--listen",
256
+ `unix:${bridge.socket}`,
257
+ "--forward",
258
+ `tcp:${bridge.hostAlias}:${bridge.port}`
259
+ ];
260
+ return [
261
+ "[Unit]",
262
+ `Description=${bridge.description}`,
263
+ "After=network.target",
264
+ "",
265
+ "[Service]",
266
+ "Type=simple",
267
+ `ExecStart=${bridge.nodePath} ${bridge.agentPath} ${args.join(" ")}`,
268
+ "Restart=on-failure",
269
+ "",
270
+ "[Install]",
271
+ "WantedBy=default.target",
272
+ ""
273
+ ].join("\n");
274
+ }
275
+ function profileDScript(gw) {
276
+ return [
277
+ "# Agent sandbox bridge env (see docs/ubuntu-vmware.md)",
278
+ "export SSH_AUTH_SOCK=/tmp/ssh-agent.sock",
279
+ "export DOCKER_HOST=unix:///tmp/docker.sock",
280
+ `export TESTCONTAINERS_HOST_OVERRIDE=${gw}`,
281
+ "",
282
+ ""
283
+ ].join("\n");
284
+ }
285
+
286
+ // packages/guest-agent-ubuntu/src/index.ts
287
+ var AGENT_PATH = fileURLToPath(import.meta.url);
288
+ var NODE_PATH = process.execPath;
289
+ var HOME = homedir();
290
+ var UNIT_DIR = join2(HOME, ".config", "systemd", "user");
291
+ var DEFAULTS = {
292
+ agentPort: 4400,
293
+ dockerPort: 4401
294
+ };
295
+ async function main(argv) {
296
+ const [cmd, ...rest] = argv;
297
+ try {
298
+ switch (cmd) {
299
+ case "install":
300
+ return install(rest);
301
+ case "bridge":
302
+ return bridgeCommand(rest);
303
+ case "status":
304
+ return status();
305
+ case "rules":
306
+ return rulesCommand(rest);
307
+ case "uninstall":
308
+ return uninstall();
309
+ default:
310
+ process.stderr.write(usage());
311
+ return 1;
312
+ }
313
+ } catch (err) {
314
+ process.stderr.write(`guest-agent-ubuntu: ${err.message}
315
+ `);
316
+ return 1;
317
+ }
318
+ }
319
+ function usage() {
320
+ return [
321
+ "usage: guest-agent-ubuntu <install|bridge|status|rules|uninstall> [options]",
322
+ " install [--agent-port N] [--docker-port N] [--host-alias A]",
323
+ " bridge <ssh-agent|docker> [--listen EP] [--forward EP] [--port N] [--host-alias A]",
324
+ " rules [--force] (content on stdin)",
325
+ ""
326
+ ].join("\n");
327
+ }
328
+ function argValue(argv, flag) {
329
+ const index = argv.indexOf(flag);
330
+ return index === -1 ? void 0 : argv[index + 1];
331
+ }
332
+ function bridgeFor(role, port, hostAlias) {
333
+ return {
334
+ unitName: `agent-sandbox-${role}.service`,
335
+ description: role === "ssh-agent" ? "Agent Sandbox SSH agent bridge" : "Agent Sandbox Docker bridge",
336
+ nodePath: NODE_PATH,
337
+ agentPath: AGENT_PATH,
338
+ role,
339
+ port,
340
+ hostAlias,
341
+ socket: UBUNTU_SOCKETS[role]
342
+ };
343
+ }
344
+ function install(argv) {
345
+ const hostAlias = argValue(argv, "--host-alias") ?? (() => {
346
+ throw new Error("--host-alias is required (the NAT router address)");
347
+ })();
348
+ const agentPort = Number(argValue(argv, "--agent-port") ?? DEFAULTS.agentPort);
349
+ const dockerPort = Number(argValue(argv, "--docker-port") ?? DEFAULTS.dockerPort);
350
+ mkdirSync2(UNIT_DIR, { recursive: true });
351
+ for (const [role, port] of [
352
+ ["ssh-agent", agentPort],
353
+ ["docker", dockerPort]
354
+ ]) {
355
+ const bridge = bridgeFor(role, port, hostAlias);
356
+ writeFileSync3(join2(UNIT_DIR, bridge.unitName), systemdUnit(bridge));
357
+ process.stdout.write(`installed:${role} (${bridge.unitName}, port ${port})
358
+ `);
359
+ }
360
+ execFileSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
361
+ for (const [role] of [["ssh-agent"], ["docker"]]) {
362
+ execFileSync("systemctl", ["--user", "enable", "--now", `agent-sandbox-${role}.service`], {
363
+ stdio: "ignore"
364
+ });
365
+ }
366
+ installProfileExports(hostAlias);
367
+ return 0;
368
+ }
369
+ function installProfileExports(gw) {
370
+ if (typeof process.getuid === "function" && process.getuid() === 0) {
371
+ writeFileSync3(PROFILE_D_PATH, profileDScript(gw));
372
+ process.stdout.write(`installed:profile.d (${PROFILE_D_PATH})
373
+ `);
374
+ return;
375
+ }
376
+ const profile = join2(HOME, ".profile");
377
+ const content = readMaybe2(profile);
378
+ if (!content.includes(profileMarker())) {
379
+ writeFileSync3(profile, `${content}${profileDScript(gw)}`);
380
+ }
381
+ process.stdout.write(
382
+ `installed:profile.d (user ~/.profile; run install via sudo for ${PROFILE_D_PATH})
383
+ `
384
+ );
385
+ }
386
+ function profileMarker() {
387
+ return "# Agent sandbox bridge env";
388
+ }
389
+ function bridgeCommand(argv) {
390
+ const role = argv[0];
391
+ if (role !== "ssh-agent" && role !== "docker") {
392
+ throw new Error(`unknown bridge role '${role}' (ssh-agent|docker)`);
393
+ }
394
+ const port = Number(
395
+ argValue(argv, "--port") ?? DEFAULTS[role === "ssh-agent" ? "agentPort" : "dockerPort"]
396
+ );
397
+ const hostAlias = argValue(argv, "--host-alias") ?? (() => {
398
+ throw new Error("--host-alias is required (the NAT router address)");
399
+ })();
400
+ const listen2 = argValue(argv, "--listen") ?? `unix:${UBUNTU_SOCKETS[role]}`;
401
+ const forward = argValue(argv, "--forward") ?? `tcp:${hostAlias}:${port}`;
402
+ return runBridge({
403
+ listen: parseEndpoint(listen2),
404
+ forward: parseEndpoint(forward),
405
+ log: (message) => process.stderr.write(`${message}
406
+ `)
407
+ });
408
+ }
409
+ async function status() {
410
+ const agentUp = await canConnect(parseEndpoint(`unix:${UBUNTU_SOCKETS["ssh-agent"]}`));
411
+ const dockerUp = await canConnect(parseEndpoint(`unix:${UBUNTU_SOCKETS.docker}`));
412
+ process.stdout.write(`bridge-status:ssh-agent=${agentUp ? "up" : "down"}
413
+ `);
414
+ process.stdout.write(`bridge-status:docker=${dockerUp ? "up" : "down"}
415
+ `);
416
+ return 0;
417
+ }
418
+ function rulesCommand(argv) {
419
+ const content = readFileSync2(0, "utf8");
420
+ if (argv.includes("--force")) {
421
+ applyRules(HOME, content);
422
+ process.stdout.write("rules:overwritten\n");
423
+ } else {
424
+ process.stdout.write(`rules:probe=${rulesAction(HOME, content)}
425
+ `);
426
+ }
427
+ return 0;
428
+ }
429
+ function uninstall() {
430
+ for (const role of ["ssh-agent", "docker"]) {
431
+ const unit = `agent-sandbox-${role}.service`;
432
+ try {
433
+ execFileSync("systemctl", ["--user", "disable", "--now", unit], { stdio: "ignore" });
434
+ } catch {
435
+ }
436
+ rmSync2(join2(UNIT_DIR, unit), { force: true });
437
+ }
438
+ if (typeof process.getuid === "function" && process.getuid() === 0) {
439
+ rmSync2(PROFILE_D_PATH, { force: true });
440
+ }
441
+ const profile = join2(HOME, ".profile");
442
+ if (existsSync2(profile)) {
443
+ const cleaned = removeProfileBlock(readMaybe2(profile));
444
+ if (cleaned !== readMaybe2(profile)) {
445
+ writeFileSync3(profile, cleaned);
446
+ }
447
+ }
448
+ process.stdout.write("uninstalled:ssh-agent,docker\n");
449
+ return 0;
450
+ }
451
+ function removeProfileBlock(content) {
452
+ const lines = content.split("\n");
453
+ const markerIndex = lines.findIndex((line) => line.startsWith(profileMarker()));
454
+ if (markerIndex === -1) {
455
+ return content;
456
+ }
457
+ const blockStart = markerIndex > 0 && lines[markerIndex - 1] === "" ? markerIndex - 1 : markerIndex;
458
+ let blockEnd = markerIndex + 1;
459
+ while (blockEnd < lines.length && lines[blockEnd] !== "") {
460
+ blockEnd += 1;
461
+ }
462
+ if (blockEnd < lines.length) {
463
+ blockEnd += 1;
464
+ }
465
+ return [...lines.slice(0, blockStart), ...lines.slice(blockEnd)].join("\n").replace(/\n{3,}/g, "\n\n");
466
+ }
467
+ function readMaybe2(path) {
468
+ try {
469
+ return readFileSync2(path, "utf8");
470
+ } catch {
471
+ return "";
472
+ }
473
+ }
474
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
475
+ void main(process.argv.slice(2)).then((code) => {
476
+ process.exit(code);
477
+ });
478
+ }
479
+ export {
480
+ removeProfileBlock
481
+ };