mintree 0.1.2

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.
Files changed (64) hide show
  1. package/README.md +188 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +12 -0
  4. package/dist/commands/dashboard.d.ts +2 -0
  5. package/dist/commands/dashboard.js +849 -0
  6. package/dist/commands/doctor.d.ts +2 -0
  7. package/dist/commands/doctor.js +327 -0
  8. package/dist/commands/helpers/index.d.ts +1 -0
  9. package/dist/commands/helpers/index.js +1 -0
  10. package/dist/commands/helpers/session-signal/end.d.ts +2 -0
  11. package/dist/commands/helpers/session-signal/end.js +9 -0
  12. package/dist/commands/helpers/session-signal/index.d.ts +1 -0
  13. package/dist/commands/helpers/session-signal/index.js +1 -0
  14. package/dist/commands/helpers/session-signal/install.d.ts +2 -0
  15. package/dist/commands/helpers/session-signal/install.js +25 -0
  16. package/dist/commands/helpers/session-signal/notification.d.ts +2 -0
  17. package/dist/commands/helpers/session-signal/notification.js +9 -0
  18. package/dist/commands/helpers/session-signal/prompt.d.ts +2 -0
  19. package/dist/commands/helpers/session-signal/prompt.js +9 -0
  20. package/dist/commands/helpers/session-signal/stop.d.ts +2 -0
  21. package/dist/commands/helpers/session-signal/stop.js +9 -0
  22. package/dist/commands/helpers/shell-init.d.ts +11 -0
  23. package/dist/commands/helpers/shell-init.js +111 -0
  24. package/dist/commands/index.d.ts +2 -0
  25. package/dist/commands/index.js +6 -0
  26. package/dist/commands/init.d.ts +2 -0
  27. package/dist/commands/init.js +129 -0
  28. package/dist/commands/worktree/clean.d.ts +11 -0
  29. package/dist/commands/worktree/clean.js +206 -0
  30. package/dist/commands/worktree/create.d.ts +18 -0
  31. package/dist/commands/worktree/create.js +93 -0
  32. package/dist/commands/worktree/index.d.ts +1 -0
  33. package/dist/commands/worktree/index.js +1 -0
  34. package/dist/commands/worktree/list.d.ts +10 -0
  35. package/dist/commands/worktree/list.js +143 -0
  36. package/dist/commands/worktree/remove.d.ts +12 -0
  37. package/dist/commands/worktree/remove.js +46 -0
  38. package/dist/commands/worktree/work.d.ts +15 -0
  39. package/dist/commands/worktree/work.js +192 -0
  40. package/dist/lib/branch.d.ts +26 -0
  41. package/dist/lib/branch.js +57 -0
  42. package/dist/lib/claude.d.ts +26 -0
  43. package/dist/lib/claude.js +67 -0
  44. package/dist/lib/dashboard.d.ts +50 -0
  45. package/dist/lib/dashboard.js +139 -0
  46. package/dist/lib/exec.d.ts +2 -0
  47. package/dist/lib/exec.js +15 -0
  48. package/dist/lib/git.d.ts +110 -0
  49. package/dist/lib/git.js +320 -0
  50. package/dist/lib/github.d.ts +7 -0
  51. package/dist/lib/github.js +15 -0
  52. package/dist/lib/markers.d.ts +21 -0
  53. package/dist/lib/markers.js +43 -0
  54. package/dist/lib/metadata.d.ts +18 -0
  55. package/dist/lib/metadata.js +44 -0
  56. package/dist/lib/session-signal.d.ts +63 -0
  57. package/dist/lib/session-signal.js +160 -0
  58. package/dist/lib/worktreeCreate.d.ts +36 -0
  59. package/dist/lib/worktreeCreate.js +184 -0
  60. package/dist/lib/worktreeRemove.d.ts +21 -0
  61. package/dist/lib/worktreeRemove.js +84 -0
  62. package/package.json +63 -0
  63. package/shell/init.bash +106 -0
  64. package/shell/init.zsh +125 -0
@@ -0,0 +1,2 @@
1
+ export declare const description = "Check system requirements and Claude Code integrations";
2
+ export default function Doctor(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,327 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { Box, Text } from "ink";
4
+ import Spinner from "ink-spinner";
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ import { createRequire } from "module";
8
+ import { tryExec, getPath } from "../lib/exec.js";
9
+ import { ghCliAvailable, getGhUserLogin, getRepoFullName } from "../lib/github.js";
10
+ import { resolveClaudeBinary } from "../lib/claude.js";
11
+ import { findMainRepoRoot, getMintreeDir, getInitScriptPath, isGitIgnored, isExecutable, pathExists, } from "../lib/git.js";
12
+ const require = createRequire(import.meta.url);
13
+ const { version } = require("../../package.json");
14
+ export const description = "Check system requirements and Claude Code integrations";
15
+ async function checkTool(name, description, required, versionCommand, hint) {
16
+ const binPath = await getPath(name);
17
+ if (!binPath) {
18
+ return { name, description, required, installed: false, hint };
19
+ }
20
+ const ver = await tryExec(versionCommand);
21
+ return {
22
+ name,
23
+ description,
24
+ required,
25
+ installed: true,
26
+ version: ver || "unknown",
27
+ path: binPath,
28
+ };
29
+ }
30
+ async function checkClaude() {
31
+ const resolved = resolveClaudeBinary();
32
+ if (!resolved) {
33
+ return {
34
+ name: "claude",
35
+ description: "Claude Code CLI",
36
+ required: true,
37
+ installed: false,
38
+ hint: "Install: npm install -g @anthropic-ai/claude-code",
39
+ };
40
+ }
41
+ const ver = await tryExec(`"${resolved}" --version 2>/dev/null | head -1`);
42
+ return {
43
+ name: "claude",
44
+ description: "Claude Code CLI",
45
+ required: true,
46
+ installed: true,
47
+ version: ver || "unknown",
48
+ path: resolved,
49
+ };
50
+ }
51
+ async function checkGh() {
52
+ const binPath = await getPath("gh");
53
+ if (!binPath) {
54
+ return {
55
+ name: "gh",
56
+ description: "GitHub CLI for issues + PRs",
57
+ required: true,
58
+ installed: false,
59
+ hint: "Install: brew install gh && gh auth login",
60
+ };
61
+ }
62
+ const ver = await tryExec("gh --version | head -1");
63
+ const login = await getGhUserLogin();
64
+ if (!login) {
65
+ return {
66
+ name: "gh",
67
+ description: "GitHub CLI for issues + PRs",
68
+ required: true,
69
+ installed: true,
70
+ version: ver || "unknown",
71
+ path: binPath,
72
+ hint: "Run: gh auth login",
73
+ };
74
+ }
75
+ return {
76
+ name: "gh",
77
+ description: "GitHub CLI for issues + PRs",
78
+ required: true,
79
+ installed: true,
80
+ version: ver || "unknown",
81
+ path: binPath,
82
+ authStatus: `Authenticated as ${login}`,
83
+ };
84
+ }
85
+ async function checkGithubIssues() {
86
+ const inGitRepo = findMainRepoRoot() !== null;
87
+ if (!(await ghCliAvailable())) {
88
+ return {
89
+ authenticated: false,
90
+ inGitRepo,
91
+ hint: "Install: brew install gh && gh auth login",
92
+ };
93
+ }
94
+ const login = await getGhUserLogin();
95
+ if (!login) {
96
+ return { authenticated: false, inGitRepo, hint: "Run: gh auth login" };
97
+ }
98
+ if (!inGitRepo) {
99
+ // Auth is fine; we're just not in a repo. Don't flag this as a failure.
100
+ return { authenticated: true, accountName: login, inGitRepo, repoName: null };
101
+ }
102
+ const repoName = await getRepoFullName();
103
+ return {
104
+ authenticated: true,
105
+ accountName: login,
106
+ repoName,
107
+ inGitRepo,
108
+ hint: !repoName
109
+ ? "Current repo is not on GitHub (gh repo view failed in this directory)"
110
+ : undefined,
111
+ };
112
+ }
113
+ function checkRemoteControl() {
114
+ const home = process.env["HOME"] || "";
115
+ const configPath = path.join(home, ".claude.json");
116
+ try {
117
+ if (fs.existsSync(configPath)) {
118
+ const content = fs.readFileSync(configPath, "utf-8");
119
+ const config = JSON.parse(content);
120
+ if (config.remoteControlAtStartup === true) {
121
+ return { enabled: true };
122
+ }
123
+ }
124
+ }
125
+ catch {
126
+ // JSON parse error or read error — fall through to disabled.
127
+ }
128
+ return {
129
+ enabled: false,
130
+ hint: 'Run /config in Claude Code and enable "Enable Remote Control for all sessions"',
131
+ };
132
+ }
133
+ function checkSessionSignalHooks() {
134
+ const home = process.env["HOME"] || "";
135
+ const settingsPath = path.join(home, ".claude", "settings.json");
136
+ const requiredEvents = ["Notification", "Stop", "UserPromptSubmit", "SessionEnd"];
137
+ const missing = [];
138
+ try {
139
+ if (!fs.existsSync(settingsPath)) {
140
+ return {
141
+ configured: false,
142
+ missingHooks: requiredEvents,
143
+ hint: "Run: mintree helpers session-signal install",
144
+ };
145
+ }
146
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
147
+ const hooks = settings.hooks || {};
148
+ for (const event of requiredEvents) {
149
+ const eventHooks = hooks[event];
150
+ if (!Array.isArray(eventHooks)) {
151
+ missing.push(event);
152
+ continue;
153
+ }
154
+ const found = eventHooks.some((entry) => {
155
+ const inner = entry.hooks || [];
156
+ return inner.some(h => typeof h.command === "string" && h.command.includes("mintree helpers session-signal"));
157
+ });
158
+ if (!found)
159
+ missing.push(event);
160
+ }
161
+ }
162
+ catch {
163
+ return {
164
+ configured: false,
165
+ missingHooks: requiredEvents,
166
+ hint: "Could not parse ~/.claude/settings.json. Run: mintree helpers session-signal install",
167
+ };
168
+ }
169
+ if (missing.length === 0) {
170
+ return { configured: true, missingHooks: [] };
171
+ }
172
+ return {
173
+ configured: false,
174
+ missingHooks: missing,
175
+ hint: "Run: mintree helpers session-signal install",
176
+ };
177
+ }
178
+ function checkShellIntegration() {
179
+ const shellEnv = process.env["SHELL"] || "";
180
+ const shell = shellEnv.includes("zsh") ? "zsh" : shellEnv.includes("bash") ? "bash" : null;
181
+ const configured = process.env["MINTREE_SHELL_INTEGRATION"] === "1";
182
+ return { configured, shell };
183
+ }
184
+ function checkMintreeSetup() {
185
+ const root = findMainRepoRoot();
186
+ if (!root) {
187
+ return {
188
+ isGitRepo: false,
189
+ mintreeFolderExists: false,
190
+ metadataExists: false,
191
+ initShExists: false,
192
+ initShExecutable: false,
193
+ worktreesIgnored: false,
194
+ sessionStatesIgnored: false,
195
+ hints: ["Not in a git repository — run `git init` first, then `mintree init`."],
196
+ };
197
+ }
198
+ const mintreeDir = getMintreeDir(root);
199
+ const metadataPath = path.join(mintreeDir, "metadata.json");
200
+ const initShPath = getInitScriptPath(root);
201
+ const mintreeFolderExists = pathExists(mintreeDir);
202
+ const metadataExists = pathExists(metadataPath);
203
+ const initShExists = pathExists(initShPath);
204
+ const initShExecutable = initShExists && isExecutable(initShPath);
205
+ const worktreesIgnored = isGitIgnored(".mintree/worktrees", root);
206
+ const sessionStatesIgnored = isGitIgnored(".mintree/session-states", root);
207
+ const hints = [];
208
+ if (!mintreeFolderExists) {
209
+ hints.push("Run: mintree init");
210
+ }
211
+ else {
212
+ if (!metadataExists)
213
+ hints.push("Missing .mintree/metadata.json — run: mintree init");
214
+ if (!worktreesIgnored)
215
+ hints.push("Add `.mintree/worktrees/` to .gitignore");
216
+ if (!sessionStatesIgnored)
217
+ hints.push("Add `.mintree/session-states/` to .gitignore");
218
+ if (initShExists && !initShExecutable) {
219
+ hints.push(`Make init.sh executable: chmod +x ${initShPath}`);
220
+ }
221
+ }
222
+ return {
223
+ isGitRepo: true,
224
+ mainRepoRoot: root,
225
+ mintreeFolderExists,
226
+ metadataExists,
227
+ initShExists,
228
+ initShExecutable,
229
+ worktreesIgnored,
230
+ sessionStatesIgnored,
231
+ hints,
232
+ };
233
+ }
234
+ function StatusIcon({ ok, required }) {
235
+ if (ok)
236
+ return _jsx(Text, { color: "green", children: "\u2713" });
237
+ return required ? _jsx(Text, { color: "red", children: "\u2717" }) : _jsx(Text, { color: "yellow", children: "\u25CB" });
238
+ }
239
+ function ToolRow({ tool }) {
240
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(StatusIcon, { ok: tool.installed && !tool.hint, required: tool.required }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: tool.name }), _jsxs(Text, { dimColor: true, children: [" - ", tool.description] }), !tool.required && _jsx(Text, { dimColor: true, children: " (optional)" })] }), tool.installed ? (_jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["Version: ", tool.version] }), tool.path && _jsxs(Text, { dimColor: true, children: ["Path: ", tool.path] }), tool.authStatus && _jsxs(Text, { dimColor: true, children: ["Auth: ", tool.authStatus] }), tool.hint && _jsxs(Text, { color: "yellow", children: ["\u21B3 ", tool.hint] })] })) : (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: "yellow", children: ["\u21B3 ", tool.hint] }) }))] }));
241
+ }
242
+ function GithubIssuesRow({ gh }) {
243
+ // Required only when we're inside a git repo. Outside one, the row is
244
+ // purely informational (auth check) so doctor can stay green when run
245
+ // from $HOME or any non-repo directory.
246
+ const required = gh.inGitRepo;
247
+ const ok = required ? gh.authenticated && !!gh.repoName : gh.authenticated;
248
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(StatusIcon, { ok: ok, required: required }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "GitHub Issues" }), _jsx(Text, { dimColor: true, children: " - issue listing + PR ops" }), !required && _jsx(Text, { dimColor: true, children: " (no repo here)" })] }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [gh.authenticated ? (_jsxs(_Fragment, { children: [_jsxs(Text, { dimColor: true, children: ["User: ", gh.accountName] }), required && (_jsxs(Text, { dimColor: true, children: ["Repo: ", gh.repoName ?? "(not a GitHub repo)"] }))] })) : (_jsx(Text, { dimColor: true, children: "Not authenticated" })), gh.hint && _jsxs(Text, { color: "yellow", children: ["\u21B3 ", gh.hint] })] })] }));
249
+ }
250
+ function ShellRow({ status }) {
251
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(StatusIcon, { ok: status.configured, required: true }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Shell Integration" }), _jsx(Text, { dimColor: true, children: " - enables `cd` into worktrees" })] }), _jsx(Box, { marginLeft: 2, flexDirection: "column", children: status.configured ? (_jsxs(Text, { dimColor: true, children: ["Shell: ", status.shell ?? "unknown", " (MINTREE_SHELL_INTEGRATION=1)"] })) : status.shell ? (_jsx(Text, { color: "yellow", children: `↳ Add to ~/.${status.shell}rc: eval "$(mintree helpers shell-init ${status.shell})"` })) : (_jsx(Text, { color: "yellow", children: "\u21B3 Unsupported shell. mintree shell integration supports zsh and bash." })) })] }));
252
+ }
253
+ function RemoteControlRow({ status }) {
254
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(StatusIcon, { ok: status.enabled, required: false }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Remote Control" }), _jsx(Text, { dimColor: true, children: " - resume Claude sessions from any device" }), _jsx(Text, { dimColor: true, children: " (optional)" })] }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["Enabled: ", status.enabled ? "yes" : "no"] }), status.hint && _jsxs(Text, { color: "yellow", children: ["\u21B3 ", status.hint] })] })] }));
255
+ }
256
+ function SessionSignalRow({ status }) {
257
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(StatusIcon, { ok: status.configured, required: false }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Session Signal Hooks" }), _jsx(Text, { dimColor: true, children: " - live session state for the dashboard" }), _jsx(Text, { dimColor: true, children: " (optional)" })] }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [status.configured ? (_jsx(Text, { dimColor: true, children: "All 4 hooks configured" })) : (_jsxs(Text, { dimColor: true, children: ["Missing: ", status.missingHooks.join(", ")] })), status.hint && _jsxs(Text, { color: "yellow", children: ["\u21B3 ", status.hint] })] })] }));
258
+ }
259
+ function MintreeSetupRow({ status }) {
260
+ if (!status.isGitRepo) {
261
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(StatusIcon, { ok: false, required: false }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Repo Setup" }), _jsx(Text, { dimColor: true, children: " - .mintree/ configuration" }), _jsx(Text, { dimColor: true, children: " (optional)" })] }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "Not in a git repository" }), status.hints.map((h, i) => (_jsxs(Text, { color: "yellow", children: ["\u21B3 ", h] }, i)))] })] }));
262
+ }
263
+ const ok = status.mintreeFolderExists &&
264
+ status.metadataExists &&
265
+ status.worktreesIgnored &&
266
+ status.sessionStatesIgnored &&
267
+ (!status.initShExists || status.initShExecutable);
268
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(StatusIcon, { ok: ok, required: false }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Repo Setup" }), _jsx(Text, { dimColor: true, children: " - .mintree/ configuration" }), _jsx(Text, { dimColor: true, children: " (optional)" })] }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["Main repo: ", status.mainRepoRoot] }), _jsxs(Text, { dimColor: true, children: [".mintree/: ", status.mintreeFolderExists ? "exists" : "missing"] }), status.mintreeFolderExists && (_jsxs(_Fragment, { children: [_jsxs(Text, { dimColor: true, children: ["metadata.json: ", status.metadataExists ? "exists" : "missing"] }), _jsxs(Text, { dimColor: true, children: ["init.sh:", " ", status.initShExists
269
+ ? status.initShExecutable
270
+ ? "executable"
271
+ : "not executable"
272
+ : "not present (optional)"] }), _jsxs(Text, { dimColor: true, children: [".mintree/worktrees ignored: ", status.worktreesIgnored ? "yes" : "no"] }), _jsxs(Text, { dimColor: true, children: [".mintree/session-states ignored: ", status.sessionStatesIgnored ? "yes" : "no"] })] })), status.hints.map((h, i) => (_jsxs(Text, { color: "yellow", children: ["\u21B3 ", h] }, i)))] })] }));
273
+ }
274
+ export default function Doctor() {
275
+ const [tools, setTools] = useState(null);
276
+ const [gh, setGh] = useState(null);
277
+ const [rc, setRc] = useState(null);
278
+ const [hooks, setHooks] = useState(null);
279
+ const [setup, setSetup] = useState(null);
280
+ const [shell, setShell] = useState(null);
281
+ useEffect(() => {
282
+ (async () => {
283
+ const toolResults = await Promise.all([
284
+ checkTool("git", "Version control", true, "git --version | head -1", "Install: brew install git"),
285
+ checkGh(),
286
+ checkClaude(),
287
+ checkTool("tmux", "Open worktrees in separate windows", false, "tmux -V", "Install: brew install tmux"),
288
+ ]);
289
+ const mintreeRow = {
290
+ name: "mintree",
291
+ description: "this CLI",
292
+ required: true,
293
+ installed: true,
294
+ version,
295
+ };
296
+ toolResults.unshift(mintreeRow);
297
+ const nodeRow = {
298
+ name: "node",
299
+ description: "Node.js runtime (≥ 20)",
300
+ required: true,
301
+ installed: true,
302
+ version: process.version,
303
+ };
304
+ toolResults.unshift(nodeRow);
305
+ const ghRes = await checkGithubIssues();
306
+ setTools(toolResults);
307
+ setGh(ghRes);
308
+ setRc(checkRemoteControl());
309
+ setHooks(checkSessionSignalHooks());
310
+ setSetup(checkMintreeSetup());
311
+ setShell(checkShellIntegration());
312
+ })();
313
+ }, []);
314
+ const loading = !tools || !gh || !rc || !hooks || !setup || !shell;
315
+ if (loading) {
316
+ return (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Checking system requirements..." })] }));
317
+ }
318
+ const requiredMissing = tools.filter(t => t.required && (!t.installed || t.hint));
319
+ const optionalMissing = tools.filter(t => !t.required && !t.installed);
320
+ // GitHub Issues only counts toward the required tally when we're inside a
321
+ // git repo; otherwise the auth-only check is purely informational.
322
+ const ghOk = gh.inGitRepo ? gh.authenticated && !!gh.repoName : true;
323
+ const shellOk = shell.configured;
324
+ const allRequired = requiredMissing.length === 0 && ghOk && shellOk;
325
+ const requiredFailing = requiredMissing.length + (ghOk ? 0 : 1) + (shellOk ? 0 : 1);
326
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Mintree Doctor" }), _jsxs(Text, { dimColor: true, children: [" v", version] })] }), _jsx(Box, { marginBottom: 1, flexDirection: "column", children: _jsx(Text, { bold: true, underline: true, children: "CLI Tools" }) }), tools.map(t => (_jsx(ToolRow, { tool: t }, t.name))), _jsx(Box, { marginBottom: 1, marginTop: 1, flexDirection: "column", children: _jsx(Text, { bold: true, underline: true, children: "Integrations" }) }), _jsx(GithubIssuesRow, { gh: gh }), _jsx(ShellRow, { status: shell }), _jsx(MintreeSetupRow, { status: setup }), _jsx(Box, { marginBottom: 1, marginTop: 1, flexDirection: "column", children: _jsx(Text, { bold: true, underline: true, children: "Claude Code" }) }), _jsx(RemoteControlRow, { status: rc }), _jsx(SessionSignalRow, { status: hooks }), _jsx(Box, { marginTop: 1, borderStyle: "single", borderColor: allRequired ? "green" : "yellow", paddingX: 2, children: allRequired ? (_jsx(Text, { color: "green", children: "All required checks pass. mintree is ready to use." })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "yellow", children: [requiredFailing, " required item(s) need attention"] }), optionalMissing.length > 0 && (_jsxs(Text, { dimColor: true, children: [optionalMissing.length, " optional item(s) not installed"] }))] })) })] }));
327
+ }
@@ -0,0 +1 @@
1
+ export declare const description = "Helpers for shell integration and Claude Code hooks";
@@ -0,0 +1 @@
1
+ export const description = "Helpers for shell integration and Claude Code hooks";
@@ -0,0 +1,2 @@
1
+ export declare const description = "Hook handler for Claude's SessionEnd event (writes state=exited)";
2
+ export default function End(): null;
@@ -0,0 +1,9 @@
1
+ import { useEffect } from "react";
2
+ import { signalState } from "../../../lib/session-signal.js";
3
+ export const description = "Hook handler for Claude's SessionEnd event (writes state=exited)";
4
+ export default function End() {
5
+ useEffect(() => {
6
+ signalState("exited");
7
+ }, []);
8
+ return null;
9
+ }
@@ -0,0 +1 @@
1
+ export declare const description = "Install Claude Code hooks and write session state files";
@@ -0,0 +1 @@
1
+ export const description = "Install Claude Code hooks and write session state files";
@@ -0,0 +1,2 @@
1
+ export declare const description = "Install the four mintree hooks in ~/.claude/settings.json";
2
+ export default function Install(): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,25 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { Box, Text } from "ink";
4
+ import { installHooks } from "../../../lib/session-signal.js";
5
+ export const description = "Install the four mintree hooks in ~/.claude/settings.json";
6
+ export default function Install() {
7
+ const [result, setResult] = useState(null);
8
+ useEffect(() => {
9
+ setTimeout(() => {
10
+ try {
11
+ const { settingsPath, created } = installHooks();
12
+ setResult({ ok: true, settingsPath, created });
13
+ }
14
+ catch (err) {
15
+ setResult({ ok: false, message: err instanceof Error ? err.message : String(err) });
16
+ }
17
+ }, 0);
18
+ }, []);
19
+ if (!result)
20
+ return null;
21
+ if (!result.ok) {
22
+ return (_jsx(Box, { padding: 1, children: _jsxs(Text, { color: "red", bold: true, children: ["\u2717 ", result.message] }) }));
23
+ }
24
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "mintree helpers session-signal install" }) }), _jsxs(Text, { children: [_jsx(Text, { color: "green", children: "\u2713" }), " ", result.created ? "created" : "updated", " ", _jsx(Text, { dimColor: true, children: result.settingsPath })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "Hooks installed: UserPromptSubmit, Stop, SessionEnd, Notification." }), _jsx(Text, { dimColor: true, children: "Re-run safely \u2014 existing mintree entries are replaced; non-mintree hooks are preserved." })] })] }));
25
+ }
@@ -0,0 +1,2 @@
1
+ export declare const description = "Hook handler for Claude's Notification event (writes state=waiting)";
2
+ export default function Notification(): null;
@@ -0,0 +1,9 @@
1
+ import { useEffect } from "react";
2
+ import { signalState } from "../../../lib/session-signal.js";
3
+ export const description = "Hook handler for Claude's Notification event (writes state=waiting)";
4
+ export default function Notification() {
5
+ useEffect(() => {
6
+ signalState("waiting");
7
+ }, []);
8
+ return null;
9
+ }
@@ -0,0 +1,2 @@
1
+ export declare const description = "Hook handler for Claude's UserPromptSubmit event (writes state=active)";
2
+ export default function Prompt(): null;
@@ -0,0 +1,9 @@
1
+ import { useEffect } from "react";
2
+ import { signalState } from "../../../lib/session-signal.js";
3
+ export const description = "Hook handler for Claude's UserPromptSubmit event (writes state=active)";
4
+ export default function Prompt() {
5
+ useEffect(() => {
6
+ signalState("active");
7
+ }, []);
8
+ return null;
9
+ }
@@ -0,0 +1,2 @@
1
+ export declare const description = "Hook handler for Claude's Stop event (writes state=idle)";
2
+ export default function Stop(): null;
@@ -0,0 +1,9 @@
1
+ import { useEffect } from "react";
2
+ import { signalState } from "../../../lib/session-signal.js";
3
+ export const description = "Hook handler for Claude's Stop event (writes state=idle)";
4
+ export default function Stop() {
5
+ useEffect(() => {
6
+ signalState("idle");
7
+ }, []);
8
+ return null;
9
+ }
@@ -0,0 +1,11 @@
1
+ import { z } from "zod";
2
+ export declare const description = "Output shell integration script (eval in your shell rc)";
3
+ export declare const args: z.ZodTuple<[z.ZodDefault<z.ZodEnum<{
4
+ zsh: "zsh";
5
+ bash: "bash";
6
+ }>>], null>;
7
+ type Props = {
8
+ args: z.infer<typeof args>;
9
+ };
10
+ export default function ShellInit({ args }: Props): null;
11
+ export {};
@@ -0,0 +1,111 @@
1
+ import { useEffect } from "react";
2
+ import { argument } from "pastel";
3
+ import { z } from "zod";
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ import { fileURLToPath } from "url";
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ // At build time this file lives at dist/commands/helpers/shell-init.js — the
10
+ // shell templates ship at <pkg>/shell/. Three levels up from __dirname.
11
+ const shellDir = path.resolve(__dirname, "..", "..", "..", "shell");
12
+ export const description = "Output shell integration script (eval in your shell rc)";
13
+ export const args = z.tuple([
14
+ z
15
+ .enum(["zsh", "bash"])
16
+ .default("zsh")
17
+ .describe(argument({
18
+ name: "shell",
19
+ description: "Target shell: zsh (default) or bash",
20
+ })),
21
+ ]);
22
+ /**
23
+ * Wraps the integration body in a bootstrap that:
24
+ * 1. Computes a per-shell cache path under XDG_CACHE_HOME/mintree/.
25
+ * 2. Writes the body verbatim to that cache file (using a heredoc so the
26
+ * body is parsed by the cache, not by this bootstrap).
27
+ * 3. Sources the cache so this shell gets the integration immediately.
28
+ *
29
+ * The cache file starts with a self-validation guard: if the `mintree`
30
+ * binary on $PATH is newer than the cache, it re-runs the bootstrap so the
31
+ * cache regenerates after an update. Subsequent shells can skip the bootstrap
32
+ * entirely — see the .rc one-liner in the comment header.
33
+ */
34
+ function buildBootstrap(shell, body) {
35
+ const cachePath = shell === "zsh"
36
+ ? `\${MINTREE_CACHE_DIR:-\${XDG_CACHE_HOME:-$HOME/.cache}/mintree}/init-zsh.zsh`
37
+ : `\${MINTREE_CACHE_DIR:-\${XDG_CACHE_HOME:-$HOME/.cache}/mintree}/init-bash.bash`;
38
+ const mkdirParent = shell === "zsh" ? `mkdir -p "\${_mintree_cache:h}"` : `mkdir -p "$(dirname "$_mintree_cache")"`;
39
+ // `${(%):-%x}` in zsh is the script path of the file being sourced. In
40
+ // bash that's `${BASH_SOURCE[0]}`. We use it to compare against the
41
+ // `mintree` binary's mtime (`-nt`) — when mintree is newer, the cache
42
+ // is stale and we regenerate.
43
+ const sourcePathExpansion = shell === "zsh" ? `\${(%):-%x}` : `\${BASH_SOURCE[0]}`;
44
+ const commandLookup = shell === "zsh" ? `\${commands[mintree]:-}` : `$(command -v mintree)`;
45
+ const headerComment = shell === "zsh"
46
+ ? `# mintree shell integration bootstrap (zsh) — self-caching
47
+ #
48
+ # Caches the integration body in \${XDG_CACHE_HOME:-~/.cache}/mintree/init-zsh.zsh
49
+ # on first run so subsequent shells can source it without spawning node.
50
+ # After upgrading mintree the cache self-invalidates by mtime.
51
+ #
52
+ # .zshrc one-liner with first-run fallback:
53
+ # _SI=\${XDG_CACHE_HOME:-$HOME/.cache}/mintree/init-zsh.zsh
54
+ # [[ -f $_SI ]] && source $_SI || eval "$(mintree helpers shell-init zsh)"`
55
+ : `# mintree shell integration bootstrap (bash) — self-caching
56
+ #
57
+ # Caches the integration body in \${XDG_CACHE_HOME:-~/.cache}/mintree/init-bash.bash
58
+ # on first run so subsequent shells can source it without spawning node.
59
+ # After upgrading mintree the cache self-invalidates by mtime.
60
+ #
61
+ # .bashrc one-liner with first-run fallback:
62
+ # _SI=\${XDG_CACHE_HOME:-$HOME/.cache}/mintree/init-bash.bash
63
+ # [[ -f "$_SI" ]] && source "$_SI" || eval "$(mintree helpers shell-init bash)"`;
64
+ // Trailing-newline-stripped body — we add our own newline structure.
65
+ const trimmedBody = body.endsWith("\n") ? body.slice(0, -1) : body;
66
+ return `${headerComment}
67
+
68
+ _mintree_cache="${cachePath}"
69
+ ${mkdirParent}
70
+
71
+ # Single-quoted heredoc delimiter so $vars and \`commands\` in the body
72
+ # aren't expanded here — they're only evaluated when the cache file is
73
+ # sourced.
74
+ cat > "$_mintree_cache" <<'MINTREE_INIT_BODY_EOF__'
75
+ # AUTO-GENERATED CACHE — do not edit. Regenerate with:
76
+ # eval "$(mintree helpers shell-init ${shell})"
77
+
78
+ # Self-validation: when the mintree binary is newer than this cache,
79
+ # re-run the bootstrap so the body refreshes, then return so the (now
80
+ # stale) definitions below aren't re-loaded into the shell.
81
+ if [[ ${commandLookup} -nt ${sourcePathExpansion} ]]; then
82
+ eval "$(command mintree helpers shell-init ${shell})"
83
+ return
84
+ fi
85
+
86
+ ${trimmedBody}
87
+ MINTREE_INIT_BODY_EOF__
88
+
89
+ # Source the cache we just wrote so this shell gets the integration now.
90
+ source "$_mintree_cache"
91
+ unset _mintree_cache
92
+ `;
93
+ }
94
+ export default function ShellInit({ args }) {
95
+ const [shell] = args;
96
+ useEffect(() => {
97
+ const filePath = path.join(shellDir, `init.${shell}`);
98
+ try {
99
+ const body = fs.readFileSync(filePath, "utf-8");
100
+ const bootstrap = buildBootstrap(shell, body);
101
+ process.stdout.write(bootstrap);
102
+ process.exit(0);
103
+ }
104
+ catch (err) {
105
+ const message = err instanceof Error ? err.message : String(err);
106
+ process.stderr.write(`mintree: failed to read ${filePath}: ${message}\n`);
107
+ process.exit(1);
108
+ }
109
+ }, [shell]);
110
+ return null;
111
+ }
@@ -0,0 +1,2 @@
1
+ export declare const description = "Show welcome screen (TUI dashboard lands in Phase 4)";
2
+ export default function Index(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ export const description = "Show welcome screen (TUI dashboard lands in Phase 4)";
4
+ export default function Index() {
5
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsx(Text, { bold: true, color: "green", children: "mintree" }), _jsx(Text, { dimColor: true, children: "Issue-driven worktrees + Claude Code sessions." }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Run ", _jsx(Text, { bold: true, children: "mintree --help" }), " to list available commands."] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Phase 0 scaffolding \u00B7 `doctor`, `init` and `worktree` commands land in subsequent phases." }) })] }));
6
+ }
@@ -0,0 +1,2 @@
1
+ export declare const description = "Initialize the current repo for mintree (creates .mintree/, updates .gitignore)";
2
+ export default function Init(): import("react/jsx-runtime").JSX.Element;