@drewpayment/mink 0.2.1 → 0.3.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/dist/cli.js +1019 -530
- package/package.json +3 -2
- package/src/cli.ts +16 -1
- package/src/commands/session-start.ts +10 -0
- package/src/commands/session-stop.ts +4 -59
- package/src/commands/sync.ts +111 -0
- package/src/commands/wiki.ts +106 -1
- package/src/core/sync.ts +333 -0
- package/src/core/vault.ts +82 -3
- package/src/types/config.ts +30 -2
- package/src/types/note.ts +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drewpayment/mink",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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": {
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"src/**/*.ts",
|
|
20
20
|
"dist/cli.js",
|
|
21
|
-
"skills/**/*"
|
|
21
|
+
"skills/**/*",
|
|
22
|
+
"dashboard/out"
|
|
22
23
|
],
|
|
23
24
|
"publishConfig": {
|
|
24
25
|
"access": "public"
|
package/src/cli.ts
CHANGED
|
@@ -137,6 +137,12 @@ switch (command) {
|
|
|
137
137
|
break;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
case "sync": {
|
|
141
|
+
const { sync } = await import("./commands/sync");
|
|
142
|
+
await sync(process.argv.slice(3));
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
|
|
140
146
|
case "bug-search": {
|
|
141
147
|
const { bugSearch } = await import("./commands/bug-search");
|
|
142
148
|
bugSearch(cwd, process.argv.slice(3).join(" "));
|
|
@@ -189,13 +195,22 @@ switch (command) {
|
|
|
189
195
|
console.log(" config [key] [value] Manage global user settings");
|
|
190
196
|
console.log();
|
|
191
197
|
console.log("Notes & Wiki:");
|
|
192
|
-
console.log(" wiki <cmd> Manage the notes/wiki vault (init|status|rebuild-index|organize)");
|
|
198
|
+
console.log(" wiki <cmd> Manage the notes/wiki vault (init|status|link|unlink|links|rebuild-index|organize)");
|
|
193
199
|
console.log(" note \"text\" Capture a note to the vault");
|
|
194
200
|
console.log(" note --daily [text] Create or append to today's daily note");
|
|
195
201
|
console.log(" note list [filters] List notes (--category, --tag, --recent)");
|
|
196
202
|
console.log(" note search <term> Full-text search across the vault");
|
|
197
203
|
console.log(" skill install Install /mink:note skill for Claude Code");
|
|
198
204
|
console.log();
|
|
205
|
+
console.log("Sync:");
|
|
206
|
+
console.log(" sync Full manual sync (pull then push)");
|
|
207
|
+
console.log(" sync init <remote-url> Connect ~/.mink to a git remote for cross-device sync");
|
|
208
|
+
console.log(" sync status Show sync state (remote, last sync, pending changes)");
|
|
209
|
+
console.log(" sync push Manually push local changes");
|
|
210
|
+
console.log(" sync pull Manually pull remote changes");
|
|
211
|
+
console.log(" sync pause / resume Temporarily disable/enable auto-sync");
|
|
212
|
+
console.log(" sync disconnect Remove git tracking (data preserved)");
|
|
213
|
+
console.log();
|
|
199
214
|
console.log("Automation & Analysis:");
|
|
200
215
|
console.log(" dashboard [--port=N] Open the real-time web dashboard");
|
|
201
216
|
console.log(" daemon <cmd> Manage the background daemon (start|stop|restart|logs)");
|
|
@@ -7,6 +7,16 @@ import { isWikiEnabled, isVaultInitialized, isInsideVault } from "../core/vault"
|
|
|
7
7
|
import { loadVaultIndex } from "../core/note-index";
|
|
8
8
|
|
|
9
9
|
export function sessionStart(cwd: string): void {
|
|
10
|
+
// Sync pull before session begins (if enabled)
|
|
11
|
+
try {
|
|
12
|
+
const { isSyncInitialized, syncPull } = require("../core/sync");
|
|
13
|
+
if (isSyncInitialized()) {
|
|
14
|
+
syncPull((msg: string) => console.error(msg));
|
|
15
|
+
}
|
|
16
|
+
} catch {
|
|
17
|
+
// Never crash hooks
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
const dir = projectDir(cwd);
|
|
11
21
|
mkdirSync(dir, { recursive: true });
|
|
12
22
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { statSync, existsSync, readFileSync } from "fs";
|
|
2
2
|
import { join, dirname } from "path";
|
|
3
|
-
import { execSync } from "child_process";
|
|
4
3
|
import { safeReadJson, atomicWriteJson, atomicWriteText } from "../core/fs-utils";
|
|
5
4
|
import { isSessionState, buildSummary } from "../core/session";
|
|
6
5
|
import { reflect } from "./reflect";
|
|
@@ -10,10 +9,8 @@ import { createActionLogWriter, consolidateLog } from "../core/action-log";
|
|
|
10
9
|
import {
|
|
11
10
|
isWikiEnabled,
|
|
12
11
|
isVaultInitialized,
|
|
13
|
-
resolveVaultPath,
|
|
14
12
|
vaultProjects,
|
|
15
13
|
} from "../core/vault";
|
|
16
|
-
import { resolveConfigValue } from "../core/global-config";
|
|
17
14
|
import type { SessionState, SessionFinalizer } from "../types/session";
|
|
18
15
|
import type { ProjectConfig } from "../types/file-index";
|
|
19
16
|
|
|
@@ -128,13 +125,11 @@ export function sessionStop(
|
|
|
128
125
|
// Never crash hooks
|
|
129
126
|
}
|
|
130
127
|
|
|
131
|
-
//
|
|
128
|
+
// Full mink sync (subsumes wiki git-backup)
|
|
132
129
|
try {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
gitBackupVault(onReminder);
|
|
137
|
-
}
|
|
130
|
+
const { isSyncInitialized, syncPush } = require("../core/sync");
|
|
131
|
+
if (isSyncInitialized()) {
|
|
132
|
+
syncPush(onReminder);
|
|
138
133
|
}
|
|
139
134
|
} catch {
|
|
140
135
|
// Never crash hooks
|
|
@@ -209,53 +204,3 @@ function writeSessionToWiki(
|
|
|
209
204
|
}
|
|
210
205
|
}
|
|
211
206
|
|
|
212
|
-
function gitBackupVault(
|
|
213
|
-
onReminder: (msg: string) => void
|
|
214
|
-
): void {
|
|
215
|
-
const vaultPath = resolveVaultPath();
|
|
216
|
-
|
|
217
|
-
// Check if vault is a git repo
|
|
218
|
-
const gitDir = join(vaultPath, ".git");
|
|
219
|
-
if (!existsSync(gitDir)) {
|
|
220
|
-
onReminder(
|
|
221
|
-
"[mink] wiki git-backup enabled but vault is not a git repo — run 'git init' in " +
|
|
222
|
-
vaultPath
|
|
223
|
-
);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
try {
|
|
228
|
-
// Check for changes
|
|
229
|
-
const status = execSync("git status --porcelain", {
|
|
230
|
-
cwd: vaultPath,
|
|
231
|
-
timeout: 5000,
|
|
232
|
-
}).toString();
|
|
233
|
-
|
|
234
|
-
if (!status.trim()) return; // Nothing to commit
|
|
235
|
-
|
|
236
|
-
// Stage and commit
|
|
237
|
-
execSync("git add -A", { cwd: vaultPath, timeout: 5000 });
|
|
238
|
-
const msg = `mink: vault update ${new Date().toISOString().split("T")[0]}`;
|
|
239
|
-
execSync(`git commit -m "${msg}"`, {
|
|
240
|
-
cwd: vaultPath,
|
|
241
|
-
timeout: 5000,
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
// Push (best-effort with timeout)
|
|
245
|
-
const remote = resolveConfigValue("wiki.git-remote").value;
|
|
246
|
-
try {
|
|
247
|
-
execSync(`git push ${remote}`, {
|
|
248
|
-
cwd: vaultPath,
|
|
249
|
-
timeout: 10000,
|
|
250
|
-
});
|
|
251
|
-
} catch {
|
|
252
|
-
onReminder(
|
|
253
|
-
`[mink] wiki git push to '${remote}' failed — local commit preserved, will retry next session`
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
} catch (err) {
|
|
257
|
-
onReminder(
|
|
258
|
-
`[mink] wiki git backup error: ${err instanceof Error ? err.message : String(err)}`
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
initSync,
|
|
3
|
+
syncPull,
|
|
4
|
+
syncPush,
|
|
5
|
+
getSyncStatus,
|
|
6
|
+
disconnectSync,
|
|
7
|
+
isSyncInitialized,
|
|
8
|
+
} from "../core/sync";
|
|
9
|
+
import { setConfigValue } from "../core/global-config";
|
|
10
|
+
|
|
11
|
+
export async function sync(args: string[]): Promise<void> {
|
|
12
|
+
const subcommand = args[0];
|
|
13
|
+
|
|
14
|
+
switch (subcommand) {
|
|
15
|
+
case undefined:
|
|
16
|
+
// No args: full manual sync (pull then push)
|
|
17
|
+
return handleManualSync();
|
|
18
|
+
|
|
19
|
+
case "init":
|
|
20
|
+
return handleInit(args.slice(1));
|
|
21
|
+
|
|
22
|
+
case "status":
|
|
23
|
+
return handleStatus();
|
|
24
|
+
|
|
25
|
+
case "push":
|
|
26
|
+
syncPush((msg) => console.error(msg));
|
|
27
|
+
return;
|
|
28
|
+
|
|
29
|
+
case "pull":
|
|
30
|
+
syncPull((msg) => console.error(msg));
|
|
31
|
+
return;
|
|
32
|
+
|
|
33
|
+
case "pause":
|
|
34
|
+
return handlePause();
|
|
35
|
+
|
|
36
|
+
case "resume":
|
|
37
|
+
return handleResume();
|
|
38
|
+
|
|
39
|
+
case "disconnect":
|
|
40
|
+
return handleDisconnect();
|
|
41
|
+
|
|
42
|
+
default:
|
|
43
|
+
console.error(`[mink] unknown sync subcommand: ${subcommand}`);
|
|
44
|
+
console.error("Usage: mink sync [init|status|push|pull|pause|resume|disconnect]");
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function handleManualSync(): void {
|
|
50
|
+
if (!isSyncInitialized()) {
|
|
51
|
+
console.error("[mink] sync is not initialized");
|
|
52
|
+
console.error("Run 'mink sync init <remote-url>' to set up sync");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log("[mink] pulling remote changes...");
|
|
57
|
+
syncPull((msg) => console.error(msg));
|
|
58
|
+
|
|
59
|
+
console.log("[mink] pushing local changes...");
|
|
60
|
+
syncPush((msg) => console.error(msg));
|
|
61
|
+
|
|
62
|
+
console.log("[mink] sync complete");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function handleInit(args: string[]): void {
|
|
66
|
+
const remoteUrl = args[0];
|
|
67
|
+
if (!remoteUrl) {
|
|
68
|
+
console.error("[mink] missing remote URL");
|
|
69
|
+
console.error("Usage: mink sync init <remote-url>");
|
|
70
|
+
console.error("Example: mink sync init git@github.com:user/mink-data.git");
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
initSync(remoteUrl);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function handleStatus(): void {
|
|
78
|
+
const status = getSyncStatus();
|
|
79
|
+
|
|
80
|
+
console.log("Mink Sync Status");
|
|
81
|
+
console.log("─".repeat(40));
|
|
82
|
+
console.log(` Enabled: ${status.enabled ? "yes" : "no"}`);
|
|
83
|
+
console.log(` Git initialized: ${status.gitInitialized ? "yes" : "no"}`);
|
|
84
|
+
console.log(` Remote URL: ${status.remoteUrl || "(not set)"}`);
|
|
85
|
+
console.log(` Branch: ${status.branch || "(none)"}`);
|
|
86
|
+
console.log(` Pending changes: ${status.pendingChanges}`);
|
|
87
|
+
console.log(` Last push: ${status.lastPush || "(never)"}`);
|
|
88
|
+
console.log(` Last pull: ${status.lastPull || "(never)"}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function handlePause(): void {
|
|
92
|
+
setConfigValue("sync.enabled", "false");
|
|
93
|
+
console.log("[mink] sync paused — auto-sync disabled");
|
|
94
|
+
console.log("[mink] run 'mink sync resume' to re-enable");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function handleResume(): void {
|
|
98
|
+
const status = getSyncStatus();
|
|
99
|
+
if (!status.gitInitialized) {
|
|
100
|
+
console.error("[mink] sync has not been initialized yet");
|
|
101
|
+
console.error("Run 'mink sync init <remote-url>' first");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
setConfigValue("sync.enabled", "true");
|
|
106
|
+
console.log("[mink] sync resumed — auto-sync re-enabled");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function handleDisconnect(): void {
|
|
110
|
+
disconnectSync();
|
|
111
|
+
}
|
package/src/commands/wiki.ts
CHANGED
|
@@ -7,6 +7,9 @@ import {
|
|
|
7
7
|
isVaultInitialized,
|
|
8
8
|
vaultManifestPath,
|
|
9
9
|
vaultTemplates,
|
|
10
|
+
linkExternal,
|
|
11
|
+
unlinkExternal,
|
|
12
|
+
listLinks,
|
|
10
13
|
} from "../core/vault";
|
|
11
14
|
import { atomicWriteJson } from "../core/fs-utils";
|
|
12
15
|
import { setConfigValue } from "../core/global-config";
|
|
@@ -34,13 +37,25 @@ export async function wiki(
|
|
|
34
37
|
case "organize":
|
|
35
38
|
wikiOrganize();
|
|
36
39
|
break;
|
|
40
|
+
case "link":
|
|
41
|
+
wikiLink(args.slice(1));
|
|
42
|
+
break;
|
|
43
|
+
case "unlink":
|
|
44
|
+
wikiUnlink(args.slice(1));
|
|
45
|
+
break;
|
|
46
|
+
case "links":
|
|
47
|
+
wikiLinks();
|
|
48
|
+
break;
|
|
37
49
|
default:
|
|
38
|
-
console.log("Usage: mink wiki <
|
|
50
|
+
console.log("Usage: mink wiki <command>");
|
|
39
51
|
console.log();
|
|
40
52
|
console.log(" init Initialize the notes/wiki vault");
|
|
41
53
|
console.log(" status Show vault statistics");
|
|
42
54
|
console.log(" rebuild-index Full rescan and reindex of vault");
|
|
43
55
|
console.log(" organize List inbox notes needing categorization");
|
|
56
|
+
console.log(" link <path> [name] Symlink external notes into the vault");
|
|
57
|
+
console.log(" unlink <name> Remove a symlinked directory from the vault");
|
|
58
|
+
console.log(" links List all linked directories");
|
|
44
59
|
break;
|
|
45
60
|
}
|
|
46
61
|
}
|
|
@@ -188,6 +203,15 @@ function wikiStatus(): void {
|
|
|
188
203
|
` last indexed: ${index.lastScanTimestamp || "never"}`
|
|
189
204
|
);
|
|
190
205
|
|
|
206
|
+
const links = listLinks();
|
|
207
|
+
if (links.length > 0) {
|
|
208
|
+
console.log();
|
|
209
|
+
console.log(" Linked directories:");
|
|
210
|
+
for (const link of links) {
|
|
211
|
+
console.log(` ${link.name} -> ${link.target}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
191
215
|
if (categoryCounts.inbox > 0) {
|
|
192
216
|
console.log();
|
|
193
217
|
console.log(
|
|
@@ -242,6 +266,87 @@ function wikiOrganize(): void {
|
|
|
242
266
|
);
|
|
243
267
|
}
|
|
244
268
|
|
|
269
|
+
function wikiLink(args: string[]): void {
|
|
270
|
+
if (!isVaultInitialized()) {
|
|
271
|
+
console.log("[mink] no vault initialized");
|
|
272
|
+
console.log(" Run 'mink wiki init' first.");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const targetPath = args[0];
|
|
277
|
+
if (!targetPath) {
|
|
278
|
+
console.log("Usage: mink wiki link <path> [name]");
|
|
279
|
+
console.log();
|
|
280
|
+
console.log(" Symlinks an external directory into the vault so it appears");
|
|
281
|
+
console.log(" alongside Mink's content in Obsidian.");
|
|
282
|
+
console.log();
|
|
283
|
+
console.log(" Examples:");
|
|
284
|
+
console.log(" mink wiki link ~/dev/notes");
|
|
285
|
+
console.log(" mink wiki link ~/dev/notes my-notes");
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const name = args[1]; // optional override
|
|
290
|
+
const result = linkExternal(targetPath, name);
|
|
291
|
+
|
|
292
|
+
if (!result.ok) {
|
|
293
|
+
console.error(`[mink] ${result.error}`);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
console.log(`[mink] linked: ${result.linkName} -> ${targetPath}`);
|
|
298
|
+
console.log(` symlink: ${result.linkPath}`);
|
|
299
|
+
console.log();
|
|
300
|
+
console.log(" Open ~/.mink/wiki/ as your Obsidian vault to see everything together.");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function wikiUnlink(args: string[]): void {
|
|
304
|
+
if (!isVaultInitialized()) {
|
|
305
|
+
console.log("[mink] no vault initialized");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const name = args[0];
|
|
310
|
+
if (!name) {
|
|
311
|
+
console.log("Usage: mink wiki unlink <name>");
|
|
312
|
+
console.log();
|
|
313
|
+
console.log(" Run 'mink wiki links' to see linked directories.");
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const result = unlinkExternal(name);
|
|
318
|
+
|
|
319
|
+
if (!result.ok) {
|
|
320
|
+
console.error(`[mink] ${result.error}`);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
console.log(`[mink] unlinked: ${name}`);
|
|
325
|
+
console.log(" (original directory was not modified)");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function wikiLinks(): void {
|
|
329
|
+
if (!isVaultInitialized()) {
|
|
330
|
+
console.log("[mink] no vault initialized");
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const links = listLinks();
|
|
335
|
+
|
|
336
|
+
if (links.length === 0) {
|
|
337
|
+
console.log("[mink] no linked directories");
|
|
338
|
+
console.log(" Use 'mink wiki link <path>' to symlink external notes into the vault.");
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
console.log("[mink] linked directories:");
|
|
343
|
+
console.log();
|
|
344
|
+
for (const link of links) {
|
|
345
|
+
console.log(` ${link.name} -> ${link.target}`);
|
|
346
|
+
console.log(` linked: ${link.linkedAt}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
245
350
|
function expandPath(raw: string): string {
|
|
246
351
|
if (raw.startsWith("~/")) {
|
|
247
352
|
return resolve(homedir(), raw.slice(2));
|