santree 0.7.4 → 0.7.6
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 +2 -3
- package/dist/commands/config.d.ts +12 -0
- package/dist/commands/config.js +444 -0
- package/dist/commands/dashboard.js +187 -80
- package/dist/commands/helpers/statusline.js +0 -30
- package/dist/commands/issue/index.d.ts +1 -1
- package/dist/commands/issue/index.js +1 -1
- package/dist/commands/pr/context.d.ts +7 -0
- package/dist/commands/pr/context.js +36 -0
- package/dist/commands/pr/fix.d.ts +11 -2
- package/dist/commands/pr/fix.js +64 -12
- package/dist/commands/worktree/create.js +17 -11
- package/dist/commands/worktree/open.js +3 -2
- package/dist/commands/worktree/switch.js +3 -11
- package/dist/lib/ai.d.ts +11 -0
- package/dist/lib/ai.js +71 -2
- package/dist/lib/cd-hint.d.ts +18 -0
- package/dist/lib/cd-hint.js +27 -0
- package/dist/lib/claude-config.d.ts +26 -0
- package/dist/lib/claude-config.js +121 -0
- package/dist/lib/config/TrackerPicker.d.ts +10 -0
- package/dist/lib/config/TrackerPicker.js +113 -0
- package/dist/lib/config/diagnostics.d.ts +37 -0
- package/dist/lib/config/diagnostics.js +190 -0
- package/dist/lib/config-store.d.ts +23 -0
- package/dist/lib/config-store.js +68 -0
- package/dist/lib/dashboard/DetailPanel.d.ts +7 -2
- package/dist/lib/dashboard/DetailPanel.js +54 -37
- package/dist/lib/dashboard/IssueList.d.ts +5 -1
- package/dist/lib/dashboard/IssueList.js +24 -5
- package/dist/lib/dashboard/MultilineTextArea.js +1 -1
- package/dist/lib/dashboard/Overlays.js +3 -4
- package/dist/lib/dashboard/ReviewDetailPanel.js +11 -2
- package/dist/lib/dashboard/ReviewList.js +6 -3
- package/dist/lib/dashboard/data.js +7 -34
- package/dist/lib/dashboard/external-editor.d.ts +1 -1
- package/dist/lib/dashboard/external-editor.js +3 -2
- package/dist/lib/dashboard/types.d.ts +19 -2
- package/dist/lib/dashboard/types.js +68 -2
- package/dist/lib/exec.d.ts +13 -0
- package/dist/lib/exec.js +11 -1
- package/dist/lib/fix-loop.d.ts +45 -0
- package/dist/lib/fix-loop.js +83 -0
- package/dist/lib/git.d.ts +3 -29
- package/dist/lib/git.js +6 -62
- package/dist/lib/github.d.ts +70 -0
- package/dist/lib/github.js +128 -0
- package/dist/lib/multiplexer/cmux.js +143 -17
- package/dist/lib/multiplexer/none.js +3 -3
- package/dist/lib/multiplexer/tmux.js +5 -29
- package/dist/lib/multiplexer/types.d.ts +34 -1
- package/dist/lib/open-url.d.ts +3 -2
- package/dist/lib/open-url.js +5 -4
- package/dist/lib/prompts.d.ts +35 -1
- package/dist/lib/prompts.js +7 -0
- package/dist/lib/setup/apply.d.ts +16 -0
- package/dist/lib/setup/apply.js +35 -0
- package/dist/lib/setup/gitignore.d.ts +24 -0
- package/dist/lib/setup/gitignore.js +95 -0
- package/dist/lib/setup/steps.d.ts +39 -0
- package/dist/lib/setup/steps.js +333 -0
- package/dist/lib/setup/tools.d.ts +23 -0
- package/dist/lib/setup/tools.js +47 -0
- package/dist/lib/squirrel-loader.d.ts +3 -1
- package/dist/lib/squirrel-loader.js +2 -2
- package/dist/lib/trackers/github/index.js +2 -2
- package/dist/lib/trackers/local/store.js +1 -1
- package/package.json +1 -2
- package/prompts/fix-context.njk +79 -0
- package/prompts/fix-loop.njk +18 -0
- package/prompts/pr.njk +1 -1
- package/dist/commands/doctor.d.ts +0 -2
- package/dist/commands/doctor.js +0 -618
- package/dist/commands/github/auth.d.ts +0 -2
- package/dist/commands/github/auth.js +0 -56
- package/dist/commands/github/index.d.ts +0 -1
- package/dist/commands/github/index.js +0 -1
- package/dist/commands/helpers/english-tutor/index.d.ts +0 -1
- package/dist/commands/helpers/english-tutor/index.js +0 -1
- package/dist/commands/helpers/english-tutor/install.d.ts +0 -8
- package/dist/commands/helpers/english-tutor/install.js +0 -24
- package/dist/commands/helpers/english-tutor/prompt.d.ts +0 -2
- package/dist/commands/helpers/english-tutor/prompt.js +0 -16
- package/dist/commands/helpers/english-tutor/session-start.d.ts +0 -2
- package/dist/commands/helpers/english-tutor/session-start.js +0 -34
- package/dist/commands/helpers/english-tutor/uninstall.d.ts +0 -2
- package/dist/commands/helpers/english-tutor/uninstall.js +0 -15
- package/dist/commands/helpers/session-signal/end.d.ts +0 -2
- package/dist/commands/helpers/session-signal/end.js +0 -13
- package/dist/commands/helpers/session-signal/index.d.ts +0 -1
- package/dist/commands/helpers/session-signal/index.js +0 -1
- package/dist/commands/helpers/session-signal/install.d.ts +0 -8
- package/dist/commands/helpers/session-signal/install.js +0 -24
- package/dist/commands/helpers/session-signal/notification.d.ts +0 -2
- package/dist/commands/helpers/session-signal/notification.js +0 -13
- package/dist/commands/helpers/session-signal/prompt.d.ts +0 -2
- package/dist/commands/helpers/session-signal/prompt.js +0 -13
- package/dist/commands/helpers/session-signal/stop.d.ts +0 -2
- package/dist/commands/helpers/session-signal/stop.js +0 -13
- package/dist/commands/helpers/shell-init.d.ts +0 -11
- package/dist/commands/helpers/shell-init.js +0 -122
- package/dist/commands/issue/setup.d.ts +0 -2
- package/dist/commands/issue/setup.js +0 -108
- package/dist/commands/issue/switch.d.ts +0 -12
- package/dist/commands/issue/switch.js +0 -38
- package/dist/commands/linear/switch.d.ts +0 -2
- package/dist/commands/linear/switch.js +0 -75
- package/dist/lib/english-tutor.d.ts +0 -13
- package/dist/lib/english-tutor.js +0 -125
- package/dist/lib/session-signal.d.ts +0 -16
- package/dist/lib/session-signal.js +0 -104
- package/prompts/english-tutor-prompt.njk +0 -15
- package/shell/init.bash.njk +0 -129
- package/shell/init.zsh.njk +0 -204
package/dist/commands/doctor.js
DELETED
|
@@ -1,618 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
import SquirrelLoader from "../lib/squirrel-loader.js";
|
|
5
|
-
import { exec, execSync } from "child_process";
|
|
6
|
-
import { promisify } from "util";
|
|
7
|
-
import { createRequire } from "module";
|
|
8
|
-
import * as fs from "fs";
|
|
9
|
-
import * as path from "path";
|
|
10
|
-
const require = createRequire(import.meta.url);
|
|
11
|
-
const { version } = require("../../package.json");
|
|
12
|
-
import { findMainRepoRoot, getSantreeDir, getInitScriptPath } from "../lib/git.js";
|
|
13
|
-
import { getIssueTracker } from "../lib/trackers/index.js";
|
|
14
|
-
import { getMultiplexer } from "../lib/multiplexer/index.js";
|
|
15
|
-
import { resolveClaudeBinary } from "../lib/ai.js";
|
|
16
|
-
import { CURRENT_VERSION, CLAUDE_CODE_PACKAGE, SANTREE_PACKAGE, getLatestVersionFor, isUpdateAvailable, detectPackageManager, getInstallCommandFor, } from "../lib/version.js";
|
|
17
|
-
const execAsync = promisify(exec);
|
|
18
|
-
export const description = "Check system requirements and integrations";
|
|
19
|
-
/**
|
|
20
|
-
* Executes a command asynchronously and returns the output, or null if it fails.
|
|
21
|
-
*/
|
|
22
|
-
async function tryExec(command) {
|
|
23
|
-
try {
|
|
24
|
-
const { stdout } = await execAsync(command);
|
|
25
|
-
return stdout.trim();
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Gets the path of a command using `which`.
|
|
33
|
-
*/
|
|
34
|
-
async function getPath(command) {
|
|
35
|
-
return tryExec(`which ${command}`);
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Checks if a tool is installed and gets its version.
|
|
39
|
-
*/
|
|
40
|
-
async function checkTool(name, description, required, versionCommand, hint) {
|
|
41
|
-
const path = await getPath(name);
|
|
42
|
-
if (!path) {
|
|
43
|
-
return {
|
|
44
|
-
name,
|
|
45
|
-
description,
|
|
46
|
-
required,
|
|
47
|
-
installed: false,
|
|
48
|
-
hint,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
const version = await tryExec(versionCommand);
|
|
52
|
-
return {
|
|
53
|
-
name,
|
|
54
|
-
description,
|
|
55
|
-
required,
|
|
56
|
-
installed: true,
|
|
57
|
-
version: version || "unknown",
|
|
58
|
-
path,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Reports the active multiplexer (tmux/cmux/none) and verifies the underlying
|
|
63
|
-
* binary is reachable. Detection is auto — each adapter's `isActive()` checks
|
|
64
|
-
* its own runtime env (`$TMUX`, `$CMUX_SURFACE_ID`).
|
|
65
|
-
*/
|
|
66
|
-
async function checkMultiplexer() {
|
|
67
|
-
const mux = getMultiplexer();
|
|
68
|
-
const description = `Multiplexer (active: ${mux.kind})`;
|
|
69
|
-
if (mux.kind === "none") {
|
|
70
|
-
return {
|
|
71
|
-
name: "multiplexer",
|
|
72
|
-
description,
|
|
73
|
-
required: false,
|
|
74
|
-
installed: false,
|
|
75
|
-
hint: "Run inside tmux or cmux to enable session window renaming. Install tmux: brew install tmux",
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
if (mux.kind === "tmux") {
|
|
79
|
-
const path = await getPath("tmux");
|
|
80
|
-
if (!path) {
|
|
81
|
-
return {
|
|
82
|
-
name: "tmux",
|
|
83
|
-
description,
|
|
84
|
-
required: false,
|
|
85
|
-
installed: false,
|
|
86
|
-
hint: "Install: brew install tmux",
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
const version = await tryExec("tmux -V");
|
|
90
|
-
return {
|
|
91
|
-
name: "tmux",
|
|
92
|
-
description,
|
|
93
|
-
required: false,
|
|
94
|
-
installed: true,
|
|
95
|
-
version: version || "unknown",
|
|
96
|
-
path,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
// cmux
|
|
100
|
-
const path = await getPath("cmux");
|
|
101
|
-
if (!path) {
|
|
102
|
-
return {
|
|
103
|
-
name: "cmux",
|
|
104
|
-
description,
|
|
105
|
-
required: false,
|
|
106
|
-
installed: false,
|
|
107
|
-
hint: "Install cmux.app from https://cmux.com (cmux is macOS-only).",
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
const version = await tryExec("cmux --version 2>/dev/null");
|
|
111
|
-
const ping = await tryExec("cmux ping 2>/dev/null");
|
|
112
|
-
// Note: cmux #1472 (programmatic workspaces with dead PTYs) is a real
|
|
113
|
-
// limitation but only surfaces when a specific dashboard flow tries to
|
|
114
|
-
// auto-execute a command in a freshly-created workspace. Showing it on
|
|
115
|
-
// every doctor run made cmux look broken when it isn't — the limitation
|
|
116
|
-
// is documented in CLAUDE.md and the README. We only flag a hint here
|
|
117
|
-
// when cmux is actually unreachable.
|
|
118
|
-
return {
|
|
119
|
-
name: "cmux",
|
|
120
|
-
description,
|
|
121
|
-
required: false,
|
|
122
|
-
installed: !!ping,
|
|
123
|
-
version: version || "unknown",
|
|
124
|
-
path,
|
|
125
|
-
hint: !ping ? "cmux app not reachable — open cmux.app." : undefined,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Checks the Claude CLI, preferring cmux's bundled binary when running inside
|
|
130
|
-
* cmux. The standard `checkTool` uses `which claude` which can't locate the
|
|
131
|
-
* cmux shim at /Applications/cmux.app/Contents/Resources/bin/claude — that
|
|
132
|
-
* binary isn't on PATH. See manaflow-ai/cmux#2048.
|
|
133
|
-
*/
|
|
134
|
-
async function checkClaude() {
|
|
135
|
-
const resolved = resolveClaudeBinary();
|
|
136
|
-
const usingBundled = resolved?.startsWith("/Applications/cmux.app/") ?? false;
|
|
137
|
-
const inCmux = getMultiplexer().kind === "cmux";
|
|
138
|
-
const description = usingBundled ? "Claude Code CLI (cmux-bundled)" : "Claude Code CLI";
|
|
139
|
-
if (!resolved) {
|
|
140
|
-
return {
|
|
141
|
-
name: "claude",
|
|
142
|
-
description,
|
|
143
|
-
required: true,
|
|
144
|
-
installed: false,
|
|
145
|
-
hint: inCmux
|
|
146
|
-
? "Open cmux.app to install its bundled Claude, or install standalone: npm install -g @anthropic-ai/claude-code"
|
|
147
|
-
: "Install: npm install -g @anthropic-ai/claude-code",
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
const version = await tryExec(`"${resolved}" --version 2>/dev/null | head -1`);
|
|
151
|
-
return {
|
|
152
|
-
name: "claude",
|
|
153
|
-
description,
|
|
154
|
-
required: true,
|
|
155
|
-
installed: true,
|
|
156
|
-
version: version || "unknown",
|
|
157
|
-
path: resolved,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Checks GitHub CLI auth status.
|
|
162
|
-
* Uses `gh api user` which works across all gh versions.
|
|
163
|
-
*/
|
|
164
|
-
async function checkGhAuth() {
|
|
165
|
-
const path = await getPath("gh");
|
|
166
|
-
if (!path) {
|
|
167
|
-
return {
|
|
168
|
-
name: "gh",
|
|
169
|
-
description: "GitHub CLI for PR operations",
|
|
170
|
-
required: true,
|
|
171
|
-
installed: false,
|
|
172
|
-
hint: "Install: brew install gh && gh auth login",
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
const version = await tryExec("gh --version | head -1");
|
|
176
|
-
const login = await tryExec("gh api user --jq .login 2>/dev/null");
|
|
177
|
-
if (!login) {
|
|
178
|
-
return {
|
|
179
|
-
name: "gh",
|
|
180
|
-
description: "GitHub CLI for PR operations",
|
|
181
|
-
required: true,
|
|
182
|
-
installed: true,
|
|
183
|
-
version: version || "unknown",
|
|
184
|
-
path,
|
|
185
|
-
hint: "Run: gh auth login",
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
return {
|
|
189
|
-
name: "gh",
|
|
190
|
-
description: "GitHub CLI for PR operations",
|
|
191
|
-
required: true,
|
|
192
|
-
installed: true,
|
|
193
|
-
version: version || "unknown",
|
|
194
|
-
path,
|
|
195
|
-
authStatus: `Authenticated as ${login}`,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Checks the active issue tracker's auth state. The tracker (Linear, GitHub)
|
|
200
|
-
* is resolved from the repo's `_tracker` config (or env / auto-detect).
|
|
201
|
-
* Doctor is the one place that legitimately names the active tracker — it's
|
|
202
|
-
* diagnostic context, not a generic UI string.
|
|
203
|
-
*/
|
|
204
|
-
async function checkTrackerAuth() {
|
|
205
|
-
const repoRoot = findMainRepoRoot();
|
|
206
|
-
const tracker = getIssueTracker(repoRoot);
|
|
207
|
-
const status = await tracker.getAuthStatus(repoRoot);
|
|
208
|
-
return {
|
|
209
|
-
displayName: tracker.displayName,
|
|
210
|
-
authenticated: status.authenticated,
|
|
211
|
-
accountLabel: status.accountLabel,
|
|
212
|
-
expiresAt: status.expiresAt,
|
|
213
|
-
repoLinked: status.repoLinked,
|
|
214
|
-
hint: status.hint,
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Checks if the shell integration is set up by looking for the
|
|
219
|
-
* SANTREE_SHELL_INTEGRATION environment variable exported by the shell scripts.
|
|
220
|
-
*/
|
|
221
|
-
function checkShellIntegration() {
|
|
222
|
-
const shell = process.env.SHELL || "";
|
|
223
|
-
const shellName = shell.includes("zsh") ? "zsh" : shell.includes("bash") ? "bash" : null;
|
|
224
|
-
const configured = process.env.SANTREE_SHELL_INTEGRATION === "1";
|
|
225
|
-
return { configured, shell: shellName };
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Checks if Claude Code Remote Control is enabled for all sessions.
|
|
229
|
-
* Remote Control lets you continue local sessions from any device.
|
|
230
|
-
*
|
|
231
|
-
* This reads from ~/.claude.json (the "global config" / application state file),
|
|
232
|
-
* which is separate from ~/.claude/settings.json (the declarative settings file).
|
|
233
|
-
* See: https://code.claude.com/docs/en/settings#settings-files
|
|
234
|
-
*/
|
|
235
|
-
function checkRemoteControl() {
|
|
236
|
-
const home = process.env.HOME || "";
|
|
237
|
-
const configPath = path.join(home, ".claude.json");
|
|
238
|
-
try {
|
|
239
|
-
if (fs.existsSync(configPath)) {
|
|
240
|
-
const content = fs.readFileSync(configPath, "utf-8");
|
|
241
|
-
const config = JSON.parse(content);
|
|
242
|
-
if (config.remoteControlAtStartup === true) {
|
|
243
|
-
return { enabled: true };
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
catch {
|
|
248
|
-
// JSON parse error or file read error
|
|
249
|
-
}
|
|
250
|
-
return {
|
|
251
|
-
enabled: false,
|
|
252
|
-
hint: 'Run /config in Claude Code and enable "Enable Remote Control for all sessions"',
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Checks statusline configuration:
|
|
257
|
-
* If ~/.claude/settings.json has statusLine pointing to santree
|
|
258
|
-
*/
|
|
259
|
-
async function checkStatusline() {
|
|
260
|
-
const home = process.env.HOME || "";
|
|
261
|
-
const claudeSettingsPath = path.join(home, ".claude", "settings.json");
|
|
262
|
-
let claudeSettingsConfigured = false;
|
|
263
|
-
let currentCommand;
|
|
264
|
-
try {
|
|
265
|
-
if (fs.existsSync(claudeSettingsPath)) {
|
|
266
|
-
const content = fs.readFileSync(claudeSettingsPath, "utf-8");
|
|
267
|
-
const settings = JSON.parse(content);
|
|
268
|
-
if (settings.statusLine?.command) {
|
|
269
|
-
currentCommand = String(settings.statusLine.command);
|
|
270
|
-
// Check if it points to santree statusline
|
|
271
|
-
claudeSettingsConfigured =
|
|
272
|
-
currentCommand.includes("santree statusline") ||
|
|
273
|
-
currentCommand.includes("santree helpers statusline");
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
catch {
|
|
278
|
-
// JSON parse error or file read error
|
|
279
|
-
}
|
|
280
|
-
let hint;
|
|
281
|
-
if (!claudeSettingsConfigured) {
|
|
282
|
-
hint =
|
|
283
|
-
'Add to ~/.claude/settings.json: "statusLine": { "type": "command", "command": "santree helpers statusline" }';
|
|
284
|
-
}
|
|
285
|
-
return {
|
|
286
|
-
claudeSettingsConfigured,
|
|
287
|
-
currentCommand,
|
|
288
|
-
hint,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* Checks if session-signal hooks are configured in ~/.claude/settings.json.
|
|
293
|
-
* Looks for hooks on Notification, Stop, UserPromptSubmit, and SessionEnd
|
|
294
|
-
* that run "santree helpers session-signal".
|
|
295
|
-
*/
|
|
296
|
-
function checkSessionSignalHooks() {
|
|
297
|
-
const home = process.env.HOME || "";
|
|
298
|
-
const claudeSettingsPath = path.join(home, ".claude", "settings.json");
|
|
299
|
-
const requiredEvents = ["Notification", "Stop", "UserPromptSubmit", "SessionEnd"];
|
|
300
|
-
const missingHooks = [];
|
|
301
|
-
try {
|
|
302
|
-
if (fs.existsSync(claudeSettingsPath)) {
|
|
303
|
-
const content = fs.readFileSync(claudeSettingsPath, "utf-8");
|
|
304
|
-
const settings = JSON.parse(content);
|
|
305
|
-
const hooks = settings.hooks || {};
|
|
306
|
-
for (const event of requiredEvents) {
|
|
307
|
-
const eventHooks = hooks[event];
|
|
308
|
-
if (!Array.isArray(eventHooks)) {
|
|
309
|
-
missingHooks.push(event);
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
// Check if any hook entry has a nested hook command containing session-signal
|
|
313
|
-
const found = eventHooks.some((entry) => {
|
|
314
|
-
const innerHooks = entry.hooks || [];
|
|
315
|
-
return innerHooks.some((h) => typeof h.command === "string" && h.command.includes("session-signal"));
|
|
316
|
-
});
|
|
317
|
-
if (!found)
|
|
318
|
-
missingHooks.push(event);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
missingHooks.push(...requiredEvents);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
catch {
|
|
326
|
-
missingHooks.push(...requiredEvents);
|
|
327
|
-
}
|
|
328
|
-
if (missingHooks.length === 0) {
|
|
329
|
-
return { configured: true, missingHooks: [] };
|
|
330
|
-
}
|
|
331
|
-
return {
|
|
332
|
-
configured: false,
|
|
333
|
-
missingHooks,
|
|
334
|
-
hint: `Missing: ${missingHooks.join(", ")}. Run: santree helpers session-signal install`,
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* Checks if english-tutor hooks are configured. Verifies the UserPromptSubmit
|
|
339
|
-
* and SessionStart hooks plus the scoped Edit permission for the practice log.
|
|
340
|
-
*/
|
|
341
|
-
function checkEnglishTutorHooks() {
|
|
342
|
-
const home = process.env.HOME || "";
|
|
343
|
-
const claudeSettingsPath = path.join(home, ".claude", "settings.json");
|
|
344
|
-
const requiredEvents = ["UserPromptSubmit", "SessionStart"];
|
|
345
|
-
const missingHooks = [];
|
|
346
|
-
let missingPermission = true;
|
|
347
|
-
const configDir = process.env.XDG_CONFIG_HOME || path.join(home, ".config");
|
|
348
|
-
const expectedPermission = `Edit(${path.join(configDir, "santree", "english-practice-log.md")})`;
|
|
349
|
-
try {
|
|
350
|
-
if (fs.existsSync(claudeSettingsPath)) {
|
|
351
|
-
const settings = JSON.parse(fs.readFileSync(claudeSettingsPath, "utf-8"));
|
|
352
|
-
const hooks = settings.hooks || {};
|
|
353
|
-
for (const event of requiredEvents) {
|
|
354
|
-
const eventHooks = hooks[event];
|
|
355
|
-
if (!Array.isArray(eventHooks)) {
|
|
356
|
-
missingHooks.push(event);
|
|
357
|
-
continue;
|
|
358
|
-
}
|
|
359
|
-
const found = eventHooks.some((entry) => {
|
|
360
|
-
const innerHooks = entry.hooks || [];
|
|
361
|
-
return innerHooks.some((h) => typeof h.command === "string" && h.command.includes("english-tutor"));
|
|
362
|
-
});
|
|
363
|
-
if (!found)
|
|
364
|
-
missingHooks.push(event);
|
|
365
|
-
}
|
|
366
|
-
const allow = settings.permissions?.allow;
|
|
367
|
-
if (Array.isArray(allow) && allow.includes(expectedPermission)) {
|
|
368
|
-
missingPermission = false;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
missingHooks.push(...requiredEvents);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
catch {
|
|
376
|
-
missingHooks.push(...requiredEvents);
|
|
377
|
-
}
|
|
378
|
-
if (missingHooks.length === 0 && !missingPermission) {
|
|
379
|
-
return { configured: true, missingHooks: [], missingPermission: false };
|
|
380
|
-
}
|
|
381
|
-
return {
|
|
382
|
-
configured: false,
|
|
383
|
-
missingHooks,
|
|
384
|
-
missingPermission,
|
|
385
|
-
hint: "Run: santree helpers english-tutor install",
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
/**
|
|
389
|
-
* Checks if a path is gitignored (via .gitignore or .git/info/exclude).
|
|
390
|
-
*/
|
|
391
|
-
function isGitIgnored(filePath, cwd) {
|
|
392
|
-
try {
|
|
393
|
-
execSync(`git check-ignore -q "${filePath}"`, { cwd, stdio: "ignore" });
|
|
394
|
-
return true; // exit 0 = ignored
|
|
395
|
-
}
|
|
396
|
-
catch {
|
|
397
|
-
return false; // exit 1 = not ignored
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* Checks if the current directory is a git repo and if .santree/init.sh exists and is executable.
|
|
402
|
-
*/
|
|
403
|
-
function checkSantreeSetup() {
|
|
404
|
-
const mainRepoRoot = findMainRepoRoot();
|
|
405
|
-
if (!mainRepoRoot) {
|
|
406
|
-
return {
|
|
407
|
-
isGitRepo: false,
|
|
408
|
-
santreeFolderExists: false,
|
|
409
|
-
initShExists: false,
|
|
410
|
-
initShExecutable: false,
|
|
411
|
-
worktreesIgnored: false,
|
|
412
|
-
metadataIgnored: false,
|
|
413
|
-
hints: ["Not in a git repository"],
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
const santreeDir = getSantreeDir(mainRepoRoot);
|
|
417
|
-
const initShPath = getInitScriptPath(mainRepoRoot);
|
|
418
|
-
const santreeFolderExists = fs.existsSync(santreeDir);
|
|
419
|
-
const initShExists = fs.existsSync(initShPath);
|
|
420
|
-
let initShExecutable = false;
|
|
421
|
-
if (initShExists) {
|
|
422
|
-
try {
|
|
423
|
-
fs.accessSync(initShPath, fs.constants.X_OK);
|
|
424
|
-
initShExecutable = true;
|
|
425
|
-
}
|
|
426
|
-
catch {
|
|
427
|
-
initShExecutable = false;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
// Check gitignore status (use relative paths for git check-ignore)
|
|
431
|
-
const worktreesIgnored = isGitIgnored(".santree/worktrees", mainRepoRoot);
|
|
432
|
-
const metadataIgnored = isGitIgnored(".santree/metadata.json", mainRepoRoot);
|
|
433
|
-
const hints = [];
|
|
434
|
-
if (!santreeFolderExists) {
|
|
435
|
-
hints.push(`Create .santree folder: mkdir ${santreeDir}`);
|
|
436
|
-
}
|
|
437
|
-
else if (!initShExists) {
|
|
438
|
-
hints.push(`Create init.sh: touch ${initShPath} && chmod +x ${initShPath}`);
|
|
439
|
-
}
|
|
440
|
-
else if (!initShExecutable) {
|
|
441
|
-
hints.push(`Make init.sh executable: chmod +x ${initShPath}`);
|
|
442
|
-
}
|
|
443
|
-
if (!worktreesIgnored) {
|
|
444
|
-
hints.push("Add .santree/worktrees to .gitignore");
|
|
445
|
-
}
|
|
446
|
-
if (!metadataIgnored) {
|
|
447
|
-
hints.push("Add .santree/metadata.json to .gitignore");
|
|
448
|
-
}
|
|
449
|
-
return {
|
|
450
|
-
isGitRepo: true,
|
|
451
|
-
mainRepoRoot,
|
|
452
|
-
santreeFolderExists,
|
|
453
|
-
initShExists,
|
|
454
|
-
initShExecutable,
|
|
455
|
-
worktreesIgnored,
|
|
456
|
-
metadataIgnored,
|
|
457
|
-
hints,
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
function StatusIcon({ ok, required }) {
|
|
461
|
-
if (ok) {
|
|
462
|
-
return _jsx(Text, { color: "green", children: "\u2713" });
|
|
463
|
-
}
|
|
464
|
-
return required ? _jsx(Text, { color: "red", children: "\u2717" }) : _jsx(Text, { color: "yellow", children: "\u25CB" });
|
|
465
|
-
}
|
|
466
|
-
function ToolRow({ tool }) {
|
|
467
|
-
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.latestVersion && tool.version && (_jsxs(Text, { color: isUpdateAvailable(tool.version, tool.latestVersion) ? "yellow" : undefined, dimColor: !isUpdateAvailable(tool.version, tool.latestVersion), children: ["Latest: ", tool.latestVersion, isUpdateAvailable(tool.version, tool.latestVersion) ? " ⬆ update available" : ""] })), tool.path && _jsxs(Text, { dimColor: true, children: ["Path: ", tool.path] }), tool.authStatus && _jsxs(Text, { dimColor: true, children: ["Auth: ", tool.authStatus] }), tool.updateHint && _jsxs(Text, { color: "yellow", children: ["\u21B3 ", tool.updateHint] }), tool.hint && _jsxs(Text, { color: "yellow", children: ["\u21B3 ", tool.hint] })] })) : (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: "yellow", children: ["\u21B3 ", tool.hint] }) }))] }));
|
|
468
|
-
}
|
|
469
|
-
function TrackerRow({ tracker }) {
|
|
470
|
-
const isOk = tracker.authenticated && !tracker.hint;
|
|
471
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(StatusIcon, { ok: isOk, required: true }), _jsx(Text, { children: " " }), _jsxs(Text, { bold: true, children: [tracker.displayName, " API"] }), _jsx(Text, { dimColor: true, children: " - Issue tracker integration" })] }), tracker.authenticated ? (_jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [tracker.accountLabel && _jsxs(Text, { dimColor: true, children: ["Account: ", tracker.accountLabel] }), tracker.repoLinked !== undefined && (_jsxs(Text, { dimColor: true, children: ["Repo linked: ", tracker.repoLinked ? "yes" : "no"] })), tracker.hint && _jsxs(Text, { color: "yellow", children: ["\u21B3 ", tracker.hint] })] })) : (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: "yellow", children: ["\u21B3 ", tracker.hint] }) }))] }));
|
|
472
|
-
}
|
|
473
|
-
function ShellRow({ configured, shell }) {
|
|
474
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(StatusIcon, { ok: configured, required: true }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Shell Integration" }), _jsx(Text, { dimColor: true, children: " - Enables directory switching" })] }), configured ? (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { dimColor: true, children: ["Shell: ", shell] }) })) : (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: "yellow", children: ["\u21B3 Add to .", shell, "rc: eval \"$(santree helpers shell-init ", shell, ")\""] }) }))] }));
|
|
475
|
-
}
|
|
476
|
-
function StatuslineRow({ status }) {
|
|
477
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(StatusIcon, { ok: status.claudeSettingsConfigured, required: false }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Claude Statusline" }), _jsx(Text, { dimColor: true, children: " - Custom statusline in Claude Code" }), _jsx(Text, { dimColor: true, children: " (optional)" })] }), _jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [status.currentCommand ? (_jsxs(Text, { dimColor: true, children: ["Command: ", status.currentCommand] })) : (_jsx(Text, { dimColor: true, children: "Command: not configured" })), status.hint && _jsxs(Text, { color: "yellow", children: ["\u21B3 ", status.hint] })] })] }));
|
|
478
|
-
}
|
|
479
|
-
function RemoteControlRow({ status }) {
|
|
480
|
-
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: " - Continue 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] })] })] }));
|
|
481
|
-
}
|
|
482
|
-
function SessionSignalRow({ status }) {
|
|
483
|
-
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: " - Surface session state in dashboard/tmux" }), _jsx(Text, { dimColor: true, children: " (optional)" })] }), _jsx(Box, { marginLeft: 2, flexDirection: "column", children: status.configured ? (_jsx(Text, { dimColor: true, children: "All hooks configured" })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { dimColor: true, children: ["Missing: ", status.missingHooks.join(", ")] }), status.hint && _jsxs(Text, { color: "yellow", children: ["\u21B3 ", status.hint] })] })) })] }));
|
|
484
|
-
}
|
|
485
|
-
function EnglishTutorRow({ status }) {
|
|
486
|
-
const missingParts = [];
|
|
487
|
-
if (status.missingHooks.length > 0)
|
|
488
|
-
missingParts.push(`hooks: ${status.missingHooks.join(", ")}`);
|
|
489
|
-
if (status.missingPermission)
|
|
490
|
-
missingParts.push("log Edit permission");
|
|
491
|
-
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: "English Tutor Hooks" }), _jsx(Text, { dimColor: true, children: " - Inline grammar corrections" }), _jsx(Text, { dimColor: true, children: " (optional)" })] }), _jsx(Box, { marginLeft: 2, flexDirection: "column", children: status.configured ? (_jsx(Text, { dimColor: true, children: "Hooks and log permission configured" })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { dimColor: true, children: ["Missing: ", missingParts.join("; ") || "—"] }), status.hint && _jsxs(Text, { color: "yellow", children: ["\u21B3 ", status.hint] })] })) })] }));
|
|
492
|
-
}
|
|
493
|
-
function SantreeSetupRow({ status }) {
|
|
494
|
-
const isOk = status.santreeFolderExists &&
|
|
495
|
-
status.initShExists &&
|
|
496
|
-
status.initShExecutable &&
|
|
497
|
-
status.worktreesIgnored &&
|
|
498
|
-
status.metadataIgnored;
|
|
499
|
-
if (!status.isGitRepo) {
|
|
500
|
-
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: "Repository Setup" }), _jsx(Text, { dimColor: true, children: " - .santree configuration" }), _jsx(Text, { dimColor: true, children: " (optional)" })] }), _jsx(Box, { marginLeft: 2, children: _jsx(Text, { dimColor: true, children: "Not in a git repository" }) })] }));
|
|
501
|
-
}
|
|
502
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(StatusIcon, { ok: isOk, required: false }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Repository Setup" }), _jsx(Text, { dimColor: true, children: " - .santree 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: [".santree folder: ", status.santreeFolderExists ? "exists" : "missing"] }), status.santreeFolderExists && (_jsxs(Text, { dimColor: true, children: ["init.sh:", " ", status.initShExists
|
|
503
|
-
? status.initShExecutable
|
|
504
|
-
? "executable"
|
|
505
|
-
: "not executable"
|
|
506
|
-
: "missing"] })), _jsxs(Text, { dimColor: true, children: [".santree/worktrees ignored: ", status.worktreesIgnored ? "yes" : "no"] }), _jsxs(Text, { dimColor: true, children: [".santree/metadata.json ignored: ", status.metadataIgnored ? "yes" : "no"] }), status.hints.map((hint, i) => (_jsxs(Text, { color: "yellow", children: ["\u21B3 ", hint] }, i)))] })] }));
|
|
507
|
-
}
|
|
508
|
-
export default function Doctor() {
|
|
509
|
-
const [tools, setTools] = useState([]);
|
|
510
|
-
const [tracker, setTracker] = useState(null);
|
|
511
|
-
const [shellStatus, setShellStatus] = useState(null);
|
|
512
|
-
const [remoteControl, setRemoteControl] = useState(null);
|
|
513
|
-
const [statusline, setStatusline] = useState(null);
|
|
514
|
-
const [sessionSignal, setSessionSignal] = useState(null);
|
|
515
|
-
const [englishTutor, setEnglishTutor] = useState(null);
|
|
516
|
-
const [santreeSetup, setSantreeSetup] = useState(null);
|
|
517
|
-
const [loading, setLoading] = useState(true);
|
|
518
|
-
useEffect(() => {
|
|
519
|
-
async function runChecks() {
|
|
520
|
-
const pm = detectPackageManager();
|
|
521
|
-
const [results, latestSantree, latestClaude] = await Promise.all([
|
|
522
|
-
Promise.all([
|
|
523
|
-
checkTool("git", "Version control", true, "git --version | head -1", "Install: brew install git"),
|
|
524
|
-
checkGhAuth(),
|
|
525
|
-
checkMultiplexer(),
|
|
526
|
-
checkClaude(),
|
|
527
|
-
]),
|
|
528
|
-
getLatestVersionFor(SANTREE_PACKAGE),
|
|
529
|
-
getLatestVersionFor(CLAUDE_CODE_PACKAGE),
|
|
530
|
-
]);
|
|
531
|
-
// Synthetic row for santree itself — surfaces update status.
|
|
532
|
-
const santreeRow = {
|
|
533
|
-
name: "santree",
|
|
534
|
-
description: "Santree CLI (this app)",
|
|
535
|
-
required: true,
|
|
536
|
-
installed: true,
|
|
537
|
-
version: CURRENT_VERSION,
|
|
538
|
-
latestVersion: latestSantree ?? undefined,
|
|
539
|
-
updateHint: latestSantree && isUpdateAvailable(CURRENT_VERSION, latestSantree)
|
|
540
|
-
? "Run: santree update"
|
|
541
|
-
: undefined,
|
|
542
|
-
};
|
|
543
|
-
results.unshift(santreeRow);
|
|
544
|
-
// Augment the claude row with latest-version info from npm registry.
|
|
545
|
-
// When the resolved binary is the cmux-bundled one, npm install can't
|
|
546
|
-
// update it — the bundled binary is shipped inside cmux.app. Show a
|
|
547
|
-
// cmux-aware hint instead of the generic npm command.
|
|
548
|
-
const claudeRow = results.find((r) => r.name === "claude");
|
|
549
|
-
if (claudeRow && claudeRow.installed && latestClaude) {
|
|
550
|
-
claudeRow.latestVersion = latestClaude;
|
|
551
|
-
if (claudeRow.version && isUpdateAvailable(claudeRow.version, latestClaude)) {
|
|
552
|
-
const isCmuxBundled = !!claudeRow.path?.includes("/cmux.app/");
|
|
553
|
-
if (isCmuxBundled) {
|
|
554
|
-
claudeRow.updateHint = "Bundled with cmux — update cmux.app to get the latest Claude.";
|
|
555
|
-
}
|
|
556
|
-
else {
|
|
557
|
-
const cmd = getInstallCommandFor(pm, `${CLAUDE_CODE_PACKAGE}@latest`);
|
|
558
|
-
claudeRow.updateHint = `Run: ${cmd.display}`;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
// Optional: a syntax-highlighting diff pager — used by `st worktree diff`
|
|
563
|
-
// and the dashboard `v` overlay when SANTREE_DIFF_TOOL is set. Any
|
|
564
|
-
// diff pager works (delta, diff-so-fancy, …); without one set, the
|
|
565
|
-
// dashboard renders inline with santree's own colorizer and the CLI
|
|
566
|
-
// falls back to git's default pager. Delta is the most popular
|
|
567
|
-
// choice so we check for it as a convenience, but it is never a
|
|
568
|
-
// hard dependency.
|
|
569
|
-
const deltaCheck = await checkTool("delta", "Recommended diff pager — any diff pager works", false, "delta --version | head -1", "Optional — santree's built-in colorizer handles the dashboard overlay; the CLI falls back to git's default pager. Set SANTREE_DIFF_TOOL to override. To install delta: brew install git-delta");
|
|
570
|
-
results.push(deltaCheck);
|
|
571
|
-
// Optional: a `.code-workspace`-aware editor (VSCode or Cursor).
|
|
572
|
-
// Santree itself works with any editor via $SANTREE_EDITOR — this
|
|
573
|
-
// check exists only because the dashboard's `E workspace` shortcut
|
|
574
|
-
// needs an editor that understands `.code-workspace` files. Missing
|
|
575
|
-
// here just means the shortcut is hidden; everything else still works.
|
|
576
|
-
const workspaceEditorDesc = "Workspace editor (`E workspace` shortcut)";
|
|
577
|
-
const [codeCheck, cursorCheck] = await Promise.all([
|
|
578
|
-
checkTool("code", workspaceEditorDesc, false, "code --version | head -1", ""),
|
|
579
|
-
checkTool("cursor", workspaceEditorDesc, false, "cursor --version | head -1", ""),
|
|
580
|
-
]);
|
|
581
|
-
if (codeCheck.installed) {
|
|
582
|
-
results.push(codeCheck);
|
|
583
|
-
}
|
|
584
|
-
else if (cursorCheck.installed) {
|
|
585
|
-
results.push(cursorCheck);
|
|
586
|
-
}
|
|
587
|
-
else {
|
|
588
|
-
results.push({
|
|
589
|
-
name: "code/cursor",
|
|
590
|
-
description: workspaceEditorDesc,
|
|
591
|
-
required: false,
|
|
592
|
-
installed: false,
|
|
593
|
-
hint: "Optional — santree works with any $SANTREE_EDITOR. Only needed for the dashboard's `.code-workspace` shortcut.",
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
const trackerResult = await checkTrackerAuth();
|
|
597
|
-
const statuslineResult = await checkStatusline();
|
|
598
|
-
setTools(results);
|
|
599
|
-
setTracker(trackerResult);
|
|
600
|
-
setShellStatus(checkShellIntegration());
|
|
601
|
-
setRemoteControl(checkRemoteControl());
|
|
602
|
-
setStatusline(statuslineResult);
|
|
603
|
-
setSessionSignal(checkSessionSignalHooks());
|
|
604
|
-
setEnglishTutor(checkEnglishTutorHooks());
|
|
605
|
-
setSantreeSetup(checkSantreeSetup());
|
|
606
|
-
setLoading(false);
|
|
607
|
-
}
|
|
608
|
-
runChecks();
|
|
609
|
-
}, []);
|
|
610
|
-
if (loading) {
|
|
611
|
-
return (_jsx(Box, { paddingY: 1, children: _jsx(SquirrelLoader, { text: "Checking system requirements..." }) }));
|
|
612
|
-
}
|
|
613
|
-
const requiredMissing = tools.filter((t) => t.required && (!t.installed || t.hint));
|
|
614
|
-
const optionalMissing = tools.filter((t) => !t.required && !t.installed);
|
|
615
|
-
const trackerOk = tracker?.authenticated && !tracker?.hint;
|
|
616
|
-
const allRequired = requiredMissing.length === 0 && trackerOk && shellStatus?.configured;
|
|
617
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Santree 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((tool) => (_jsx(ToolRow, { tool: tool }, tool.name))), _jsx(Box, { marginBottom: 1, marginTop: 1, flexDirection: "column", children: _jsx(Text, { bold: true, underline: true, children: "Integrations" }) }), tracker && _jsx(TrackerRow, { tracker: tracker }), shellStatus && _jsx(ShellRow, { configured: shellStatus.configured, shell: shellStatus.shell }), santreeSetup && _jsx(SantreeSetupRow, { status: santreeSetup }), _jsx(Box, { marginBottom: 1, marginTop: 1, flexDirection: "column", children: _jsx(Text, { bold: true, underline: true, children: "Claude Code" }) }), remoteControl && _jsx(RemoteControlRow, { status: remoteControl }), statusline && _jsx(StatuslineRow, { status: statusline }), sessionSignal && _jsx(SessionSignalRow, { status: sessionSignal }), englishTutor && _jsx(EnglishTutorRow, { status: englishTutor }), _jsx(Box, { marginTop: 1, borderStyle: "single", borderColor: allRequired ? "green" : "yellow", paddingX: 2, children: allRequired ? (_jsx(Text, { color: "green", children: "All requirements satisfied! Santree is ready to use." })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "yellow", children: [requiredMissing.length + (trackerOk ? 0 : 1) + (shellStatus?.configured ? 0 : 1), " ", "required item(s) need attention"] }), optionalMissing.length > 0 && (_jsxs(Text, { dimColor: true, children: [optionalMissing.length, " optional item(s) not installed"] }))] })) })] }));
|
|
618
|
-
}
|