@drewpayment/mink 0.9.1 → 0.10.1
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 +62 -1
- 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 +988 -454
- package/package.json +1 -1
- package/src/cli.ts +9 -2
- package/src/commands/scan.ts +29 -6
- package/src/commands/upgrade.ts +128 -0
- package/src/commands/wiki.ts +19 -3
- package/src/core/note-index.ts +50 -1
- package/src/core/scanner.ts +19 -3
- package/src/core/self-update.ts +363 -0
- package/src/core/task-registry.ts +52 -2
- package/src/types/config.ts +24 -0
- package/src/types/note.ts +1 -0
- /package/dashboard/out/_next/static/{r7Xr9mrUpunsz4QtD3jh1 → e0QWU9rPMeSlJJLTwij89}/_buildManifest.js +0 -0
- /package/dashboard/out/_next/static/{r7Xr9mrUpunsz4QtD3jh1 → e0QWU9rPMeSlJJLTwij89}/_ssgManifest.js +0 -0
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -107,6 +107,12 @@ switch (command) {
|
|
|
107
107
|
break;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
case "upgrade": {
|
|
111
|
+
const { upgrade } = await import("./commands/upgrade");
|
|
112
|
+
await upgrade(cwd, process.argv.slice(3));
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
110
116
|
case "restore": {
|
|
111
117
|
const { restore } = await import("./commands/restore");
|
|
112
118
|
restore(cwd, process.argv.slice(3));
|
|
@@ -213,7 +219,7 @@ switch (command) {
|
|
|
213
219
|
console.log(" config [key] [value] Manage global user settings");
|
|
214
220
|
console.log();
|
|
215
221
|
console.log("Notes & Wiki:");
|
|
216
|
-
console.log(" wiki <cmd> Manage the notes/wiki vault (init|status|link|unlink|links|rebuild-index|organize)");
|
|
222
|
+
console.log(" wiki <cmd> Manage the notes/wiki vault (init|status|link|unlink|links|rebuild-index|scan|organize)");
|
|
217
223
|
console.log(" note \"text\" Capture a note to the vault");
|
|
218
224
|
console.log(" note --daily [text] Create or append to today's daily note");
|
|
219
225
|
console.log(" note list [filters] List notes (--category, --tag, --recent)");
|
|
@@ -244,7 +250,8 @@ switch (command) {
|
|
|
244
250
|
console.log(" dashboard [--port=N] Open the real-time web dashboard");
|
|
245
251
|
console.log(" daemon <cmd> Manage the background daemon (start|stop|restart|logs|install|uninstall)");
|
|
246
252
|
console.log(" cron <cmd> [id] Manage scheduled tasks (list|run|retry)");
|
|
247
|
-
console.log(" update [options] Update Mink across registered projects");
|
|
253
|
+
console.log(" update [options] Update Mink hooks across registered projects");
|
|
254
|
+
console.log(" upgrade [options] Self-upgrade the mink CLI from npm (--check|--dry-run|--force)");
|
|
248
255
|
console.log(" restore [backup] Restore state from a backup");
|
|
249
256
|
console.log(" bug search <term> Search the bug log");
|
|
250
257
|
console.log(" detect-waste Detect and flag wasteful patterns");
|
package/src/commands/scan.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
2
|
+
import { join, relative } from "path";
|
|
3
3
|
import { fileIndexPath, configPath } from "../core/paths";
|
|
4
4
|
import { atomicWriteJson, safeReadJson } from "../core/fs-utils";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
scanProject,
|
|
7
|
+
scanProjectWithStats,
|
|
8
|
+
loadConfig,
|
|
9
|
+
getExcludes,
|
|
10
|
+
} from "../core/scanner";
|
|
6
11
|
import { extractDescription } from "../core/description";
|
|
7
12
|
import { estimateTokens } from "../core/token-estimate";
|
|
8
13
|
import {
|
|
@@ -13,6 +18,11 @@ import {
|
|
|
13
18
|
} from "../core/index-store";
|
|
14
19
|
import type { FileIndex, FileIndexEntry } from "../types/file-index";
|
|
15
20
|
|
|
21
|
+
function configRelativePath(cfgPath: string, cwd: string): string {
|
|
22
|
+
const rel = relative(cwd, cfgPath);
|
|
23
|
+
return rel.startsWith("..") ? cfgPath : rel;
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
function loadExistingIndex(indexPath: string): FileIndex {
|
|
17
27
|
const raw = safeReadJson(indexPath);
|
|
18
28
|
if (isFileIndex(raw)) return raw;
|
|
@@ -64,7 +74,8 @@ export function scan(cwd: string, options: { check: boolean }): void {
|
|
|
64
74
|
const start = Date.now();
|
|
65
75
|
const index = loadExistingIndex(idxPath);
|
|
66
76
|
|
|
67
|
-
const
|
|
77
|
+
const stats = scanProjectWithStats(cwd, excludes, maxFiles);
|
|
78
|
+
const scanned = stats.files;
|
|
68
79
|
|
|
69
80
|
// Build new entries, preserving lifetime counters
|
|
70
81
|
const newIndex = createEmptyIndex();
|
|
@@ -95,7 +106,19 @@ export function scan(cwd: string, options: { check: boolean }): void {
|
|
|
95
106
|
atomicWriteJson(idxPath, newIndex);
|
|
96
107
|
|
|
97
108
|
const elapsed = Date.now() - start;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
109
|
+
if (stats.truncated > 0) {
|
|
110
|
+
console.log(
|
|
111
|
+
`[mink] scanned ${stats.totalScanned} files; indexed ${newIndex.header.totalFiles} most recent in ${elapsed}ms`
|
|
112
|
+
);
|
|
113
|
+
console.log(
|
|
114
|
+
` ${stats.truncated} files past maxFiles=${maxFiles} were not indexed`
|
|
115
|
+
);
|
|
116
|
+
console.log(
|
|
117
|
+
` raise the cap by setting "maxFiles" in ${configRelativePath(cfgPath, cwd)}`
|
|
118
|
+
);
|
|
119
|
+
} else {
|
|
120
|
+
console.log(
|
|
121
|
+
`[mink] indexed ${newIndex.header.totalFiles} files in ${elapsed}ms`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
101
124
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { runSelfUpgrade, PACKAGE_NAME, type UpgradeResult } from "../core/self-update";
|
|
2
|
+
|
|
3
|
+
interface UpgradeArgs {
|
|
4
|
+
check: boolean;
|
|
5
|
+
dryRun: boolean;
|
|
6
|
+
force: boolean;
|
|
7
|
+
yes: boolean;
|
|
8
|
+
help: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function parseArgs(args: string[]): UpgradeArgs {
|
|
12
|
+
const out: UpgradeArgs = {
|
|
13
|
+
check: false,
|
|
14
|
+
dryRun: false,
|
|
15
|
+
force: false,
|
|
16
|
+
yes: false,
|
|
17
|
+
help: false,
|
|
18
|
+
};
|
|
19
|
+
for (const arg of args) {
|
|
20
|
+
switch (arg) {
|
|
21
|
+
case "--check":
|
|
22
|
+
out.check = true;
|
|
23
|
+
break;
|
|
24
|
+
case "--dry-run":
|
|
25
|
+
out.dryRun = true;
|
|
26
|
+
break;
|
|
27
|
+
case "--force":
|
|
28
|
+
out.force = true;
|
|
29
|
+
break;
|
|
30
|
+
case "--yes":
|
|
31
|
+
case "-y":
|
|
32
|
+
out.yes = true;
|
|
33
|
+
break;
|
|
34
|
+
case "--help":
|
|
35
|
+
case "-h":
|
|
36
|
+
out.help = true;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function printHelp(): void {
|
|
44
|
+
console.log("Usage: mink upgrade [options]");
|
|
45
|
+
console.log("");
|
|
46
|
+
console.log("Check the npm registry for a newer mink release and install it.");
|
|
47
|
+
console.log(`Tracks the 'latest' dist-tag of ${PACKAGE_NAME}.`);
|
|
48
|
+
console.log("");
|
|
49
|
+
console.log("Options:");
|
|
50
|
+
console.log(" --check Report whether an upgrade is available; do not install");
|
|
51
|
+
console.log(" --dry-run Resolve everything but do not run the install command");
|
|
52
|
+
console.log(" --force Install the latest version even if it is not strictly newer");
|
|
53
|
+
console.log(" --yes, -y Skip the interactive confirmation prompt");
|
|
54
|
+
console.log(" --help, -h Show this help");
|
|
55
|
+
console.log("");
|
|
56
|
+
console.log("Auto-update on a schedule:");
|
|
57
|
+
console.log(" mink config set cli.auto-update true");
|
|
58
|
+
console.log(" mink config set cli.auto-update-schedule \"0 4 * * *\"");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function describeResult(r: UpgradeResult): string {
|
|
62
|
+
switch (r.status) {
|
|
63
|
+
case "up-to-date":
|
|
64
|
+
return `Already up-to-date — ${r.current} matches latest.`;
|
|
65
|
+
case "update-available":
|
|
66
|
+
return `Update available: ${r.current} → ${r.latest}` +
|
|
67
|
+
(r.packageManager ? ` (would install via ${r.packageManager})` : "");
|
|
68
|
+
case "would-upgrade":
|
|
69
|
+
return `Would upgrade: ${r.current} → ${r.latest}\n command: ${r.command}`;
|
|
70
|
+
case "upgraded":
|
|
71
|
+
return `Upgraded ${r.from} → ${r.to} (via ${r.packageManager}).`;
|
|
72
|
+
case "skipped":
|
|
73
|
+
return `Skipped: ${r.reason}`;
|
|
74
|
+
case "error":
|
|
75
|
+
return `Error: ${r.reason}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function confirm(prompt: string): Promise<boolean> {
|
|
80
|
+
if (!process.stdin.isTTY) return false;
|
|
81
|
+
process.stdout.write(prompt);
|
|
82
|
+
return new Promise<boolean>((resolveConfirm) => {
|
|
83
|
+
process.stdin.setEncoding("utf-8");
|
|
84
|
+
process.stdin.once("data", (chunk) => {
|
|
85
|
+
const answer = String(chunk).trim().toLowerCase();
|
|
86
|
+
resolveConfirm(answer === "y" || answer === "yes");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function upgrade(_cwd: string, args: string[]): Promise<void> {
|
|
92
|
+
const parsed = parseArgs(args);
|
|
93
|
+
|
|
94
|
+
if (parsed.help) {
|
|
95
|
+
printHelp();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// For interactive runs without --yes/--check/--dry-run/--force, do a check
|
|
100
|
+
// first and ask for confirmation before mutating the global install.
|
|
101
|
+
const isCheckLike = parsed.check || parsed.dryRun;
|
|
102
|
+
if (!isCheckLike && !parsed.yes && process.stdin.isTTY) {
|
|
103
|
+
const probe = await runSelfUpgrade({ source: "manual", checkOnly: true, force: parsed.force });
|
|
104
|
+
console.log(describeResult(probe));
|
|
105
|
+
if (probe.status !== "update-available" && !parsed.force) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const ok = await confirm("Proceed with install? [y/N] ");
|
|
109
|
+
if (!ok) {
|
|
110
|
+
console.log("Aborted.");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const result = await runSelfUpgrade({
|
|
116
|
+
source: "manual",
|
|
117
|
+
checkOnly: parsed.check,
|
|
118
|
+
dryRun: parsed.dryRun,
|
|
119
|
+
force: parsed.force,
|
|
120
|
+
interactive: true,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
console.log(describeResult(result));
|
|
124
|
+
|
|
125
|
+
if (result.status === "error") {
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
}
|
package/src/commands/wiki.ts
CHANGED
|
@@ -14,7 +14,11 @@ import {
|
|
|
14
14
|
import { atomicWriteJson } from "../core/fs-utils";
|
|
15
15
|
import { setConfigValue } from "../core/global-config";
|
|
16
16
|
import { seedTemplates } from "../core/vault-templates";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
rebuildVaultIndex,
|
|
19
|
+
loadVaultIndex,
|
|
20
|
+
vaultIndexStaleness,
|
|
21
|
+
} from "../core/note-index";
|
|
18
22
|
import { updateMasterIndex } from "../core/note-linker";
|
|
19
23
|
import type { VaultManifest, NoteCategory } from "../types/note";
|
|
20
24
|
|
|
@@ -32,6 +36,7 @@ export async function wiki(
|
|
|
32
36
|
wikiStatus();
|
|
33
37
|
break;
|
|
34
38
|
case "rebuild-index":
|
|
39
|
+
case "scan":
|
|
35
40
|
wikiRebuildIndex();
|
|
36
41
|
break;
|
|
37
42
|
case "organize":
|
|
@@ -51,7 +56,8 @@ export async function wiki(
|
|
|
51
56
|
console.log();
|
|
52
57
|
console.log(" init Initialize the notes/wiki vault");
|
|
53
58
|
console.log(" status Show vault statistics");
|
|
54
|
-
console.log(" rebuild-index Full rescan and reindex of vault");
|
|
59
|
+
console.log(" rebuild-index Full rescan and reindex of vault (alias: scan)");
|
|
60
|
+
console.log(" scan Alias for rebuild-index");
|
|
55
61
|
console.log(" organize List inbox notes needing categorization");
|
|
56
62
|
console.log(" link <path> [name] Symlink external notes into the vault");
|
|
57
63
|
console.log(" unlink <name> Remove a symlinked directory from the vault");
|
|
@@ -174,6 +180,13 @@ function wikiStatus(): void {
|
|
|
174
180
|
}
|
|
175
181
|
|
|
176
182
|
const vaultPath = resolveVaultPath();
|
|
183
|
+
const staleness = vaultIndexStaleness();
|
|
184
|
+
if (staleness.isStale) {
|
|
185
|
+
console.log(
|
|
186
|
+
`[mink] vault index is stale (${staleness.reason}) — rebuilding...`
|
|
187
|
+
);
|
|
188
|
+
rebuildVaultIndex();
|
|
189
|
+
}
|
|
177
190
|
const index = loadVaultIndex();
|
|
178
191
|
|
|
179
192
|
const categoryCounts: Record<string, number> = {
|
|
@@ -200,7 +213,10 @@ function wikiStatus(): void {
|
|
|
200
213
|
}
|
|
201
214
|
console.log();
|
|
202
215
|
console.log(
|
|
203
|
-
` last
|
|
216
|
+
` last full scan: ${index.lastFullScanTimestamp || "never"}`
|
|
217
|
+
);
|
|
218
|
+
console.log(
|
|
219
|
+
` last update: ${index.lastScanTimestamp || "never"}`
|
|
204
220
|
);
|
|
205
221
|
|
|
206
222
|
const links = listLinks();
|
package/src/core/note-index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { VaultIndex, VaultIndexEntry, NoteCategory } from "../types/note";
|
|
|
7
7
|
export function createEmptyVaultIndex(): VaultIndex {
|
|
8
8
|
return {
|
|
9
9
|
lastScanTimestamp: "",
|
|
10
|
+
lastFullScanTimestamp: "",
|
|
10
11
|
totalNotes: 0,
|
|
11
12
|
entries: {},
|
|
12
13
|
};
|
|
@@ -182,7 +183,9 @@ export function rebuildVaultIndex(): VaultIndex {
|
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
|
|
185
|
-
|
|
186
|
+
const now = new Date().toISOString();
|
|
187
|
+
index.lastScanTimestamp = now;
|
|
188
|
+
index.lastFullScanTimestamp = now;
|
|
186
189
|
saveVaultIndex(index);
|
|
187
190
|
return index;
|
|
188
191
|
}
|
|
@@ -219,6 +222,52 @@ export function getRecentNotes(n: number): VaultIndexEntry[] {
|
|
|
219
222
|
.slice(0, n);
|
|
220
223
|
}
|
|
221
224
|
|
|
225
|
+
export interface VaultStaleness {
|
|
226
|
+
isStale: boolean;
|
|
227
|
+
reason: string | null;
|
|
228
|
+
diskCount: number;
|
|
229
|
+
indexCount: number;
|
|
230
|
+
lastFullScan: string | null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function vaultIndexStaleness(): VaultStaleness {
|
|
234
|
+
const index = loadVaultIndex();
|
|
235
|
+
const root = resolveVaultPath();
|
|
236
|
+
const diskCount = collectAllMarkdown(root).length;
|
|
237
|
+
const indexCount = Object.keys(index.entries).length;
|
|
238
|
+
const lastFullScan = index.lastFullScanTimestamp || null;
|
|
239
|
+
|
|
240
|
+
if (!lastFullScan) {
|
|
241
|
+
return {
|
|
242
|
+
isStale: true,
|
|
243
|
+
reason: "no full scan on record",
|
|
244
|
+
diskCount,
|
|
245
|
+
indexCount,
|
|
246
|
+
lastFullScan: null,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const delta = Math.abs(diskCount - indexCount);
|
|
251
|
+
const threshold = Math.max(5, Math.floor(diskCount * 0.05));
|
|
252
|
+
if (delta >= threshold) {
|
|
253
|
+
return {
|
|
254
|
+
isStale: true,
|
|
255
|
+
reason: `${diskCount} files on disk but ${indexCount} in index`,
|
|
256
|
+
diskCount,
|
|
257
|
+
indexCount,
|
|
258
|
+
lastFullScan,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
isStale: false,
|
|
264
|
+
reason: null,
|
|
265
|
+
diskCount,
|
|
266
|
+
indexCount,
|
|
267
|
+
lastFullScan,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
222
271
|
interface ScannedMarkdown {
|
|
223
272
|
absolutePath: string;
|
|
224
273
|
relativePath: string;
|
package/src/core/scanner.ts
CHANGED
|
@@ -87,13 +87,29 @@ export function getExcludes(config: ProjectConfig): string[] {
|
|
|
87
87
|
return [...DEFAULT_EXCLUDES, ...(config.excludePatterns ?? [])];
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
export
|
|
90
|
+
export interface ScanStats {
|
|
91
|
+
files: ScannedFile[];
|
|
92
|
+
totalScanned: number;
|
|
93
|
+
truncated: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function scanProjectWithStats(
|
|
91
97
|
projectRoot: string,
|
|
92
98
|
excludes: string[],
|
|
93
99
|
maxFiles: number = DEFAULT_MAX_FILES
|
|
94
|
-
):
|
|
100
|
+
): ScanStats {
|
|
95
101
|
const results: ScannedFile[] = [];
|
|
96
102
|
walkDirectory(projectRoot, projectRoot, excludes, results);
|
|
97
103
|
results.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
98
|
-
|
|
104
|
+
const totalScanned = results.length;
|
|
105
|
+
const files = results.slice(0, maxFiles);
|
|
106
|
+
return { files, totalScanned, truncated: totalScanned - files.length };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function scanProject(
|
|
110
|
+
projectRoot: string,
|
|
111
|
+
excludes: string[],
|
|
112
|
+
maxFiles: number = DEFAULT_MAX_FILES
|
|
113
|
+
): ScannedFile[] {
|
|
114
|
+
return scanProjectWithStats(projectRoot, excludes, maxFiles).files;
|
|
99
115
|
}
|