@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.
- package/README.md +33 -0
- package/agents/mink-agent.md.tmpl +84 -0
- package/dashboard/out/404.html +1 -1
- package/dashboard/out/action-log.html +1 -1
- package/dashboard/out/action-log.txt +1 -1
- package/dashboard/out/activity.html +1 -1
- package/dashboard/out/activity.txt +1 -1
- package/dashboard/out/bugs.html +1 -1
- package/dashboard/out/bugs.txt +1 -1
- package/dashboard/out/capture.html +1 -1
- package/dashboard/out/capture.txt +1 -1
- package/dashboard/out/config.html +1 -1
- package/dashboard/out/config.txt +1 -1
- package/dashboard/out/daemon.html +1 -1
- package/dashboard/out/daemon.txt +1 -1
- package/dashboard/out/design.html +1 -1
- package/dashboard/out/design.txt +1 -1
- package/dashboard/out/discord.html +1 -1
- package/dashboard/out/discord.txt +1 -1
- package/dashboard/out/file-index.html +1 -1
- package/dashboard/out/file-index.txt +1 -1
- package/dashboard/out/index.html +1 -1
- package/dashboard/out/index.txt +1 -1
- package/dashboard/out/insights.html +1 -1
- package/dashboard/out/insights.txt +1 -1
- package/dashboard/out/learning.html +1 -1
- package/dashboard/out/learning.txt +1 -1
- package/dashboard/out/overview.html +1 -1
- package/dashboard/out/overview.txt +1 -1
- package/dashboard/out/scheduler.html +1 -1
- package/dashboard/out/scheduler.txt +1 -1
- package/dashboard/out/sync.html +1 -1
- package/dashboard/out/sync.txt +1 -1
- package/dashboard/out/tokens.html +1 -1
- package/dashboard/out/tokens.txt +1 -1
- package/dashboard/out/waste.html +1 -1
- package/dashboard/out/waste.txt +1 -1
- package/dashboard/out/wiki.html +1 -1
- package/dashboard/out/wiki.txt +1 -1
- package/dist/cli.js +784 -372
- package/package.json +2 -1
- package/src/cli.ts +8 -1
- package/src/commands/agent.ts +245 -0
- package/src/commands/daemon.ts +12 -1
- package/src/commands/skill.ts +16 -1
- package/src/core/daemon-service.ts +227 -0
- /package/dashboard/out/_next/static/{Dw8C--0lGz5BIGsnG-e5H → IWTIkvB7I3-GawTXJW4-9}/_buildManifest.js +0 -0
- /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.
|
|
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
|
+
}
|
package/src/commands/daemon.ts
CHANGED
|
@@ -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(
|
|
52
|
+
console.error(
|
|
53
|
+
"Usage: mink daemon <start|stop|restart|logs|install|uninstall>"
|
|
54
|
+
);
|
|
44
55
|
process.exit(1);
|
|
45
56
|
}
|
|
46
57
|
}
|
package/src/commands/skill.ts
CHANGED
|
@@ -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
|
-
//
|
|
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, "&")
|
|
136
|
+
.replace(/</g, "<")
|
|
137
|
+
.replace(/>/g, ">")
|
|
138
|
+
.replace(/"/g, """);
|
|
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
|
+
}
|
|
File without changes
|
/package/dashboard/out/_next/static/{Dw8C--0lGz5BIGsnG-e5H → IWTIkvB7I3-GawTXJW4-9}/_ssgManifest.js
RENAMED
|
File without changes
|