@codedir/mimir-code 0.1.1 → 0.1.3

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/dist/cli.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/env node
2
+
1
3
  // src/cli.ts
2
4
  import { Command } from "commander";
3
5
 
@@ -5,28 +7,28 @@ import { Command } from "commander";
5
7
  import fs from "fs/promises";
6
8
  import fg from "fast-glob";
7
9
  var FileSystemAdapter = class {
8
- async readFile(path8, encoding = "utf-8") {
9
- return fs.readFile(path8, encoding);
10
+ async readFile(path9, encoding = "utf-8") {
11
+ return fs.readFile(path9, encoding);
10
12
  }
11
- async writeFile(path8, content, encoding = "utf-8") {
12
- await fs.writeFile(path8, content, encoding);
13
+ async writeFile(path9, content, encoding = "utf-8") {
14
+ await fs.writeFile(path9, content, encoding);
13
15
  }
14
- async exists(path8) {
16
+ async exists(path9) {
15
17
  try {
16
- await fs.access(path8);
18
+ await fs.access(path9);
17
19
  return true;
18
20
  } catch {
19
21
  return false;
20
22
  }
21
23
  }
22
- async mkdir(path8, options) {
23
- await fs.mkdir(path8, options);
24
+ async mkdir(path9, options) {
25
+ await fs.mkdir(path9, options);
24
26
  }
25
- async readdir(path8) {
26
- return fs.readdir(path8);
27
+ async readdir(path9) {
28
+ return fs.readdir(path9);
27
29
  }
28
- async stat(path8) {
29
- const stats = await fs.stat(path8);
30
+ async stat(path9) {
31
+ const stats = await fs.stat(path9);
30
32
  return {
31
33
  isFile: () => stats.isFile(),
32
34
  isDirectory: () => stats.isDirectory(),
@@ -34,11 +36,11 @@ var FileSystemAdapter = class {
34
36
  mtime: stats.mtime
35
37
  };
36
38
  }
37
- async unlink(path8) {
38
- await fs.unlink(path8);
39
+ async unlink(path9) {
40
+ await fs.unlink(path9);
39
41
  }
40
- async rmdir(path8, options) {
41
- await fs.rmdir(path8, options);
42
+ async rmdir(path9, options) {
43
+ await fs.rmdir(path9, options);
42
44
  }
43
45
  async copyFile(src, dest) {
44
46
  await fs.copyFile(src, dest);
@@ -51,6 +53,94 @@ var FileSystemAdapter = class {
51
53
  }
52
54
  };
53
55
 
56
+ // src/platform/ProcessExecutorAdapter.ts
57
+ import { execa, execaCommand } from "execa";
58
+ var ProcessExecutorAdapter = class {
59
+ /**
60
+ * Execute command and wait for completion
61
+ */
62
+ async execute(command, args = [], options = {}) {
63
+ try {
64
+ const result = await execa(command, args, {
65
+ cwd: options.cwd,
66
+ env: options.env,
67
+ timeout: options.timeout,
68
+ shell: options.shell,
69
+ input: options.input,
70
+ reject: false
71
+ // Don't throw on non-zero exit codes
72
+ });
73
+ return {
74
+ stdout: result.stdout,
75
+ stderr: result.stderr,
76
+ exitCode: result.exitCode ?? (result.failed ? 1 : 0),
77
+ command: result.command,
78
+ timedOut: result.timedOut ?? false
79
+ };
80
+ } catch (error) {
81
+ const exitCode = error.code === "ENOENT" ? 127 : error.exitCode || 1;
82
+ return {
83
+ stdout: error.stdout || "",
84
+ stderr: error.stderr || error.message || String(error),
85
+ exitCode,
86
+ command: error.command || `${command} ${args.join(" ")}`,
87
+ timedOut: error.timedOut ?? false
88
+ };
89
+ }
90
+ }
91
+ /**
92
+ * Spawn process (doesn't wait for completion)
93
+ */
94
+ spawn(command, args = [], options = {}) {
95
+ const subprocess = execa(command, args, {
96
+ cwd: options.cwd,
97
+ env: options.env,
98
+ timeout: options.timeout,
99
+ shell: options.shell,
100
+ stdin: options.input ? "pipe" : "inherit",
101
+ stdout: "pipe",
102
+ stderr: "pipe"
103
+ });
104
+ if (options.input && subprocess.stdin) {
105
+ subprocess.stdin.write(options.input);
106
+ subprocess.stdin.end();
107
+ }
108
+ return subprocess;
109
+ }
110
+ /**
111
+ * Execute command in shell
112
+ */
113
+ async executeShell(command, options = {}) {
114
+ try {
115
+ const result = await execaCommand(command, {
116
+ cwd: options.cwd,
117
+ env: options.env,
118
+ timeout: options.timeout,
119
+ shell: true,
120
+ input: options.input,
121
+ reject: false
122
+ // Don't throw on non-zero exit codes
123
+ });
124
+ return {
125
+ stdout: result.stdout,
126
+ stderr: result.stderr,
127
+ exitCode: result.exitCode ?? (result.failed ? 1 : 0),
128
+ command: result.command,
129
+ timedOut: result.timedOut ?? false
130
+ };
131
+ } catch (error) {
132
+ const exitCode = error.code === "ENOENT" ? 127 : error.exitCode || 1;
133
+ return {
134
+ stdout: error.stdout || "",
135
+ stderr: error.stderr || error.message || String(error),
136
+ exitCode,
137
+ command: error.command || command,
138
+ timedOut: error.timedOut ?? false
139
+ };
140
+ }
141
+ }
142
+ };
143
+
54
144
  // src/config/schemas.ts
55
145
  import { z } from "zod";
56
146
  var ThemeSchema = z.enum([
@@ -3816,6 +3906,19 @@ import { fileURLToPath } from "url";
3816
3906
  import { readFileSync, existsSync } from "fs";
3817
3907
  function locateWasmFile() {
3818
3908
  const wasmFileName = "sql-wasm.wasm";
3909
+ const currentDir = dirname(fileURLToPath(import.meta.url));
3910
+ const nodeModulesPaths = [
3911
+ // Relative to the built module (dist/storage/Database.js)
3912
+ join(currentDir, "..", "..", "node_modules", "sql.js", "dist", wasmFileName),
3913
+ // Relative to current working directory (for local development)
3914
+ join(process.cwd(), "node_modules", "sql.js", "dist", wasmFileName)
3915
+ ];
3916
+ for (const modulePath of nodeModulesPaths) {
3917
+ if (existsSync(modulePath)) {
3918
+ const buffer = readFileSync(modulePath);
3919
+ return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
3920
+ }
3921
+ }
3819
3922
  const executablePath = process.argv[0] || process.execPath;
3820
3923
  const binaryDir = dirname(executablePath);
3821
3924
  const resourcesPaths = [
@@ -3832,23 +3935,13 @@ function locateWasmFile() {
3832
3935
  return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
3833
3936
  }
3834
3937
  }
3835
- const currentDir = dirname(fileURLToPath(import.meta.url));
3836
- const nodeModulesPaths = [
3837
- join(currentDir, "..", "..", "node_modules", "sql.js", "dist", wasmFileName),
3838
- join(process.cwd(), "node_modules", "sql.js", "dist", wasmFileName)
3839
- ];
3840
- for (const modulePath of nodeModulesPaths) {
3841
- if (existsSync(modulePath)) {
3842
- const buffer = readFileSync(modulePath);
3843
- return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
3844
- }
3845
- }
3846
3938
  const diagnostics = [
3939
+ `import.meta.url: ${import.meta.url}`,
3940
+ `currentDir: ${currentDir}`,
3847
3941
  `process.argv[0]: ${process.argv[0]}`,
3848
3942
  `process.execPath: ${process.execPath}`,
3849
3943
  `executablePath: ${executablePath}`,
3850
3944
  `binaryDir: ${binaryDir}`,
3851
- `currentDir: ${currentDir}`,
3852
3945
  `process.cwd(): ${process.cwd()}`
3853
3946
  ];
3854
3947
  throw new Error(
@@ -3858,7 +3951,7 @@ Diagnostics:
3858
3951
  ${diagnostics.join("\n")}
3859
3952
 
3860
3953
  Tried:
3861
- ` + [...resourcesPaths, ...nodeModulesPaths].map((p) => ` - ${p}`).join("\n")
3954
+ ` + [...nodeModulesPaths, ...resourcesPaths].map((p) => ` - ${p}`).join("\n")
3862
3955
  );
3863
3956
  }
3864
3957
  var DatabaseManager = class _DatabaseManager {
@@ -6058,13 +6151,457 @@ var InitCommand = class {
6058
6151
  }
6059
6152
  };
6060
6153
 
6154
+ // src/cli/commands/UninstallCommand.ts
6155
+ import path8 from "path";
6156
+ import os6 from "os";
6157
+ import React11 from "react";
6158
+ import { render as render3, Box as Box12, Text as Text11 } from "ink";
6159
+ import Spinner from "ink-spinner";
6160
+ import TextInput2 from "ink-text-input";
6161
+ var UninstallCommand = class {
6162
+ constructor(fs4, executor2) {
6163
+ this.fs = fs4;
6164
+ this.executor = executor2;
6165
+ }
6166
+ async execute(options = {}) {
6167
+ try {
6168
+ const autoConfirm = options.yes || options.quiet;
6169
+ let confirmed = autoConfirm;
6170
+ if (!autoConfirm) {
6171
+ confirmed = await this.promptConfirmation();
6172
+ }
6173
+ if (!confirmed) {
6174
+ if (!options.quiet) {
6175
+ logger.info("Uninstall cancelled.");
6176
+ }
6177
+ return;
6178
+ }
6179
+ let keepConfig;
6180
+ if (options.removeConfig !== void 0) {
6181
+ keepConfig = !options.removeConfig;
6182
+ } else if (options.keepConfig !== void 0) {
6183
+ keepConfig = options.keepConfig;
6184
+ } else if (autoConfirm) {
6185
+ keepConfig = true;
6186
+ } else {
6187
+ keepConfig = await this.promptKeepConfig();
6188
+ }
6189
+ const result = await this.uninstall(keepConfig, options.quiet);
6190
+ if (!options.quiet) {
6191
+ this.printSummary(result);
6192
+ }
6193
+ if (!result.success) {
6194
+ process.exit(1);
6195
+ }
6196
+ } catch (error) {
6197
+ if (!options.quiet) {
6198
+ logger.error("Uninstall failed", { error });
6199
+ }
6200
+ process.exit(1);
6201
+ }
6202
+ }
6203
+ async promptConfirmation() {
6204
+ return new Promise((resolve) => {
6205
+ const ConfirmPrompt = () => {
6206
+ const [input, setInput] = React11.useState("");
6207
+ const [submitted, setSubmitted] = React11.useState(false);
6208
+ React11.useEffect(() => {
6209
+ if (submitted) {
6210
+ const answer = input.toLowerCase();
6211
+ resolve(answer === "y" || answer === "yes");
6212
+ }
6213
+ }, [submitted, input]);
6214
+ if (submitted) {
6215
+ return null;
6216
+ }
6217
+ return React11.createElement(
6218
+ Box12,
6219
+ { flexDirection: "column", marginY: 1 },
6220
+ React11.createElement(
6221
+ Box12,
6222
+ { marginBottom: 1 },
6223
+ React11.createElement(
6224
+ Text11,
6225
+ { bold: true, color: "yellow" },
6226
+ "\u26A0\uFE0F WARNING: This will uninstall Mimir from your system."
6227
+ )
6228
+ ),
6229
+ React11.createElement(
6230
+ Box12,
6231
+ null,
6232
+ React11.createElement(Text11, null, "Are you sure you want to continue? (y/N): "),
6233
+ React11.createElement(TextInput2, {
6234
+ value: input,
6235
+ onChange: setInput,
6236
+ onSubmit: () => setSubmitted(true)
6237
+ })
6238
+ )
6239
+ );
6240
+ };
6241
+ render3(React11.createElement(ConfirmPrompt));
6242
+ });
6243
+ }
6244
+ async promptKeepConfig() {
6245
+ return new Promise((resolve) => {
6246
+ const ConfigPrompt = () => {
6247
+ const [input, setInput] = React11.useState("");
6248
+ const [submitted, setSubmitted] = React11.useState(false);
6249
+ React11.useEffect(() => {
6250
+ if (submitted) {
6251
+ const answer = input.toLowerCase();
6252
+ resolve(answer !== "n" && answer !== "no");
6253
+ }
6254
+ }, [submitted, input]);
6255
+ if (submitted) {
6256
+ return null;
6257
+ }
6258
+ return React11.createElement(
6259
+ Box12,
6260
+ { flexDirection: "column", marginY: 1 },
6261
+ React11.createElement(
6262
+ Box12,
6263
+ { marginBottom: 1 },
6264
+ React11.createElement(
6265
+ Text11,
6266
+ null,
6267
+ "Do you want to keep your Mimir configuration and data in ~/.mimir?"
6268
+ )
6269
+ ),
6270
+ React11.createElement(
6271
+ Box12,
6272
+ null,
6273
+ React11.createElement(Text11, null, "Keep configuration? (Y/n): "),
6274
+ React11.createElement(TextInput2, {
6275
+ value: input,
6276
+ onChange: setInput,
6277
+ onSubmit: () => setSubmitted(true)
6278
+ })
6279
+ )
6280
+ );
6281
+ };
6282
+ render3(React11.createElement(ConfigPrompt));
6283
+ });
6284
+ }
6285
+ async uninstall(keepConfig, quiet = false) {
6286
+ const result = {
6287
+ success: true,
6288
+ removed: [],
6289
+ errors: [],
6290
+ keepConfig
6291
+ };
6292
+ let clear;
6293
+ if (!quiet) {
6294
+ const UninstallProgress = () => React11.createElement(
6295
+ Box12,
6296
+ null,
6297
+ React11.createElement(
6298
+ Text11,
6299
+ { color: "cyan" },
6300
+ React11.createElement(Spinner, { type: "dots" }),
6301
+ " Uninstalling Mimir..."
6302
+ )
6303
+ );
6304
+ const rendered = render3(React11.createElement(UninstallProgress));
6305
+ clear = rendered.clear;
6306
+ }
6307
+ try {
6308
+ const homeDir = os6.homedir();
6309
+ const installType = await this.detectInstallType();
6310
+ if (!quiet) {
6311
+ logger.info(`Detected installation type: ${installType}`);
6312
+ }
6313
+ if (installType === "binary") {
6314
+ await this.removeBinaryInstallation(homeDir, result, quiet);
6315
+ }
6316
+ if (installType === "npm") {
6317
+ await this.removeNpmInstallation(result, quiet);
6318
+ }
6319
+ if (!keepConfig) {
6320
+ await this.removeGlobalConfig(homeDir, result, quiet);
6321
+ } else if (!quiet) {
6322
+ logger.info("Keeping global configuration at ~/.mimir");
6323
+ }
6324
+ } catch (error) {
6325
+ result.success = false;
6326
+ result.errors.push(
6327
+ `Uninstall failed: ${error instanceof Error ? error.message : String(error)}`
6328
+ );
6329
+ if (!quiet) {
6330
+ logger.error("Uninstall error", { error });
6331
+ }
6332
+ } finally {
6333
+ if (clear) {
6334
+ clear();
6335
+ }
6336
+ }
6337
+ return result;
6338
+ }
6339
+ async detectInstallType() {
6340
+ try {
6341
+ const scriptPath = process.argv[1];
6342
+ if (!scriptPath) {
6343
+ logger.debug("No script path found in process.argv[1]");
6344
+ return "unknown";
6345
+ }
6346
+ logger.debug(`Detecting install type from script path: ${scriptPath}`);
6347
+ const normalizedPath = path8.normalize(scriptPath).toLowerCase();
6348
+ if (normalizedPath.includes("node_modules")) {
6349
+ logger.debug("Detected npm installation (node_modules in path)");
6350
+ return "npm";
6351
+ }
6352
+ const homeDir = os6.homedir();
6353
+ const mimirBinPath = path8.normalize(path8.join(homeDir, ".mimir", "bin")).toLowerCase();
6354
+ const localBinPath = path8.normalize(path8.join(homeDir, ".local", "bin")).toLowerCase();
6355
+ if (normalizedPath.includes(mimirBinPath)) {
6356
+ logger.debug("Detected binary installation (.mimir/bin in path)");
6357
+ return "binary";
6358
+ }
6359
+ if (normalizedPath.includes(localBinPath)) {
6360
+ logger.debug("Detected binary installation (.local/bin in path)");
6361
+ return "binary";
6362
+ }
6363
+ const binaryPaths = [
6364
+ // Unix binary locations
6365
+ path8.join(homeDir, ".local", "bin", "mimir"),
6366
+ path8.join(homeDir, ".mimir", "bin", "mimir"),
6367
+ // Windows binary locations (.exe, .cmd variants)
6368
+ path8.join(homeDir, ".local", "bin", "mimir.exe"),
6369
+ path8.join(homeDir, ".local", "bin", "mimir.cmd"),
6370
+ path8.join(homeDir, ".mimir", "bin", "mimir.exe"),
6371
+ path8.join(homeDir, ".mimir", "bin", "mimir.cmd")
6372
+ ];
6373
+ for (const binPath of binaryPaths) {
6374
+ if (await this.fs.exists(binPath)) {
6375
+ logger.debug(`Detected binary installation (found file at ${binPath})`);
6376
+ return "binary";
6377
+ }
6378
+ }
6379
+ logger.debug("Could not detect installation type - defaulting to unknown");
6380
+ return "unknown";
6381
+ } catch (error) {
6382
+ logger.debug("Error detecting installation type", { error });
6383
+ return "unknown";
6384
+ }
6385
+ }
6386
+ async removeNpmInstallation(result, quiet = false) {
6387
+ if (!this.executor) {
6388
+ if (!quiet) {
6389
+ logger.warn("Cannot automatically uninstall npm package.");
6390
+ logger.info("Please run manually: npm uninstall -g @codedir/mimir-code");
6391
+ }
6392
+ return;
6393
+ }
6394
+ try {
6395
+ if (!quiet) {
6396
+ logger.info("Removing npm global package...");
6397
+ }
6398
+ const npmResult = await this.executor.execute(
6399
+ "npm",
6400
+ ["uninstall", "-g", "@codedir/mimir-code"],
6401
+ {
6402
+ cwd: process.cwd()
6403
+ }
6404
+ );
6405
+ if (npmResult.exitCode === 0) {
6406
+ result.removed.push("npm global package (@codedir/mimir-code)");
6407
+ if (!quiet) {
6408
+ logger.info("Successfully uninstalled npm package");
6409
+ }
6410
+ } else {
6411
+ throw new Error(`npm uninstall failed: ${npmResult.stderr}`);
6412
+ }
6413
+ } catch (error) {
6414
+ if (!quiet) {
6415
+ logger.error("Failed to uninstall npm package", { error });
6416
+ logger.info("Please run manually: npm uninstall -g @codedir/mimir-code");
6417
+ }
6418
+ result.errors.push(
6419
+ `npm uninstall failed: ${error instanceof Error ? error.message : String(error)}`
6420
+ );
6421
+ }
6422
+ }
6423
+ async removeBinaryInstallation(homeDir, result, quiet = false) {
6424
+ const isWindows = process.platform === "win32";
6425
+ const localBinPath = path8.join(homeDir, ".local", "bin", "mimir");
6426
+ const mimirBinDir = path8.join(homeDir, ".mimir", "bin");
6427
+ if (isWindows) {
6428
+ await this.spawnWindowsCleanup(localBinPath, mimirBinDir, quiet);
6429
+ await this.removeFromWindowsPath(quiet);
6430
+ result.removed.push("Binary (scheduled for deletion after exit)");
6431
+ result.removed.push("PATH entry");
6432
+ if (!quiet) {
6433
+ logger.info("\u2713 Scheduled binary deletion");
6434
+ logger.info("\u2713 Removed from PATH");
6435
+ logger.warn("\u26A0 Cleanup will complete in ~3 seconds");
6436
+ }
6437
+ } else {
6438
+ if (await this.fs.exists(localBinPath)) {
6439
+ await this.fs.unlink(localBinPath);
6440
+ result.removed.push("~/.local/bin/mimir");
6441
+ if (!quiet) {
6442
+ logger.info("Removed binary from ~/.local/bin");
6443
+ }
6444
+ }
6445
+ if (await this.fs.exists(mimirBinDir)) {
6446
+ await this.fs.rmdir(mimirBinDir, { recursive: true });
6447
+ result.removed.push("~/.mimir/bin/");
6448
+ if (!quiet) {
6449
+ logger.info("Removed binary directory");
6450
+ }
6451
+ }
6452
+ if (!quiet) {
6453
+ logger.info("\u2713 Binary uninstalled");
6454
+ logger.warn("\u26A0 Current terminal still has mimir cached");
6455
+ logger.info(" Run: hash -r (to clear shell cache)");
6456
+ }
6457
+ }
6458
+ }
6459
+ async spawnWindowsCleanup(binPath, binDir, quiet) {
6460
+ if (!this.executor) {
6461
+ if (!quiet) {
6462
+ logger.warn("Cannot spawn cleanup process - no executor available");
6463
+ }
6464
+ return;
6465
+ }
6466
+ try {
6467
+ const cleanupScript = `@echo off
6468
+ REM Wait for parent process to exit
6469
+ timeout /t 3 /nobreak >nul 2>&1
6470
+
6471
+ REM Delete binary if it exists
6472
+ if exist "${binPath}" (
6473
+ del /f /q "${binPath}" >nul 2>&1
6474
+ )
6475
+ if exist "${binPath}.exe" (
6476
+ del /f /q "${binPath}.exe" >nul 2>&1
6477
+ )
6478
+ if exist "${binPath}.cmd" (
6479
+ del /f /q "${binPath}.cmd" >nul 2>&1
6480
+ )
6481
+
6482
+ REM Delete binary directory
6483
+ if exist "${binDir}" (
6484
+ rmdir /s /q "${binDir}" >nul 2>&1
6485
+ )
6486
+
6487
+ REM Delete this cleanup script
6488
+ del /f /q "%~f0" >nul 2>&1
6489
+ `;
6490
+ const tempDir = os6.tmpdir();
6491
+ const cleanupPath = path8.join(tempDir, `mimir-cleanup-${Date.now()}.bat`);
6492
+ await this.fs.writeFile(cleanupPath, cleanupScript);
6493
+ const { spawn } = await import("child_process");
6494
+ const child = spawn("cmd", ["/c", cleanupPath], {
6495
+ detached: true,
6496
+ stdio: "ignore",
6497
+ windowsHide: true
6498
+ });
6499
+ child.unref();
6500
+ if (!quiet) {
6501
+ logger.info("Spawned background cleanup process");
6502
+ }
6503
+ } catch (error) {
6504
+ if (!quiet) {
6505
+ logger.error("Failed to spawn cleanup process", { error });
6506
+ }
6507
+ }
6508
+ }
6509
+ async removeFromWindowsPath(quiet) {
6510
+ if (!this.executor) return;
6511
+ try {
6512
+ const result = await this.executor.execute(
6513
+ "powershell",
6514
+ ["-NoProfile", "-Command", '[Environment]::GetEnvironmentVariable("Path", "User")'],
6515
+ { cwd: process.cwd() }
6516
+ );
6517
+ if (result.exitCode !== 0) {
6518
+ throw new Error("Failed to read PATH");
6519
+ }
6520
+ const currentPath = result.stdout.trim();
6521
+ const pathEntries = currentPath.split(";").filter(Boolean);
6522
+ const filteredEntries = pathEntries.filter((entry) => {
6523
+ const normalizedEntry = path8.normalize(entry.trim()).toLowerCase();
6524
+ return !normalizedEntry.includes("mimir");
6525
+ });
6526
+ if (filteredEntries.length < pathEntries.length) {
6527
+ const newPath = filteredEntries.join(";");
6528
+ await this.executor.execute(
6529
+ "powershell",
6530
+ [
6531
+ "-NoProfile",
6532
+ "-Command",
6533
+ `[Environment]::SetEnvironmentVariable("Path", "${newPath}", "User")`
6534
+ ],
6535
+ { cwd: process.cwd() }
6536
+ );
6537
+ const removedCount = pathEntries.length - filteredEntries.length;
6538
+ if (!quiet) {
6539
+ logger.info(`Removed ${removedCount} PATH entry/entries containing 'mimir'`);
6540
+ }
6541
+ }
6542
+ } catch (error) {
6543
+ if (!quiet) {
6544
+ logger.warn("Failed to remove from PATH", { error });
6545
+ logger.info("You may need to manually remove from PATH");
6546
+ }
6547
+ }
6548
+ }
6549
+ async removeGlobalConfig(homeDir, result, quiet = false) {
6550
+ const mimirDir = path8.join(homeDir, ".mimir");
6551
+ if (await this.fs.exists(mimirDir)) {
6552
+ await this.fs.rmdir(mimirDir, { recursive: true });
6553
+ result.removed.push("~/.mimir/");
6554
+ if (!quiet) {
6555
+ logger.info("Removed configuration directory: ~/.mimir");
6556
+ logger.info("All Mimir data has been deleted.");
6557
+ }
6558
+ }
6559
+ }
6560
+ /* eslint-disable no-console */
6561
+ printSummary(result) {
6562
+ console.log("");
6563
+ console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
6564
+ if (result.success) {
6565
+ console.log("\u2705 Mimir has been uninstalled");
6566
+ } else {
6567
+ console.log("\u274C Uninstall completed with errors");
6568
+ }
6569
+ console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
6570
+ if (result.removed.length > 0) {
6571
+ console.log("");
6572
+ console.log("Removed:");
6573
+ result.removed.forEach((item) => console.log(` - ${item}`));
6574
+ }
6575
+ if (result.keepConfig) {
6576
+ console.log("");
6577
+ console.log("Configuration preserved:");
6578
+ console.log(" - ~/.mimir/ (your settings and data)");
6579
+ console.log("");
6580
+ console.log("To remove it later, run:");
6581
+ console.log(" mimir uninstall --yes --remove-config");
6582
+ console.log(" or manually: rm -rf ~/.mimir");
6583
+ }
6584
+ if (result.errors.length > 0) {
6585
+ console.log("");
6586
+ console.log("Errors:");
6587
+ result.errors.forEach((error) => console.log(` - ${error}`));
6588
+ }
6589
+ console.log("");
6590
+ console.log("Thank you for using Mimir! \u{1F44B}");
6591
+ console.log("");
6592
+ }
6593
+ /* eslint-enable no-console */
6594
+ };
6595
+
6061
6596
  // src/cli.ts
6062
6597
  var fs3 = new FileSystemAdapter();
6598
+ var executor = new ProcessExecutorAdapter();
6063
6599
  var configLoader = new ConfigLoader(fs3);
6064
6600
  var firstRunDetector = new FirstRunDetector(fs3);
6065
6601
  var setupCommand = new SetupCommand(configLoader);
6066
6602
  var chatCommand = new ChatCommand(configLoader, firstRunDetector, setupCommand, fs3);
6067
6603
  var initCommand = new InitCommand(fs3, configLoader);
6604
+ var uninstallCommand = new UninstallCommand(fs3, executor);
6068
6605
  var program = new Command();
6069
6606
  program.name("mimir").description("Platform-agnostic, BYOK AI coding agent CLI").version("0.1.0");
6070
6607
  program.command("setup").description("Run setup wizard").action(async () => {
@@ -6079,6 +6616,12 @@ program.command("init").description("Initialize Mimir in current project").optio
6079
6616
  await initCommand.execute(void 0, options);
6080
6617
  process.exit(0);
6081
6618
  });
6619
+ program.command("uninstall").description("Uninstall Mimir from your system").option("-y, --yes", "Skip confirmation prompts").option("--keep-config", "Keep configuration directory (~/.mimir)").option("--remove-config", "Remove configuration directory (~/.mimir)").option("-q, --quiet", "Suppress output (implies --yes)").action(
6620
+ async (options) => {
6621
+ await uninstallCommand.execute(options);
6622
+ process.exit(0);
6623
+ }
6624
+ );
6082
6625
  var history = program.command("history").description("Manage conversation history");
6083
6626
  history.command("list").description("List recent conversations").action(() => {
6084
6627
  logger.warn("Listing conversations... (not implemented yet)");