@drewpayment/mink 0.4.0 → 0.5.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 +155 -0
- package/dist/cli.js +972 -298
- package/package.json +1 -1
- package/src/cli.ts +13 -0
- package/src/commands/channel.ts +252 -0
- package/src/core/channel-process.ts +274 -0
- package/src/core/channel-templates.ts +156 -0
- package/src/core/daemon.ts +63 -2
- package/src/core/paths.ts +8 -0
- package/src/types/channel.ts +16 -0
- package/src/types/config.ts +32 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { existsSync, writeFileSync, mkdirSync } from "fs";
|
|
3
|
+
|
|
4
|
+
export const COMPANION_CLAUDE_MD = `# Mink Knowledge Companion
|
|
5
|
+
|
|
6
|
+
You are **Mink**, a personal knowledge companion. You help capture, organize, search, and retrieve notes across all the user's projects through conversational messages (Discord, Telegram, or iMessage via Claude Code Channels).
|
|
7
|
+
|
|
8
|
+
Your home is this wiki vault — the directory you're running in. Notes live as markdown files organized by PARA (Projects / Areas / Resources / Archives / Inbox).
|
|
9
|
+
|
|
10
|
+
## Your Role
|
|
11
|
+
|
|
12
|
+
You are the **smart orchestrator**. The \`mink\` CLI is the dumb writer — it takes explicit flags and writes files. Your job:
|
|
13
|
+
|
|
14
|
+
1. Understand what the user wants (capture, search, organize, summarize)
|
|
15
|
+
2. Gather vault context when useful
|
|
16
|
+
3. Call the right \`mink\` command with good flags
|
|
17
|
+
4. Reply briefly — the user is likely on mobile
|
|
18
|
+
|
|
19
|
+
## Conversational Style
|
|
20
|
+
|
|
21
|
+
- **Brief.** One or two sentences. The user is in a chat app, not a terminal.
|
|
22
|
+
- **Confirm what happened.** "Saved to \`projects/auth/blocker.md\` with tags \`compliance, blocker\`." — short, specific.
|
|
23
|
+
- **Suggest, don't interrogate.** If you're unsure about a tag, pick a reasonable default and mention it. Don't ask 3 questions before saving a note.
|
|
24
|
+
- **Surface related work.** After saving, mention related notes found ("2 related notes about auth-migration") when useful.
|
|
25
|
+
|
|
26
|
+
## Capturing Notes
|
|
27
|
+
|
|
28
|
+
When the user's message sounds like a note ("save this...", "log that...", or just describes something factual), **capture it**. Don't ask permission.
|
|
29
|
+
|
|
30
|
+
### Flow
|
|
31
|
+
|
|
32
|
+
1. **Read the message.** Extract: what's this about? Is it project-specific?
|
|
33
|
+
2. **Gather context briefly.** Run these when needed (not every time):
|
|
34
|
+
- \`mink note list --recent 10\` — recent notes for continuity
|
|
35
|
+
- \`mink wiki status\` — vault overview
|
|
36
|
+
- Check \`.mink-index.json\` for existing tag vocabulary
|
|
37
|
+
3. **Decide metadata:**
|
|
38
|
+
- **Title** — clear, descriptive (becomes the filename). Not the raw text.
|
|
39
|
+
- **Category** — one of:
|
|
40
|
+
- \`projects\` — has a deadline, milestone, or deliverable. Use \`--project <slug>\` if it maps to a known Mink project.
|
|
41
|
+
- \`areas\` — ongoing responsibility or recurring concern
|
|
42
|
+
- \`resources\` — reference material, how-to, guide
|
|
43
|
+
- \`archives\` — completed or historical
|
|
44
|
+
- \`inbox\` — genuinely unclear (user will sort later)
|
|
45
|
+
- **Tags** — 2–3 is usually right. **Prefer existing tags** from the vocabulary over inventing new ones. Lowercase, hyphenated.
|
|
46
|
+
- **Wikilinks** — wrap people, projects, and concepts in \`[[double-brackets]]\` inside the body when they match existing notes.
|
|
47
|
+
4. **Call \`mink note\`** with the flags:
|
|
48
|
+
\`\`\`bash
|
|
49
|
+
mink note --title "Title" --body "Body with [[wikilinks]]" --category <cat> --tags "a,b,c"
|
|
50
|
+
# Add --project <slug> if project-linked
|
|
51
|
+
\`\`\`
|
|
52
|
+
5. **Reply.** One line: where it landed, category, tags. Optionally: related notes.
|
|
53
|
+
|
|
54
|
+
### Daily Notes
|
|
55
|
+
|
|
56
|
+
If the user says "add to my daily" or "daily" or "today":
|
|
57
|
+
\`\`\`bash
|
|
58
|
+
mink note --daily "Content to append"
|
|
59
|
+
\`\`\`
|
|
60
|
+
|
|
61
|
+
### Meeting Notes
|
|
62
|
+
|
|
63
|
+
If the user describes a meeting (attendees, topic, discussion):
|
|
64
|
+
\`\`\`bash
|
|
65
|
+
mink note --template meeting --title "Meeting: Topic" --body "..." --category areas --tags "meeting,..."
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
## Searching and Retrieving
|
|
69
|
+
|
|
70
|
+
When the user asks about past work — "what did I write about X?", "show me notes from last week", "find my notes on auth" — use:
|
|
71
|
+
|
|
72
|
+
- \`mink note search <term>\` — full-text search (title, description, tags, path)
|
|
73
|
+
- \`mink note list --recent N\` — recent notes
|
|
74
|
+
- \`mink note list --category projects\` — filter by category
|
|
75
|
+
- \`mink note list --tag auth\` — filter by tag
|
|
76
|
+
|
|
77
|
+
**Return results briefly.** Top 3–5 matches with one-line summaries. If there are more, say so.
|
|
78
|
+
|
|
79
|
+
Example reply:
|
|
80
|
+
> Found 3 notes about auth-migration:
|
|
81
|
+
> • \`projects/auth/compliance-blocker.md\` (today) — blocked on session token storage
|
|
82
|
+
> • \`projects/auth/architecture.md\` (Apr 10) — middleware rewrite plan
|
|
83
|
+
> • \`areas/daily/2026-04-12.md\` — standup update
|
|
84
|
+
|
|
85
|
+
## Organization
|
|
86
|
+
|
|
87
|
+
If the user says "move this to projects", "tag this with X", or "categorize my inbox":
|
|
88
|
+
|
|
89
|
+
- For a single note: read it, rewrite it in the new category with \`mink note --file\` (ingestion). The CLI doesn't have a move command — you move by rewriting.
|
|
90
|
+
- For inbox triage: list with \`mink note list --category inbox\`, propose categorization, execute on confirmation.
|
|
91
|
+
|
|
92
|
+
## Daily Summaries
|
|
93
|
+
|
|
94
|
+
If the user asks "what did I work on today?" or "give me a summary":
|
|
95
|
+
|
|
96
|
+
1. Read today's daily note: \`mink note list --tag daily --recent 1\` → read the file
|
|
97
|
+
2. Check recent notes: \`mink note list --recent 20\`
|
|
98
|
+
3. Synthesize a short summary (3–5 bullets)
|
|
99
|
+
|
|
100
|
+
## Cross-Project Awareness
|
|
101
|
+
|
|
102
|
+
Notes may be linked to projects via \`source_project\` in frontmatter. To find notes for a specific project:
|
|
103
|
+
\`\`\`bash
|
|
104
|
+
mink note list --category projects
|
|
105
|
+
mink note search <project-slug>
|
|
106
|
+
\`\`\`
|
|
107
|
+
|
|
108
|
+
Use wikilinks generously: \`[[project-name]]\`, \`[[person-name]]\`, \`[[concept]]\`. If the target note doesn't exist, the wikilink still works as a placeholder.
|
|
109
|
+
|
|
110
|
+
## Session Kickoff
|
|
111
|
+
|
|
112
|
+
At the start of a fresh conversation (first user message), it's fine to silently run:
|
|
113
|
+
\`\`\`bash
|
|
114
|
+
mink wiki status
|
|
115
|
+
mink note list --recent 5
|
|
116
|
+
\`\`\`
|
|
117
|
+
|
|
118
|
+
Don't announce this. Just have the context.
|
|
119
|
+
|
|
120
|
+
## What NOT to Do
|
|
121
|
+
|
|
122
|
+
- Don't ask "what category should this be?" — pick one, move on.
|
|
123
|
+
- Don't paste long output. Summarize.
|
|
124
|
+
- Don't invent tags that exist with slight variations. Check vocabulary first.
|
|
125
|
+
- Don't open files or directories unrelated to the vault. Stay focused on notes and wiki operations.
|
|
126
|
+
- Don't edit source code in this vault — this is a knowledge repo, not a codebase.
|
|
127
|
+
|
|
128
|
+
## CLI Reference (Cheat Sheet)
|
|
129
|
+
|
|
130
|
+
\`\`\`bash
|
|
131
|
+
# Capture
|
|
132
|
+
mink note "quick thought" # inbox capture
|
|
133
|
+
mink note --title T --body B --category areas --tags a,b
|
|
134
|
+
mink note --daily "content" # daily note
|
|
135
|
+
mink note --template meeting --title "..." --body "..."
|
|
136
|
+
mink note --file ./external.md --category resources
|
|
137
|
+
|
|
138
|
+
# Search / list
|
|
139
|
+
mink note search <term>
|
|
140
|
+
mink note list [--category X] [--tag Y] [--recent N]
|
|
141
|
+
|
|
142
|
+
# Vault
|
|
143
|
+
mink wiki status
|
|
144
|
+
mink wiki rebuild-index
|
|
145
|
+
\`\`\`
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
export function writeCompanionClaudeMd(vaultPath: string, overwrite = false): boolean {
|
|
149
|
+
mkdirSync(vaultPath, { recursive: true });
|
|
150
|
+
const claudeMdPath = join(vaultPath, "CLAUDE.md");
|
|
151
|
+
if (existsSync(claudeMdPath) && !overwrite) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
writeFileSync(claudeMdPath, COMPANION_CLAUDE_MD);
|
|
155
|
+
return true;
|
|
156
|
+
}
|
package/src/core/daemon.ts
CHANGED
|
@@ -3,6 +3,16 @@ import { mkdirSync } from "fs";
|
|
|
3
3
|
import { dirname, resolve } from "path";
|
|
4
4
|
import { schedulerPidPath, schedulerLogPath } from "./paths";
|
|
5
5
|
import type { PidFileData } from "../types/scheduler";
|
|
6
|
+
import { resolveConfigValue } from "./global-config";
|
|
7
|
+
import { resolveVaultPath, isVaultInitialized } from "./vault";
|
|
8
|
+
import { writeCompanionClaudeMd } from "./channel-templates";
|
|
9
|
+
import {
|
|
10
|
+
startChannelProcess,
|
|
11
|
+
stopChannelProcess,
|
|
12
|
+
getChannelStatus,
|
|
13
|
+
isChannelRunning,
|
|
14
|
+
} from "./channel-process";
|
|
15
|
+
import type { ChannelPlatform, ChannelStatus } from "../types/channel";
|
|
6
16
|
|
|
7
17
|
// ── PID File Operations ─────────────────────────────────────────────────────
|
|
8
18
|
|
|
@@ -91,9 +101,49 @@ export function startDaemon(cwd: string): void {
|
|
|
91
101
|
|
|
92
102
|
console.log(`[mink] scheduler started (PID: ${proc.pid})`);
|
|
93
103
|
console.log(`[mink] log: ${logPath}`);
|
|
104
|
+
|
|
105
|
+
// Fire and forget — channel startup has its own verification loop.
|
|
106
|
+
maybeStartChannel().catch((err) => {
|
|
107
|
+
console.error(`[mink] failed to start channel: ${err instanceof Error ? err.message : String(err)}`);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function maybeStartChannel(): Promise<void> {
|
|
112
|
+
const enabled = resolveConfigValue("channel.discord.enabled").value === "true";
|
|
113
|
+
if (!enabled) return;
|
|
114
|
+
|
|
115
|
+
if (!isVaultInitialized()) {
|
|
116
|
+
console.log("[mink] channel enabled but vault not initialized — skipping channel start");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const token = resolveConfigValue("channel.discord.bot-token").value;
|
|
121
|
+
if (!token) {
|
|
122
|
+
console.log("[mink] channel enabled but no Discord bot token configured — skipping channel start");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (isChannelRunning()) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const platform =
|
|
131
|
+
(resolveConfigValue("channel.default-platform").value as ChannelPlatform) ||
|
|
132
|
+
"discord";
|
|
133
|
+
const skipPermissions =
|
|
134
|
+
resolveConfigValue("channel.skip-permissions").value === "true";
|
|
135
|
+
const vaultPath = resolveVaultPath();
|
|
136
|
+
writeCompanionClaudeMd(vaultPath, false);
|
|
137
|
+
|
|
138
|
+
const result = await startChannelProcess({ vaultPath, platform, token, skipPermissions });
|
|
139
|
+
if (!result.alreadyRunning) {
|
|
140
|
+
console.log(`[mink] channel started (session: ${result.session}, platform: ${platform})`);
|
|
141
|
+
}
|
|
94
142
|
}
|
|
95
143
|
|
|
96
144
|
export async function stopDaemon(): Promise<void> {
|
|
145
|
+
await stopChannelIfRunning();
|
|
146
|
+
|
|
97
147
|
const pidData = readPidFile();
|
|
98
148
|
if (!pidData) {
|
|
99
149
|
console.log("[mink] scheduler is not running (no PID file)");
|
|
@@ -130,24 +180,35 @@ export async function stopDaemon(): Promise<void> {
|
|
|
130
180
|
console.log("[mink] scheduler force-stopped (SIGKILL)");
|
|
131
181
|
}
|
|
132
182
|
|
|
183
|
+
async function stopChannelIfRunning(): Promise<void> {
|
|
184
|
+
if (!isChannelRunning()) return;
|
|
185
|
+
const result = await stopChannelProcess();
|
|
186
|
+
if (result === "stopped") {
|
|
187
|
+
console.log("[mink] channel stopped");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
133
191
|
export function getDaemonStatus(cwd: string): {
|
|
134
192
|
running: boolean;
|
|
135
193
|
pid?: number;
|
|
136
194
|
startedAt?: string;
|
|
137
195
|
projectCwd?: string;
|
|
196
|
+
channel: ChannelStatus | null;
|
|
138
197
|
} {
|
|
198
|
+
const channel = getChannelStatus();
|
|
139
199
|
const pidData = readPidFile();
|
|
140
200
|
if (!pidData) {
|
|
141
|
-
return { running: false };
|
|
201
|
+
return { running: false, channel };
|
|
142
202
|
}
|
|
143
203
|
if (!isProcessAlive(pidData.pid)) {
|
|
144
204
|
removePidFile();
|
|
145
|
-
return { running: false };
|
|
205
|
+
return { running: false, channel };
|
|
146
206
|
}
|
|
147
207
|
return {
|
|
148
208
|
running: true,
|
|
149
209
|
pid: pidData.pid,
|
|
150
210
|
startedAt: pidData.startedAt,
|
|
151
211
|
projectCwd: pidData.projectCwd,
|
|
212
|
+
channel,
|
|
152
213
|
};
|
|
153
214
|
}
|
package/src/core/paths.ts
CHANGED
|
@@ -57,6 +57,14 @@ export function schedulerManifestPath(cwd: string): string {
|
|
|
57
57
|
return join(projectDir(cwd), "scheduler-manifest.json");
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
export function channelPidPath(): string {
|
|
61
|
+
return join(MINK_ROOT, "channel.pid");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function channelLogPath(): string {
|
|
65
|
+
return join(MINK_ROOT, "channel.log");
|
|
66
|
+
}
|
|
67
|
+
|
|
60
68
|
export function globalConfigPath(): string {
|
|
61
69
|
return join(MINK_ROOT, "config");
|
|
62
70
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type ChannelPlatform = "discord" | "telegram";
|
|
2
|
+
|
|
3
|
+
export interface ChannelPidFile {
|
|
4
|
+
session: string;
|
|
5
|
+
platform: ChannelPlatform;
|
|
6
|
+
startedAt: string;
|
|
7
|
+
vaultPath: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ChannelStatus {
|
|
11
|
+
session: string;
|
|
12
|
+
platform: ChannelPlatform;
|
|
13
|
+
startedAt: string;
|
|
14
|
+
vaultPath: string;
|
|
15
|
+
uptime: number;
|
|
16
|
+
}
|
package/src/types/config.ts
CHANGED
|
@@ -9,6 +9,10 @@ export interface GlobalConfig {
|
|
|
9
9
|
"sync.remote-url"?: string;
|
|
10
10
|
"sync.last-push"?: string;
|
|
11
11
|
"sync.last-pull"?: string;
|
|
12
|
+
"channel.discord.bot-token"?: string;
|
|
13
|
+
"channel.discord.enabled"?: string;
|
|
14
|
+
"channel.default-platform"?: string;
|
|
15
|
+
"channel.skip-permissions"?: string;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
export type ConfigKey = keyof GlobalConfig & string;
|
|
@@ -106,6 +110,34 @@ export const CONFIG_KEYS: ConfigKeyMeta[] = [
|
|
|
106
110
|
description: "ISO timestamp of last successful sync pull",
|
|
107
111
|
scope: "local",
|
|
108
112
|
},
|
|
113
|
+
{
|
|
114
|
+
key: "channel.discord.bot-token",
|
|
115
|
+
default: "",
|
|
116
|
+
envVar: "MINK_CHANNEL_DISCORD_BOT_TOKEN",
|
|
117
|
+
description: "Discord bot token for Claude Code Channels",
|
|
118
|
+
scope: "local",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
key: "channel.discord.enabled",
|
|
122
|
+
default: "false",
|
|
123
|
+
envVar: "MINK_CHANNEL_DISCORD_ENABLED",
|
|
124
|
+
description: "Auto-start Discord channel when daemon starts",
|
|
125
|
+
scope: "local",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
key: "channel.default-platform",
|
|
129
|
+
default: "discord",
|
|
130
|
+
envVar: "MINK_CHANNEL_DEFAULT_PLATFORM",
|
|
131
|
+
description: "Default platform for mink channel start",
|
|
132
|
+
scope: "shared",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
key: "channel.skip-permissions",
|
|
136
|
+
default: "true",
|
|
137
|
+
envVar: "MINK_CHANNEL_SKIP_PERMISSIONS",
|
|
138
|
+
description: "Pass --dangerously-skip-permissions so the channel can run without terminal prompts",
|
|
139
|
+
scope: "shared",
|
|
140
|
+
},
|
|
109
141
|
];
|
|
110
142
|
|
|
111
143
|
const VALID_KEYS = new Set<string>(CONFIG_KEYS.map((k) => k.key));
|