@drewpayment/mink 0.6.0 → 0.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.
Files changed (48) hide show
  1. package/README.md +33 -0
  2. package/agents/mink-agent.md.tmpl +84 -0
  3. package/dashboard/out/404.html +1 -1
  4. package/dashboard/out/action-log.html +1 -1
  5. package/dashboard/out/action-log.txt +1 -1
  6. package/dashboard/out/activity.html +1 -1
  7. package/dashboard/out/activity.txt +1 -1
  8. package/dashboard/out/bugs.html +1 -1
  9. package/dashboard/out/bugs.txt +1 -1
  10. package/dashboard/out/capture.html +1 -1
  11. package/dashboard/out/capture.txt +1 -1
  12. package/dashboard/out/config.html +1 -1
  13. package/dashboard/out/config.txt +1 -1
  14. package/dashboard/out/daemon.html +1 -1
  15. package/dashboard/out/daemon.txt +1 -1
  16. package/dashboard/out/design.html +1 -1
  17. package/dashboard/out/design.txt +1 -1
  18. package/dashboard/out/discord.html +1 -1
  19. package/dashboard/out/discord.txt +1 -1
  20. package/dashboard/out/file-index.html +1 -1
  21. package/dashboard/out/file-index.txt +1 -1
  22. package/dashboard/out/index.html +1 -1
  23. package/dashboard/out/index.txt +1 -1
  24. package/dashboard/out/insights.html +1 -1
  25. package/dashboard/out/insights.txt +1 -1
  26. package/dashboard/out/learning.html +1 -1
  27. package/dashboard/out/learning.txt +1 -1
  28. package/dashboard/out/overview.html +1 -1
  29. package/dashboard/out/overview.txt +1 -1
  30. package/dashboard/out/scheduler.html +1 -1
  31. package/dashboard/out/scheduler.txt +1 -1
  32. package/dashboard/out/sync.html +1 -1
  33. package/dashboard/out/sync.txt +1 -1
  34. package/dashboard/out/tokens.html +1 -1
  35. package/dashboard/out/tokens.txt +1 -1
  36. package/dashboard/out/waste.html +1 -1
  37. package/dashboard/out/waste.txt +1 -1
  38. package/dashboard/out/wiki.html +1 -1
  39. package/dashboard/out/wiki.txt +1 -1
  40. package/dist/cli.js +784 -372
  41. package/package.json +2 -1
  42. package/src/cli.ts +8 -1
  43. package/src/commands/agent.ts +245 -0
  44. package/src/commands/daemon.ts +12 -1
  45. package/src/commands/skill.ts +16 -1
  46. package/src/core/daemon-service.ts +227 -0
  47. /package/dashboard/out/_next/static/{Dw8C--0lGz5BIGsnG-e5H → IWTIkvB7I3-GawTXJW4-9}/_buildManifest.js +0 -0
  48. /package/dashboard/out/_next/static/{Dw8C--0lGz5BIGsnG-e5H → IWTIkvB7I3-GawTXJW4-9}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drewpayment/mink",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "A hidden presence that moves alongside the developer — token efficiency and cross-project wiki for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,6 +19,7 @@
19
19
  "src/**/*.ts",
20
20
  "dist/cli.js",
21
21
  "skills/**/*",
22
+ "agents/**/*",
22
23
  "dashboard/out"
23
24
  ],
24
25
  "publishConfig": {
package/src/cli.ts CHANGED
@@ -143,6 +143,12 @@ switch (command) {
143
143
  break;
144
144
  }
145
145
 
146
+ case "agent": {
147
+ const { agent } = await import("./commands/agent");
148
+ await agent(cwd, process.argv.slice(3));
149
+ break;
150
+ }
151
+
146
152
  case "sync": {
147
153
  const { sync } = await import("./commands/sync");
148
154
  await sync(process.argv.slice(3));
@@ -213,6 +219,7 @@ switch (command) {
213
219
  console.log(" note list [filters] List notes (--category, --tag, --recent)");
214
220
  console.log(" note search <term> Full-text search across the vault");
215
221
  console.log(" skill install Install /mink:note skill for Claude Code");
222
+ console.log(" agent Open a Claude Code session with the mink-agent persona");
216
223
  console.log();
217
224
  console.log("Devices & Sync:");
218
225
  console.log(" device Show current device info");
@@ -235,7 +242,7 @@ switch (command) {
235
242
  console.log();
236
243
  console.log("Automation & Analysis:");
237
244
  console.log(" dashboard [--port=N] Open the real-time web dashboard");
238
- console.log(" daemon <cmd> Manage the background daemon (start|stop|restart|logs)");
245
+ console.log(" daemon <cmd> Manage the background daemon (start|stop|restart|logs|install|uninstall)");
239
246
  console.log(" cron <cmd> [id] Manage scheduled tasks (list|run|retry)");
240
247
  console.log(" update [options] Update Mink across registered projects");
241
248
  console.log(" restore [backup] Restore state from a backup");
@@ -0,0 +1,245 @@
1
+ import { join, resolve, dirname } from "path";
2
+ import { homedir } from "os";
3
+ import {
4
+ existsSync,
5
+ mkdirSync,
6
+ readFileSync,
7
+ writeFileSync,
8
+ } from "fs";
9
+ import { createHash } from "crypto";
10
+ import { spawnSync } from "child_process";
11
+ import { minkRoot } from "../core/paths";
12
+ import { resolveVaultPath } from "../core/vault";
13
+
14
+ const AGENT_NAME = "mink-agent";
15
+ const TEMPLATE_FILE = `${AGENT_NAME}.md.tmpl`;
16
+ const INSTALLED_FILE = `${AGENT_NAME}.md`;
17
+
18
+ function getAgentTemplatePath(): string {
19
+ let dir = dirname(new URL(import.meta.url).pathname);
20
+ while (true) {
21
+ if (
22
+ existsSync(join(dir, "package.json")) &&
23
+ existsSync(join(dir, "agents", TEMPLATE_FILE))
24
+ ) {
25
+ return join(dir, "agents", TEMPLATE_FILE);
26
+ }
27
+ const parent = dirname(dir);
28
+ if (parent === dir) break;
29
+ dir = parent;
30
+ }
31
+ return resolve(
32
+ dirname(new URL(import.meta.url).pathname),
33
+ "../../agents",
34
+ TEMPLATE_FILE
35
+ );
36
+ }
37
+
38
+ function getMinkVersion(): string {
39
+ let dir = dirname(new URL(import.meta.url).pathname);
40
+ while (true) {
41
+ const pkgPath = join(dir, "package.json");
42
+ if (existsSync(pkgPath)) {
43
+ try {
44
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
45
+ if (pkg.name && pkg.version) return pkg.version;
46
+ } catch {
47
+ // fall through
48
+ }
49
+ }
50
+ const parent = dirname(dir);
51
+ if (parent === dir) break;
52
+ dir = parent;
53
+ }
54
+ return "unknown";
55
+ }
56
+
57
+ function renderTemplate(template: string, vars: Record<string, string>): string {
58
+ let out = template;
59
+ for (const [key, value] of Object.entries(vars)) {
60
+ out = out.split(`{{${key}}}`).join(value);
61
+ }
62
+ return out;
63
+ }
64
+
65
+ function sha256(text: string): string {
66
+ return createHash("sha256").update(text).digest("hex");
67
+ }
68
+
69
+ function claudeAgentsDir(): string {
70
+ return join(homedir(), ".claude", "agents");
71
+ }
72
+
73
+ function installedAgentPath(): string {
74
+ return join(claudeAgentsDir(), INSTALLED_FILE);
75
+ }
76
+
77
+ interface InstallResult {
78
+ action: "installed" | "updated" | "unchanged" | "skipped";
79
+ path: string;
80
+ }
81
+
82
+ function installAgentDefinition(opts: { force: boolean; skip: boolean }): InstallResult {
83
+ const templatePath = getAgentTemplatePath();
84
+ if (!existsSync(templatePath)) {
85
+ throw new Error(
86
+ `[mink agent] bundled agent template not found at ${templatePath}\n` +
87
+ " This usually means the package was installed without bundled assets."
88
+ );
89
+ }
90
+
91
+ const installed = installedAgentPath();
92
+
93
+ if (opts.skip && existsSync(installed)) {
94
+ return { action: "skipped", path: installed };
95
+ }
96
+
97
+ const template = readFileSync(templatePath, "utf-8");
98
+ const rendered = renderTemplate(template, {
99
+ MINK_ROOT: minkRoot(),
100
+ VAULT_PATH: resolveVaultPath(),
101
+ MINK_VERSION: getMinkVersion(),
102
+ });
103
+
104
+ const exists = existsSync(installed);
105
+ if (!opts.force && exists) {
106
+ const current = readFileSync(installed, "utf-8");
107
+ if (sha256(current) === sha256(rendered)) {
108
+ return { action: "unchanged", path: installed };
109
+ }
110
+ }
111
+
112
+ mkdirSync(claudeAgentsDir(), { recursive: true });
113
+ writeFileSync(installed, rendered);
114
+ return {
115
+ action: exists ? "updated" : "installed",
116
+ path: installed,
117
+ };
118
+ }
119
+
120
+ function isClaudeOnPath(): boolean {
121
+ const result = spawnSync("claude", ["--version"], {
122
+ stdio: "ignore",
123
+ });
124
+ return !result.error && result.status === 0;
125
+ }
126
+
127
+ interface ParsedArgs {
128
+ noUpdate: boolean;
129
+ reinstall: boolean;
130
+ passthrough: string[];
131
+ showHelp: boolean;
132
+ }
133
+
134
+ function parseArgs(args: string[]): ParsedArgs {
135
+ const out: ParsedArgs = {
136
+ noUpdate: false,
137
+ reinstall: false,
138
+ passthrough: [],
139
+ showHelp: false,
140
+ };
141
+ let inPassthrough = false;
142
+ for (const arg of args) {
143
+ if (inPassthrough) {
144
+ out.passthrough.push(arg);
145
+ continue;
146
+ }
147
+ if (arg === "--") {
148
+ inPassthrough = true;
149
+ continue;
150
+ }
151
+ if (arg === "--no-update") {
152
+ out.noUpdate = true;
153
+ continue;
154
+ }
155
+ if (arg === "--reinstall") {
156
+ out.reinstall = true;
157
+ continue;
158
+ }
159
+ if (arg === "--help" || arg === "-h") {
160
+ out.showHelp = true;
161
+ continue;
162
+ }
163
+ out.passthrough.push(arg);
164
+ }
165
+ return out;
166
+ }
167
+
168
+ function printHelp(): void {
169
+ console.log("Usage: mink agent [options] [-- <claude args...>]");
170
+ console.log();
171
+ console.log("Open an interactive Claude Code session in your mink home with");
172
+ console.log("the mink-agent persona — a proactive note/wiki assistant.");
173
+ console.log();
174
+ console.log("Options:");
175
+ console.log(" --no-update Don't refresh ~/.claude/agents/mink-agent.md if it exists");
176
+ console.log(" --reinstall Force overwrite the installed agent definition");
177
+ console.log(" -- <args> Forward remaining arguments to `claude`");
178
+ console.log();
179
+ console.log("Environment:");
180
+ console.log(" MINK_AGENT_NO_UPDATE=1 Equivalent to --no-update");
181
+ console.log();
182
+ console.log("The agent is bound to your mink root and resolved vault path. Changing");
183
+ console.log("`mink config wiki.path` triggers a refresh on the next launch.");
184
+ }
185
+
186
+ export async function agent(_cwd: string, rawArgs: string[]): Promise<void> {
187
+ const args = parseArgs(rawArgs);
188
+
189
+ if (args.showHelp) {
190
+ printHelp();
191
+ return;
192
+ }
193
+
194
+ const skipUpdate = args.noUpdate || process.env.MINK_AGENT_NO_UPDATE === "1";
195
+
196
+ const root = minkRoot();
197
+ if (!existsSync(root)) {
198
+ mkdirSync(root, { recursive: true });
199
+ }
200
+
201
+ let result: InstallResult;
202
+ try {
203
+ result = installAgentDefinition({
204
+ force: args.reinstall,
205
+ skip: skipUpdate && !args.reinstall,
206
+ });
207
+ } catch (err) {
208
+ const msg = err instanceof Error ? err.message : String(err);
209
+ console.error(msg);
210
+ process.exit(1);
211
+ }
212
+
213
+ switch (result.action) {
214
+ case "installed":
215
+ console.log(`[mink] installed mink-agent definition (v${getMinkVersion()}) -> ${result.path}`);
216
+ break;
217
+ case "updated":
218
+ console.log(`[mink] updated mink-agent definition -> ${result.path}`);
219
+ break;
220
+ case "unchanged":
221
+ case "skipped":
222
+ // silent
223
+ break;
224
+ }
225
+
226
+ if (!isClaudeOnPath()) {
227
+ console.error("[mink agent] `claude` (Claude Code CLI) was not found on PATH.");
228
+ console.error(" Install Claude Code: https://claude.com/claude-code");
229
+ process.exit(1);
230
+ }
231
+
232
+ const claudeArgs = ["--agent", AGENT_NAME, ...args.passthrough];
233
+ const child = spawnSync("claude", claudeArgs, {
234
+ cwd: root,
235
+ stdio: "inherit",
236
+ });
237
+
238
+ if (child.error) {
239
+ console.error(`[mink agent] failed to launch claude: ${child.error.message}`);
240
+ process.exit(1);
241
+ }
242
+ if (typeof child.status === "number") {
243
+ process.exit(child.status);
244
+ }
245
+ }
@@ -1,5 +1,6 @@
1
1
  import { readFileSync, existsSync } from "fs";
2
2
  import { startDaemon, stopDaemon } from "../core/daemon";
3
+ import { installService, uninstallService } from "../core/daemon-service";
3
4
  import { schedulerLogPath } from "../core/paths";
4
5
 
5
6
  export async function daemon(cwd: string, args: string[]): Promise<void> {
@@ -36,11 +37,21 @@ export async function daemon(cwd: string, args: string[]): Promise<void> {
36
37
  break;
37
38
  }
38
39
 
40
+ case "install":
41
+ installService({ force: args.includes("--force") });
42
+ break;
43
+
44
+ case "uninstall":
45
+ uninstallService();
46
+ break;
47
+
39
48
  default:
40
49
  console.error(
41
50
  `[mink] unknown daemon subcommand: ${subcommand ?? "(none)"}`
42
51
  );
43
- console.error("Usage: mink daemon <start|stop|restart|logs>");
52
+ console.error(
53
+ "Usage: mink daemon <start|stop|restart|logs|install|uninstall>"
54
+ );
44
55
  process.exit(1);
45
56
  }
46
57
  }
@@ -17,7 +17,22 @@ const AGENTS_SKILLS_DIR = join(homedir(), ".agents", "skills");
17
17
  const CLAUDE_SKILLS_DIR = join(homedir(), ".claude", "skills");
18
18
 
19
19
  function getSkillsSourceDir(): string {
20
- // Skills live at repo-root/skills/ (the standard skills/{name}/SKILL.md layout)
20
+ // Walk up from this file until we find a directory that contains both
21
+ // package.json and skills/ — works from src/commands/skill.ts (dev) and
22
+ // from dist/cli.js (bundled + installed as a package), which sit at
23
+ // different depths relative to the package root.
24
+ let dir = dirname(new URL(import.meta.url).pathname);
25
+ while (true) {
26
+ if (
27
+ existsSync(join(dir, "package.json")) &&
28
+ existsSync(join(dir, "skills"))
29
+ ) {
30
+ return join(dir, "skills");
31
+ }
32
+ const parent = dirname(dir);
33
+ if (parent === dir) break;
34
+ dir = parent;
35
+ }
21
36
  return resolve(
22
37
  dirname(new URL(import.meta.url).pathname),
23
38
  "../../skills"
@@ -0,0 +1,227 @@
1
+ import { execSync } from "child_process";
2
+ import { existsSync, mkdirSync, unlinkSync, writeFileSync } from "fs";
3
+ import { homedir } from "os";
4
+ import { dirname, join, resolve } from "path";
5
+ import { resolveCliPath } from "../commands/init";
6
+
7
+ export type ServicePlatform = "systemd" | "launchd";
8
+
9
+ export interface ServiceInvocation {
10
+ /** Absolute path to the executable used in ExecStart / ProgramArguments[0]. */
11
+ executable: string;
12
+ /** Arguments following the executable (e.g. ["daemon", "start"] or ["<cli.js>", "daemon", "start"]). */
13
+ args: string[];
14
+ /** Directory that should be added to PATH for the service's environment. */
15
+ pathDir: string;
16
+ }
17
+
18
+ export interface ServicePaths {
19
+ unitFile: string;
20
+ unitDir: string;
21
+ }
22
+
23
+ export function detectPlatform(): ServicePlatform | null {
24
+ if (process.platform === "linux") return "systemd";
25
+ if (process.platform === "darwin") return "launchd";
26
+ return null;
27
+ }
28
+
29
+ /**
30
+ * Resolve how the service should invoke mink.
31
+ *
32
+ * Prefer argv[1] when it is a bin shim (no .js/.ts extension) — that is the
33
+ * stable, install-method-agnostic entry point (e.g. ~/.bun/bin/mink). Fall
34
+ * back to invoking the compiled bundle via the current interpreter when
35
+ * running from source or from a non-shim entry.
36
+ */
37
+ export function resolveServiceInvocation(): ServiceInvocation {
38
+ const entry = process.argv[1];
39
+ if (entry && !/\.(js|ts|mjs|cjs)$/.test(entry) && existsSync(entry)) {
40
+ return {
41
+ executable: entry,
42
+ args: ["daemon", "start"],
43
+ pathDir: dirname(entry),
44
+ };
45
+ }
46
+
47
+ const cliPath = resolveCliPath();
48
+ const interpreter = process.execPath;
49
+ return {
50
+ executable: interpreter,
51
+ args: [cliPath, "daemon", "start"],
52
+ pathDir: dirname(interpreter),
53
+ };
54
+ }
55
+
56
+ export function servicePaths(platform: ServicePlatform): ServicePaths {
57
+ const home = homedir();
58
+ if (platform === "systemd") {
59
+ const unitDir = join(home, ".config", "systemd", "user");
60
+ return { unitDir, unitFile: join(unitDir, "mink-daemon.service") };
61
+ }
62
+ const unitDir = join(home, "Library", "LaunchAgents");
63
+ return { unitDir, unitFile: join(unitDir, "com.mink.daemon.plist") };
64
+ }
65
+
66
+ /** Build a systemd user unit file for the mink daemon. */
67
+ export function renderSystemdUnit(inv: ServiceInvocation): string {
68
+ const execStart = [inv.executable, ...inv.args].join(" ");
69
+ const stopArgs = inv.args.map((a) => (a === "start" ? "stop" : a));
70
+ const execStop = [inv.executable, ...stopArgs].join(" ");
71
+ const pathEnv = `${inv.pathDir}:/usr/local/bin:/usr/bin:/bin`;
72
+
73
+ return [
74
+ "[Unit]",
75
+ "Description=Mink background daemon",
76
+ "After=network-online.target",
77
+ "Wants=network-online.target",
78
+ "",
79
+ "[Service]",
80
+ "Type=forking",
81
+ `ExecStart=${execStart}`,
82
+ `ExecStop=${execStop}`,
83
+ "Restart=on-failure",
84
+ "RestartSec=10",
85
+ `Environment="PATH=${pathEnv}"`,
86
+ "",
87
+ "[Install]",
88
+ "WantedBy=default.target",
89
+ "",
90
+ ].join("\n");
91
+ }
92
+
93
+ /** Build a launchd user agent plist for the mink daemon. */
94
+ export function renderLaunchdPlist(inv: ServiceInvocation, logPath: string): string {
95
+ const programArgs = [inv.executable, ...inv.args]
96
+ .map((a) => ` <string>${escapeXml(a)}</string>`)
97
+ .join("\n");
98
+ const pathEnv = `${inv.pathDir}:/usr/local/bin:/usr/bin:/bin`;
99
+
100
+ return [
101
+ '<?xml version="1.0" encoding="UTF-8"?>',
102
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
103
+ '<plist version="1.0">',
104
+ "<dict>",
105
+ " <key>Label</key>",
106
+ " <string>com.mink.daemon</string>",
107
+ " <key>ProgramArguments</key>",
108
+ " <array>",
109
+ programArgs,
110
+ " </array>",
111
+ " <key>RunAtLoad</key>",
112
+ " <true/>",
113
+ " <key>KeepAlive</key>",
114
+ " <dict>",
115
+ " <key>SuccessfulExit</key>",
116
+ " <false/>",
117
+ " </dict>",
118
+ " <key>EnvironmentVariables</key>",
119
+ " <dict>",
120
+ " <key>PATH</key>",
121
+ ` <string>${escapeXml(pathEnv)}</string>`,
122
+ " </dict>",
123
+ " <key>StandardOutPath</key>",
124
+ ` <string>${escapeXml(logPath)}</string>`,
125
+ " <key>StandardErrorPath</key>",
126
+ ` <string>${escapeXml(logPath)}</string>`,
127
+ "</dict>",
128
+ "</plist>",
129
+ "",
130
+ ].join("\n");
131
+ }
132
+
133
+ function escapeXml(s: string): string {
134
+ return s
135
+ .replace(/&/g, "&amp;")
136
+ .replace(/</g, "&lt;")
137
+ .replace(/>/g, "&gt;")
138
+ .replace(/"/g, "&quot;");
139
+ }
140
+
141
+ export interface InstallOptions {
142
+ force?: boolean;
143
+ }
144
+
145
+ export function installService(options: InstallOptions = {}): void {
146
+ const platform = detectPlatform();
147
+ if (!platform) {
148
+ console.error(
149
+ `[mink] daemon install is not supported on ${process.platform} (supported: linux, darwin)`
150
+ );
151
+ process.exit(1);
152
+ }
153
+
154
+ const paths = servicePaths(platform);
155
+ if (existsSync(paths.unitFile) && !options.force) {
156
+ console.error(`[mink] unit file already exists: ${paths.unitFile}`);
157
+ console.error(
158
+ " re-run with --force to overwrite, or run `mink daemon uninstall` first"
159
+ );
160
+ process.exit(1);
161
+ }
162
+
163
+ const inv = resolveServiceInvocation();
164
+ mkdirSync(paths.unitDir, { recursive: true });
165
+
166
+ if (platform === "systemd") {
167
+ writeFileSync(paths.unitFile, renderSystemdUnit(inv));
168
+ try {
169
+ execSync("systemctl --user daemon-reload", { stdio: "ignore" });
170
+ } catch {
171
+ // systemctl may be unavailable (e.g. CI, WSL1) — the file is still written.
172
+ }
173
+ console.log(`[mink] wrote ${paths.unitFile}`);
174
+ console.log("[mink] next steps:");
175
+ console.log(" systemctl --user enable --now mink-daemon.service");
176
+ console.log(" # To survive logout (one-time, requires sudo):");
177
+ console.log(` sudo loginctl enable-linger ${process.env.USER ?? "$USER"}`);
178
+ } else {
179
+ const { schedulerLogPath } = require("./paths") as typeof import("./paths");
180
+ writeFileSync(paths.unitFile, renderLaunchdPlist(inv, schedulerLogPath()));
181
+ console.log(`[mink] wrote ${paths.unitFile}`);
182
+ console.log("[mink] next steps:");
183
+ console.log(` launchctl load -w ${paths.unitFile}`);
184
+ console.log(" # Launch agents run automatically on login; no lingering needed.");
185
+ }
186
+ }
187
+
188
+ export function uninstallService(): void {
189
+ const platform = detectPlatform();
190
+ if (!platform) {
191
+ console.error(
192
+ `[mink] daemon uninstall is not supported on ${process.platform} (supported: linux, darwin)`
193
+ );
194
+ process.exit(1);
195
+ }
196
+
197
+ const paths = servicePaths(platform);
198
+ if (!existsSync(paths.unitFile)) {
199
+ console.log(`[mink] no unit file at ${paths.unitFile} — nothing to uninstall`);
200
+ return;
201
+ }
202
+
203
+ if (platform === "systemd") {
204
+ try {
205
+ execSync("systemctl --user disable --now mink-daemon.service", {
206
+ stdio: "ignore",
207
+ });
208
+ } catch {
209
+ // Service may not be enabled / running — proceed to file removal.
210
+ }
211
+ unlinkSync(paths.unitFile);
212
+ try {
213
+ execSync("systemctl --user daemon-reload", { stdio: "ignore" });
214
+ } catch {
215
+ // Ignore.
216
+ }
217
+ console.log(`[mink] removed ${paths.unitFile}`);
218
+ } else {
219
+ try {
220
+ execSync(`launchctl unload -w ${paths.unitFile}`, { stdio: "ignore" });
221
+ } catch {
222
+ // Ignore — may not be loaded.
223
+ }
224
+ unlinkSync(paths.unitFile);
225
+ console.log(`[mink] removed ${paths.unitFile}`);
226
+ }
227
+ }