@drewpayment/mink 0.13.0-beta.4 → 0.13.0-beta.5
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 +22 -4
- 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/compression.html +1 -1
- package/dashboard/out/compression.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.bun.js +3961 -3354
- package/dist/cli.node.js +4347 -3535
- package/package.json +1 -1
- package/src/cli.ts +29 -5
- package/src/commands/init.ts +132 -10
- package/src/commands/post-read.ts +1 -1
- package/src/commands/post-tool.ts +1 -1
- package/src/commands/refresh-hooks.ts +42 -0
- package/src/commands/retrieve.ts +1 -1
- package/src/commands/session-start.ts +11 -0
- package/src/core/agent-detect.ts +88 -0
- package/src/core/agent-pi.ts +383 -0
- package/src/core/code-skeleton.ts +1 -1
- package/src/core/compress-tool-output.ts +4 -4
- package/src/core/compression.ts +6 -7
- package/src/core/dashboard-api.ts +1 -1
- package/src/core/hook-output.ts +1 -1
- package/src/core/hook-refresh.ts +81 -0
- package/src/core/output-compression.ts +2 -2
- package/src/core/prompt.ts +27 -0
- package/src/core/self-update.ts +15 -0
- package/src/repositories/compression-cache-repo.ts +1 -1
- package/src/repositories/token-ledger-repo.ts +1 -1
- package/src/storage/schema.ts +2 -2
- package/src/types/compression.ts +1 -1
- package/src/types/config.ts +3 -2
- package/src/types/dashboard.ts +2 -2
- package/src/types/hook-input.ts +1 -1
- package/src/types/token-ledger.ts +2 -2
- /package/dashboard/out/_next/static/{zeNEMTDaNBRpOk-to-GkA → Yov5CTLEIMMDdQaCwuG1a}/_buildManifest.js +0 -0
- /package/dashboard/out/_next/static/{zeNEMTDaNBRpOk-to-GkA → Yov5CTLEIMMDdQaCwuG1a}/_ssgManifest.js +0 -0
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -16,8 +16,24 @@ switch (command) {
|
|
|
16
16
|
break;
|
|
17
17
|
|
|
18
18
|
case "init": {
|
|
19
|
-
const { init } = await import("./commands/init");
|
|
20
|
-
|
|
19
|
+
const { init, resolveTargetsFromFlag } = await import("./commands/init");
|
|
20
|
+
const args = process.argv.slice(3);
|
|
21
|
+
const agentFlagIndex = args.findIndex(
|
|
22
|
+
(a) => a === "--agent" || a.startsWith("--agent=")
|
|
23
|
+
);
|
|
24
|
+
let agentValue: string | undefined;
|
|
25
|
+
if (agentFlagIndex !== -1) {
|
|
26
|
+
const a = args[agentFlagIndex];
|
|
27
|
+
agentValue = a.includes("=") ? a.split("=").slice(1).join("=") : args[agentFlagIndex + 1];
|
|
28
|
+
}
|
|
29
|
+
const yes = args.includes("--yes") || args.includes("-y");
|
|
30
|
+
const targets = agentValue ? resolveTargetsFromFlag(agentValue) : undefined;
|
|
31
|
+
if (agentValue && (!targets || targets.length === 0)) {
|
|
32
|
+
console.error(`[mink] unknown --agent value: ${agentValue}`);
|
|
33
|
+
console.error(" Valid: claude, pi, all (or a comma-separated list)");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
await init(cwd, { targets, interactive: !yes });
|
|
21
37
|
break;
|
|
22
38
|
}
|
|
23
39
|
|
|
@@ -59,6 +75,12 @@ switch (command) {
|
|
|
59
75
|
break;
|
|
60
76
|
}
|
|
61
77
|
|
|
78
|
+
case "refresh-hooks": {
|
|
79
|
+
const { refreshHooks } = await import("./commands/refresh-hooks");
|
|
80
|
+
refreshHooks(cwd, process.argv.slice(3));
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
|
|
62
84
|
case "pre-write": {
|
|
63
85
|
const { preWrite } = await import("./commands/pre-write");
|
|
64
86
|
await preWrite(cwd);
|
|
@@ -233,7 +255,9 @@ switch (command) {
|
|
|
233
255
|
console.log("Usage: mink <command> [options]");
|
|
234
256
|
console.log();
|
|
235
257
|
console.log("Commands:");
|
|
236
|
-
console.log(" init
|
|
258
|
+
console.log(" init [--agent X] [--yes] Initialize Mink in the current project");
|
|
259
|
+
console.log(" --agent claude|pi|all (default: detect & prompt)");
|
|
260
|
+
console.log(" refresh-hooks [--all] Regenerate hook wiring after an upgrade (--all: every project)");
|
|
237
261
|
console.log(" status Display project health at a glance");
|
|
238
262
|
console.log(" scan [--check] Force a full file index rescan");
|
|
239
263
|
console.log(" config [key] [value] Manage global user settings");
|
|
@@ -275,7 +299,7 @@ switch (command) {
|
|
|
275
299
|
console.log(" restore [backup] Restore state from a backup");
|
|
276
300
|
console.log(" bug search <term> Search the bug log");
|
|
277
301
|
console.log(" detect-waste Detect and flag wasteful patterns");
|
|
278
|
-
console.log(" retrieve <token> Return a compressed tool output's original (spec
|
|
302
|
+
console.log(" retrieve <token> Return a compressed tool output's original (spec 22)");
|
|
279
303
|
console.log(" reflect Generate learning memory reflections");
|
|
280
304
|
console.log(" designqc [target] Capture design screenshots (spec 13)");
|
|
281
305
|
console.log(" framework-advisor Generate framework advisor knowledge file (spec 14)");
|
|
@@ -285,7 +309,7 @@ switch (command) {
|
|
|
285
309
|
console.log(" session-stop Finalize session and log data");
|
|
286
310
|
console.log(" pre-read / post-read File read hooks");
|
|
287
311
|
console.log(" pre-write / post-write File write hooks");
|
|
288
|
-
console.log(" post-tool Tool-output compression hook (Bash/Grep/MCP, spec
|
|
312
|
+
console.log(" post-tool Tool-output compression hook (Bash/Grep/MCP, spec 22)");
|
|
289
313
|
break;
|
|
290
314
|
|
|
291
315
|
default:
|
package/src/commands/init.ts
CHANGED
|
@@ -12,6 +12,23 @@ import {
|
|
|
12
12
|
isInsideVault,
|
|
13
13
|
vaultProjects,
|
|
14
14
|
} from "../core/vault";
|
|
15
|
+
import {
|
|
16
|
+
type AgentId,
|
|
17
|
+
AGENTS,
|
|
18
|
+
detectAgents,
|
|
19
|
+
resolveTargetsFromFlag,
|
|
20
|
+
} from "../core/agent-detect";
|
|
21
|
+
import { installPi } from "../core/agent-pi";
|
|
22
|
+
import { ask, stdinIsInteractive } from "../core/prompt";
|
|
23
|
+
|
|
24
|
+
export { resolveTargetsFromFlag };
|
|
25
|
+
|
|
26
|
+
export interface InitOptions {
|
|
27
|
+
/** Explicit set of hosts to wire. When omitted, Mink detects and/or prompts. */
|
|
28
|
+
targets?: AgentId[];
|
|
29
|
+
/** Allow an interactive agent-selection prompt when stdin is a TTY. */
|
|
30
|
+
interactive?: boolean;
|
|
31
|
+
}
|
|
15
32
|
|
|
16
33
|
interface HookCommand {
|
|
17
34
|
type: "command";
|
|
@@ -91,7 +108,7 @@ export function buildHooksConfig(cliPath: string): HooksConfig {
|
|
|
91
108
|
{ matcher: "Read", hooks: hook(`${prefix} post-read`) },
|
|
92
109
|
{ matcher: "Edit", hooks: hook(`${prefix} post-write`) },
|
|
93
110
|
{ matcher: "Write", hooks: hook(`${prefix} post-write`) },
|
|
94
|
-
// Tool-output compression (spec
|
|
111
|
+
// Tool-output compression (spec 22) — on by default; a no-op when disabled via config.
|
|
95
112
|
{ matcher: "Bash", hooks: hook(`${prefix} post-tool`) },
|
|
96
113
|
{ matcher: "Grep", hooks: hook(`${prefix} post-tool`) },
|
|
97
114
|
],
|
|
@@ -172,17 +189,80 @@ export function mergeHooksIntoSettings(
|
|
|
172
189
|
atomicWriteJson(settingsPath, existing);
|
|
173
190
|
}
|
|
174
191
|
|
|
192
|
+
/** Wire Mink into Claude Code: settings.json hooks + the project rule file. */
|
|
193
|
+
export function installClaude(
|
|
194
|
+
cwd: string,
|
|
195
|
+
cliPath: string
|
|
196
|
+
): { settingsPath: string; rulePath: string } {
|
|
197
|
+
const settingsPath = resolve(cwd, ".claude", "settings.json");
|
|
198
|
+
mergeHooksIntoSettings(settingsPath, buildHooksConfig(cliPath));
|
|
199
|
+
const rulePath = writeMinkRule(cwd);
|
|
200
|
+
return { settingsPath, rulePath };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Decide which hosts to wire. Explicit `targets` win; otherwise Mink detects
|
|
205
|
+
* installed hosts and — only when running interactively at a TTY — asks the
|
|
206
|
+
* user to confirm. Non-interactive runs fall back to the detected set, and to
|
|
207
|
+
* Claude Code when nothing is detected (preserving Mink's original behavior).
|
|
208
|
+
*/
|
|
209
|
+
export async function resolveTargets(
|
|
210
|
+
cwd: string,
|
|
211
|
+
opts: InitOptions
|
|
212
|
+
): Promise<AgentId[]> {
|
|
213
|
+
if (opts.targets && opts.targets.length > 0) return opts.targets;
|
|
214
|
+
|
|
215
|
+
const detected = detectAgents(cwd);
|
|
216
|
+
const detectedIds = detected.filter((a) => a.detected).map((a) => a.id);
|
|
217
|
+
|
|
218
|
+
if (opts.interactive && stdinIsInteractive()) {
|
|
219
|
+
return promptForAgents(detected, detectedIds);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return detectedIds.length > 0 ? detectedIds : ["claude"];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function promptForAgents(
|
|
226
|
+
detected: ReturnType<typeof detectAgents>,
|
|
227
|
+
defaults: AgentId[]
|
|
228
|
+
): Promise<AgentId[]> {
|
|
229
|
+
const fallback: AgentId[] = defaults.length > 0 ? defaults : ["claude"];
|
|
230
|
+
|
|
231
|
+
console.log("Which assistant(s) should Mink work with?");
|
|
232
|
+
detected.forEach((a, i) => {
|
|
233
|
+
const tag = a.detected ? ` (detected — ${a.signals.join(", ")})` : "";
|
|
234
|
+
console.log(` ${i + 1}) ${a.label}${tag}`);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const answer = (
|
|
238
|
+
await ask(
|
|
239
|
+
`Enter numbers (comma-separated), 'a' for all [default: ${fallback.join(", ")}]: `
|
|
240
|
+
)
|
|
241
|
+
)
|
|
242
|
+
.trim()
|
|
243
|
+
.toLowerCase();
|
|
244
|
+
|
|
245
|
+
if (answer === "") return fallback;
|
|
246
|
+
if (answer === "a" || answer === "all") return AGENTS.map((a) => a.id);
|
|
247
|
+
|
|
248
|
+
const picked = answer
|
|
249
|
+
.split(",")
|
|
250
|
+
.map((s) => parseInt(s.trim(), 10))
|
|
251
|
+
.filter((n) => Number.isInteger(n) && n >= 1 && n <= detected.length)
|
|
252
|
+
.map((n) => detected[n - 1].id);
|
|
253
|
+
|
|
254
|
+
return picked.length > 0 ? picked : fallback;
|
|
255
|
+
}
|
|
256
|
+
|
|
175
257
|
function isExistingInstallation(cwd: string): boolean {
|
|
176
258
|
const dir = projectDir(cwd);
|
|
177
259
|
if (!existsSync(dir)) return false;
|
|
178
260
|
return existsSync(join(dir, "file-index.json"));
|
|
179
261
|
}
|
|
180
262
|
|
|
181
|
-
export async function init(cwd: string): Promise<void> {
|
|
263
|
+
export async function init(cwd: string, opts: InitOptions = {}): Promise<void> {
|
|
182
264
|
const runtime = detectRuntime();
|
|
183
265
|
const cliPath = resolveCliPath();
|
|
184
|
-
const hooks = buildHooksConfig(cliPath);
|
|
185
|
-
const settingsPath = resolve(cwd, ".claude", "settings.json");
|
|
186
266
|
const dir = projectDir(cwd);
|
|
187
267
|
const upgrading = isExistingInstallation(cwd);
|
|
188
268
|
|
|
@@ -193,8 +273,25 @@ export async function init(cwd: string): Promise<void> {
|
|
|
193
273
|
console.log(` backup: ${backupName}`);
|
|
194
274
|
}
|
|
195
275
|
|
|
196
|
-
|
|
197
|
-
|
|
276
|
+
const targets = await resolveTargets(cwd, opts);
|
|
277
|
+
|
|
278
|
+
// Wire each selected host. Each installer is idempotent — re-running replaces
|
|
279
|
+
// Mink's prior entries rather than duplicating them — and touches only that
|
|
280
|
+
// host's configuration.
|
|
281
|
+
const wired: Record<string, string[]> = {};
|
|
282
|
+
for (const target of targets) {
|
|
283
|
+
if (target === "claude") {
|
|
284
|
+
const { settingsPath, rulePath } = installClaude(cwd, cliPath);
|
|
285
|
+
wired.claude = [`hooks: ${settingsPath}`, `rule: ${rulePath}`];
|
|
286
|
+
} else if (target === "pi") {
|
|
287
|
+
const r = installPi(cwd, cliPath);
|
|
288
|
+
wired.pi = [
|
|
289
|
+
`extension: ${r.extensionPath}`,
|
|
290
|
+
`guidance: ${r.guidancePath}`,
|
|
291
|
+
...(r.notePath ? [`note skill: ${r.notePath}`] : []),
|
|
292
|
+
];
|
|
293
|
+
}
|
|
294
|
+
}
|
|
198
295
|
|
|
199
296
|
mkdirSync(dir, { recursive: true });
|
|
200
297
|
|
|
@@ -217,28 +314,53 @@ export async function init(cwd: string): Promise<void> {
|
|
|
217
314
|
!Array.isArray(existingMeta.pathsByDevice)
|
|
218
315
|
? (existingMeta.pathsByDevice as Record<string, string>)
|
|
219
316
|
: {};
|
|
317
|
+
// Record the set of wired hosts as the authoritative source of truth, unioned
|
|
318
|
+
// with any previously wired host so a single-target re-init never silently
|
|
319
|
+
// unwires the other.
|
|
320
|
+
const priorAgents = Array.isArray(existingMeta?.agents)
|
|
321
|
+
? (existingMeta!.agents as string[])
|
|
322
|
+
: [];
|
|
323
|
+
const agents = Array.from(new Set([...priorAgents, ...targets]));
|
|
324
|
+
// Stamp the Mink version that generated these hooks so session-start (and
|
|
325
|
+
// `mink refresh-hooks`) can self-heal stale wiring after an upgrade.
|
|
326
|
+
let hooksVersion = "0.0.0";
|
|
327
|
+
try {
|
|
328
|
+
hooksVersion = require("../core/self-update").getInstallInfo().currentVersion;
|
|
329
|
+
} catch {
|
|
330
|
+
// Fall through with a sentinel; a missing/old stamp just forces one refresh.
|
|
331
|
+
}
|
|
220
332
|
atomicWriteJson(metaPath, {
|
|
221
333
|
...(existingMeta ?? {}),
|
|
222
334
|
cwd,
|
|
223
335
|
name: basename(cwd),
|
|
224
336
|
initTimestamp: existingMeta?.initTimestamp ?? new Date().toISOString(),
|
|
225
337
|
version: "0.1.0",
|
|
338
|
+
hooksVersion,
|
|
226
339
|
pathsByDevice: { ...existingPathsByDevice, [deviceId]: cwd },
|
|
340
|
+
agents,
|
|
227
341
|
...(isNotesProject ? { projectType: "notes" } : {}),
|
|
228
342
|
});
|
|
229
343
|
|
|
344
|
+
const printWiring = () => {
|
|
345
|
+
for (const id of Object.keys(wired)) {
|
|
346
|
+
const label = AGENTS.find((a) => a.id === id)?.label ?? id;
|
|
347
|
+
console.log(` ${label}:`);
|
|
348
|
+
for (const line of wired[id]) console.log(` ${line}`);
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
230
352
|
if (upgrading) {
|
|
231
353
|
console.log(`[mink] upgrade complete`);
|
|
232
354
|
console.log(` project: ${projectId}`);
|
|
233
|
-
console.log(`
|
|
234
|
-
|
|
355
|
+
console.log(` agents: ${agents.join(", ")}`);
|
|
356
|
+
printWiring();
|
|
235
357
|
} else {
|
|
236
358
|
console.log(`[mink] initialized`);
|
|
237
359
|
console.log(` project: ${projectId} (${identity.source})`);
|
|
238
360
|
console.log(` state: ${dir}`);
|
|
239
361
|
console.log(` runtime: ${runtime}`);
|
|
240
|
-
console.log(`
|
|
241
|
-
|
|
362
|
+
console.log(` agents: ${agents.join(", ")}`);
|
|
363
|
+
printWiring();
|
|
242
364
|
}
|
|
243
365
|
|
|
244
366
|
// Surface a one-time hint when the project is in a git repo with no remote
|
|
@@ -205,7 +205,7 @@ export async function postRead(cwd: string): Promise<void> {
|
|
|
205
205
|
// Persist state
|
|
206
206
|
atomicWriteJson(sessionPath(cwd), state);
|
|
207
207
|
|
|
208
|
-
// Tool-output compression (spec
|
|
208
|
+
// Tool-output compression (spec 22). Substitute a compact, reversible
|
|
209
209
|
// summary for a large whole-file read. Skipped for ranged reads (their
|
|
210
210
|
// output is only a slice) and a no-op unless compression is enabled. Uses
|
|
211
211
|
// the on-disk content as the canonical original so signature extraction
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Generic PostToolUse compression hook (spec
|
|
1
|
+
// Generic PostToolUse compression hook (spec 22) for tools that produce large,
|
|
2
2
|
// non-file output — Bash, Grep/Glob, and MCP tools. The Read tool is handled by
|
|
3
3
|
// post-read (which has the on-disk content and ranged-read awareness); this hook
|
|
4
4
|
// compresses the payload text directly.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// `mink refresh-hooks [--all]` — regenerate Mink's generated hook wiring after an
|
|
2
|
+
// upgrade so it matches the installed version's templates.
|
|
3
|
+
//
|
|
4
|
+
// (default) Refresh the current project.
|
|
5
|
+
// --all Refresh every registered project that exists on this device.
|
|
6
|
+
//
|
|
7
|
+
// `--all` is what `runSelfUpgrade` spawns (as the freshly-installed binary) right
|
|
8
|
+
// after a successful upgrade, so every project is refreshed eagerly rather than
|
|
9
|
+
// waiting for its next session-start.
|
|
10
|
+
|
|
11
|
+
import { existsSync } from "fs";
|
|
12
|
+
import { refreshProjectHooks } from "../core/hook-refresh";
|
|
13
|
+
|
|
14
|
+
export function refreshHooks(cwd: string, args: string[]): void {
|
|
15
|
+
const all = args.includes("--all");
|
|
16
|
+
|
|
17
|
+
if (!all) {
|
|
18
|
+
const r = refreshProjectHooks(cwd, { force: true });
|
|
19
|
+
console.log(
|
|
20
|
+
r.refreshed
|
|
21
|
+
? `[mink] refreshed hooks (${r.agents.join(", ")}) → ${r.version}`
|
|
22
|
+
: "[mink] nothing to refresh — run `mink init` first."
|
|
23
|
+
);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { listRegisteredProjects } = require("../core/project-registry");
|
|
28
|
+
const { getOrCreateDeviceId } = require("../core/device");
|
|
29
|
+
const deviceId = getOrCreateDeviceId();
|
|
30
|
+
|
|
31
|
+
let refreshed = 0;
|
|
32
|
+
for (const p of listRegisteredProjects()) {
|
|
33
|
+
const local = p.pathsByDevice?.[deviceId] ?? p.cwd;
|
|
34
|
+
if (!local || !existsSync(local)) continue; // not present on this device
|
|
35
|
+
const r = refreshProjectHooks(local, { force: true });
|
|
36
|
+
if (r.refreshed) {
|
|
37
|
+
refreshed++;
|
|
38
|
+
console.log(` ${p.name} (${r.agents.join(", ")})`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
console.log(`[mink] refreshed hooks for ${refreshed} project(s) → installed version.`);
|
|
42
|
+
}
|
package/src/commands/retrieve.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// `mink retrieve <token>` — return the byte-exact original of a previously
|
|
2
|
-
// compressed tool output (spec
|
|
2
|
+
// compressed tool output (spec 22 §Reversibility). Prints the original to
|
|
3
3
|
// stdout on a hit; on a miss (unknown or expired token) it prints a short,
|
|
4
4
|
// non-fatal notice to stderr and exits 0 so the assistant is never stranded by
|
|
5
5
|
// an error.
|
|
@@ -41,6 +41,17 @@ export function sessionStart(cwd: string): void {
|
|
|
41
41
|
// Migration is best-effort; never block session-start
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// Self-heal stale hook wiring after a Mink upgrade. Regenerates this project's
|
|
45
|
+
// configured hooks (Claude settings, Pi extension) when the generating version
|
|
46
|
+
// changed, so a `mink upgrade` never leaves the user re-running `mink init`.
|
|
47
|
+
// Idempotent, version-gated, and silent — never blocks session-start.
|
|
48
|
+
try {
|
|
49
|
+
const { refreshHooksIfStale } = require("../core/hook-refresh");
|
|
50
|
+
refreshHooksIfStale(cwd);
|
|
51
|
+
} catch {
|
|
52
|
+
// Never crash hooks
|
|
53
|
+
}
|
|
54
|
+
|
|
44
55
|
// Sync pull before session begins (if enabled)
|
|
45
56
|
try {
|
|
46
57
|
const { isSyncInitialized, syncPull } = require("../core/sync");
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
|
|
6
|
+
// Supported host coding assistants Mink can attach to. Adding a new host is a
|
|
7
|
+
// matter of appending an entry here plus an installer in init.ts.
|
|
8
|
+
export type AgentId = "claude" | "pi";
|
|
9
|
+
|
|
10
|
+
export interface AgentMeta {
|
|
11
|
+
id: AgentId;
|
|
12
|
+
label: string;
|
|
13
|
+
/** Project-local config directory that signals the host is used here. */
|
|
14
|
+
projectDir: string;
|
|
15
|
+
/** Per-user global config directory that signals the host is installed. */
|
|
16
|
+
globalDir: string;
|
|
17
|
+
/** Executable name to probe on PATH. */
|
|
18
|
+
bin: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const AGENTS: AgentMeta[] = [
|
|
22
|
+
{
|
|
23
|
+
id: "claude",
|
|
24
|
+
label: "Claude Code",
|
|
25
|
+
projectDir: ".claude",
|
|
26
|
+
globalDir: join(homedir(), ".claude"),
|
|
27
|
+
bin: "claude",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "pi",
|
|
31
|
+
label: "Pi",
|
|
32
|
+
projectDir: ".pi",
|
|
33
|
+
globalDir: join(homedir(), ".pi"),
|
|
34
|
+
bin: "pi",
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export interface AgentInfo extends AgentMeta {
|
|
39
|
+
detected: boolean;
|
|
40
|
+
/** Human-readable reasons the host was (or was not) detected. */
|
|
41
|
+
signals: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function commandExists(bin: string): boolean {
|
|
45
|
+
try {
|
|
46
|
+
const probe = process.platform === "win32" ? `where ${bin}` : `command -v ${bin}`;
|
|
47
|
+
execSync(probe, { stdio: "ignore" });
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Inspect a single host's footprint relative to `cwd`. Detection is best-effort
|
|
56
|
+
* and layered, strongest signal first: a project-local config directory is the
|
|
57
|
+
* clearest sign the host is actually used here; a global config directory or a
|
|
58
|
+
* binary on PATH only prove the host is installed somewhere.
|
|
59
|
+
*/
|
|
60
|
+
export function detectAgent(meta: AgentMeta, cwd: string): AgentInfo {
|
|
61
|
+
const signals: string[] = [];
|
|
62
|
+
if (existsSync(join(cwd, meta.projectDir))) {
|
|
63
|
+
signals.push(`project config (${meta.projectDir}/)`);
|
|
64
|
+
}
|
|
65
|
+
if (existsSync(meta.globalDir)) {
|
|
66
|
+
signals.push("global config");
|
|
67
|
+
}
|
|
68
|
+
if (commandExists(meta.bin)) {
|
|
69
|
+
signals.push("on PATH");
|
|
70
|
+
}
|
|
71
|
+
return { ...meta, detected: signals.length > 0, signals };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function detectAgents(cwd: string): AgentInfo[] {
|
|
75
|
+
return AGENTS.map((m) => detectAgent(m, cwd));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function resolveTargetsFromFlag(flag: string): AgentId[] {
|
|
79
|
+
const normalized = flag.trim().toLowerCase();
|
|
80
|
+
if (normalized === "all") return AGENTS.map((a) => a.id);
|
|
81
|
+
const ids = normalized
|
|
82
|
+
.split(",")
|
|
83
|
+
.map((s) => s.trim())
|
|
84
|
+
.filter(Boolean);
|
|
85
|
+
const valid = AGENTS.map((a) => a.id) as string[];
|
|
86
|
+
const resolved = ids.filter((id): id is AgentId => valid.includes(id));
|
|
87
|
+
return resolved;
|
|
88
|
+
}
|