@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 +572 -29
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
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(
|
|
9
|
-
return fs.readFile(
|
|
10
|
+
async readFile(path9, encoding = "utf-8") {
|
|
11
|
+
return fs.readFile(path9, encoding);
|
|
10
12
|
}
|
|
11
|
-
async writeFile(
|
|
12
|
-
await fs.writeFile(
|
|
13
|
+
async writeFile(path9, content, encoding = "utf-8") {
|
|
14
|
+
await fs.writeFile(path9, content, encoding);
|
|
13
15
|
}
|
|
14
|
-
async exists(
|
|
16
|
+
async exists(path9) {
|
|
15
17
|
try {
|
|
16
|
-
await fs.access(
|
|
18
|
+
await fs.access(path9);
|
|
17
19
|
return true;
|
|
18
20
|
} catch {
|
|
19
21
|
return false;
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
|
-
async mkdir(
|
|
23
|
-
await fs.mkdir(
|
|
24
|
+
async mkdir(path9, options) {
|
|
25
|
+
await fs.mkdir(path9, options);
|
|
24
26
|
}
|
|
25
|
-
async readdir(
|
|
26
|
-
return fs.readdir(
|
|
27
|
+
async readdir(path9) {
|
|
28
|
+
return fs.readdir(path9);
|
|
27
29
|
}
|
|
28
|
-
async stat(
|
|
29
|
-
const stats = await fs.stat(
|
|
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(
|
|
38
|
-
await fs.unlink(
|
|
39
|
+
async unlink(path9) {
|
|
40
|
+
await fs.unlink(path9);
|
|
39
41
|
}
|
|
40
|
-
async rmdir(
|
|
41
|
-
await fs.rmdir(
|
|
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
|
-
` + [...
|
|
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)");
|