santree 0.7.3 → 0.7.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/dist/commands/dashboard.js +76 -25
- package/dist/commands/doctor.js +12 -85
- package/dist/commands/setup.d.ts +11 -0
- package/dist/commands/setup.js +194 -0
- package/dist/commands/worktree/create.js +11 -10
- package/dist/commands/worktree/switch.js +3 -11
- package/dist/lib/cd-hint.d.ts +18 -0
- package/dist/lib/cd-hint.js +27 -0
- package/dist/lib/claude-config.d.ts +12 -0
- package/dist/lib/claude-config.js +86 -0
- package/dist/lib/dashboard/DetailPanel.d.ts +5 -1
- package/dist/lib/dashboard/DetailPanel.js +18 -1
- package/dist/lib/dashboard/Overlays.js +5 -0
- package/dist/lib/dashboard/types.d.ts +6 -0
- package/dist/lib/dashboard/types.js +37 -2
- package/dist/lib/setup/apply.d.ts +16 -0
- package/dist/lib/setup/apply.js +35 -0
- package/dist/lib/setup/gitignore.d.ts +17 -0
- package/dist/lib/setup/gitignore.js +57 -0
- package/dist/lib/setup/shell-config.d.ts +38 -0
- package/dist/lib/setup/shell-config.js +131 -0
- package/dist/lib/setup/steps.d.ts +35 -0
- package/dist/lib/setup/steps.js +328 -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/triage-config.d.ts +21 -0
- package/dist/lib/triage-config.js +32 -0
- package/package.json +1 -2
- package/dist/commands/helpers/shell-init.d.ts +0 -11
- package/dist/commands/helpers/shell-init.js +0 -122
- package/shell/init.bash.njk +0 -129
- package/shell/init.zsh.njk +0 -204
|
@@ -10,7 +10,9 @@ const require = createRequire(import.meta.url);
|
|
|
10
10
|
const { version } = require("../../package.json");
|
|
11
11
|
import { findMainRepoRoot, createWorktree, getDefaultBranch, getBaseBranch, hasInitScript, getInitScriptPath, removeWorktree, getDiffTool, getWorktreeStatus, stageFile, unstageFile, stageAll, unstageAll, discardFile, } from "../lib/git.js";
|
|
12
12
|
import { run, spawnAsync } from "../lib/exec.js";
|
|
13
|
-
import {
|
|
13
|
+
import { printCdHint } from "../lib/cd-hint.js";
|
|
14
|
+
import { resolveAgentBinary, resolveClaudeBinary, fillCommitMessage, askTicketQuestion, } from "../lib/ai.js";
|
|
15
|
+
import { readTriageInvestigateConfig, isTriageInvestigateConfigured, buildInvestigatePrompt, buildInvestigateCommand, } from "../lib/triage-config.js";
|
|
14
16
|
import { getInstalledClaudeVersion } from "../lib/version.js";
|
|
15
17
|
import { extractTicketId, getStagedDiffContent } from "../lib/git.js";
|
|
16
18
|
import { getMultiplexer } from "../lib/multiplexer/index.js";
|
|
@@ -325,6 +327,11 @@ export default function Dashboard() {
|
|
|
325
327
|
// Triage tab appears at all. Recomputed on every data refresh — feature
|
|
326
328
|
// detection via `tracker.supportsTriage`, never a kind check.
|
|
327
329
|
const [supportsTriage, setSupportsTriage] = useState(false);
|
|
330
|
+
// Whether `.santree/metadata.json` configures an "investigate triage ticket"
|
|
331
|
+
// skill/prompt (`_triage.skill_name` / `_triage.prompt`). Drives whether the
|
|
332
|
+
// Triage `[i]` action renders enabled (cyan) or greyed. Read fresh on every
|
|
333
|
+
// data refresh so manual edits to metadata.json show up within a cycle.
|
|
334
|
+
const [triageInvestigateConfigured, setTriageInvestigateConfigured] = useState(false);
|
|
328
335
|
const refreshTimerRef = useRef(null);
|
|
329
336
|
const repoRootRef = useRef(null);
|
|
330
337
|
const stateRef = useRef(state);
|
|
@@ -440,6 +447,7 @@ export default function Dashboard() {
|
|
|
440
447
|
setHasWorkspaceFile(hasWs);
|
|
441
448
|
const tracker = getIssueTracker(repoRoot);
|
|
442
449
|
setSupportsTriage(tracker.supportsTriage === true);
|
|
450
|
+
setTriageInvestigateConfigured(isTriageInvestigateConfigured(readTriageInvestigateConfig(repoRoot)));
|
|
443
451
|
dispatch({ type: "SET_DATA", ...data });
|
|
444
452
|
dispatch({ type: "SET_REVIEWS_DATA", flatReviews: reviewData.flatReviews });
|
|
445
453
|
// Triage on-call rotations (Linear). Best-effort, non-blocking — never
|
|
@@ -1042,7 +1050,9 @@ export default function Dashboard() {
|
|
|
1042
1050
|
? `cd ${shellEscape(sessionCwd)} && ${bin} --resume ${sessionId}`
|
|
1043
1051
|
: null;
|
|
1044
1052
|
const contextArg = contextFile ? ` --context-file "${contextFile}"` : "";
|
|
1045
|
-
const workCmd = mode === "plan"
|
|
1053
|
+
const workCmd = mode === "plan"
|
|
1054
|
+
? `santree worktree work --plan${contextArg}`
|
|
1055
|
+
: `santree worktree work${contextArg}`;
|
|
1046
1056
|
const cmd = resumeCmd ?? workCmd;
|
|
1047
1057
|
const mux = getMultiplexer();
|
|
1048
1058
|
const selected = await mux.selectWindow(windowName);
|
|
@@ -1084,7 +1094,7 @@ export default function Dashboard() {
|
|
|
1084
1094
|
});
|
|
1085
1095
|
}
|
|
1086
1096
|
}
|
|
1087
|
-
// Delayed refresh to pick up session ID created by `
|
|
1097
|
+
// Delayed refresh to pick up session ID created by `santree worktree work`
|
|
1088
1098
|
setTimeout(() => refresh(), 3000);
|
|
1089
1099
|
}, [refresh]);
|
|
1090
1100
|
const launchAfterCreation = useCallback(async (mode, worktreePath, ticketId, contextFile) => {
|
|
@@ -1093,8 +1103,8 @@ export default function Dashboard() {
|
|
|
1093
1103
|
const windowName = ticketId;
|
|
1094
1104
|
const contextArg = contextFile ? ` --context-file "${contextFile}"` : "";
|
|
1095
1105
|
const workCmd = mode === "plan"
|
|
1096
|
-
? `
|
|
1097
|
-
: `
|
|
1106
|
+
? `santree worktree work --plan${contextArg}`
|
|
1107
|
+
: `santree worktree work${contextArg}`;
|
|
1098
1108
|
const created = await mux.createWindow({
|
|
1099
1109
|
name: windowName,
|
|
1100
1110
|
cwd: worktreePath,
|
|
@@ -1116,10 +1126,10 @@ export default function Dashboard() {
|
|
|
1116
1126
|
}
|
|
1117
1127
|
else {
|
|
1118
1128
|
leaveAltScreen();
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1129
|
+
printCdHint({
|
|
1130
|
+
path: worktreePath,
|
|
1131
|
+
work: { mode, contextFile: contextFile || undefined },
|
|
1132
|
+
});
|
|
1123
1133
|
exit();
|
|
1124
1134
|
}
|
|
1125
1135
|
}, [exit, refresh]);
|
|
@@ -1270,10 +1280,10 @@ export default function Dashboard() {
|
|
|
1270
1280
|
}
|
|
1271
1281
|
else {
|
|
1272
1282
|
leaveAltScreen();
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1283
|
+
printCdHint({
|
|
1284
|
+
path: di.worktree.path,
|
|
1285
|
+
work: { mode, contextFile: contextFile || undefined },
|
|
1286
|
+
});
|
|
1277
1287
|
exit();
|
|
1278
1288
|
}
|
|
1279
1289
|
}
|
|
@@ -2565,7 +2575,7 @@ export default function Dashboard() {
|
|
|
2565
2575
|
const created = await mux.createWindow({
|
|
2566
2576
|
name: windowName,
|
|
2567
2577
|
cwd,
|
|
2568
|
-
command: "
|
|
2578
|
+
command: "santree pr review",
|
|
2569
2579
|
});
|
|
2570
2580
|
dispatch({
|
|
2571
2581
|
type: "SET_ACTION_MESSAGE",
|
|
@@ -2577,7 +2587,7 @@ export default function Dashboard() {
|
|
|
2577
2587
|
}
|
|
2578
2588
|
else {
|
|
2579
2589
|
leaveAltScreen();
|
|
2580
|
-
|
|
2590
|
+
printCdHint({ path: ri.worktree.path });
|
|
2581
2591
|
exit();
|
|
2582
2592
|
}
|
|
2583
2593
|
return;
|
|
@@ -2658,6 +2668,43 @@ export default function Dashboard() {
|
|
|
2658
2668
|
dispatch({ type: "TRIAGE_ASK_OPEN", ticketId: di.issue.identifier });
|
|
2659
2669
|
return;
|
|
2660
2670
|
}
|
|
2671
|
+
// Investigate — hand the ticket to a user-configured skill/prompt in a
|
|
2672
|
+
// new multiplexer window. Config lives in `.santree/metadata.json`
|
|
2673
|
+
// (`_triage.skill_name` or `_triage.prompt`); read fresh so manual
|
|
2674
|
+
// edits take effect without waiting for a refresh.
|
|
2675
|
+
if (input === "i") {
|
|
2676
|
+
const root = repoRootRef.current;
|
|
2677
|
+
if (!root)
|
|
2678
|
+
return;
|
|
2679
|
+
const prompt = buildInvestigatePrompt(readTriageInvestigateConfig(root), di.issue.identifier);
|
|
2680
|
+
if (!prompt) {
|
|
2681
|
+
dispatch({
|
|
2682
|
+
type: "SET_ACTION_MESSAGE",
|
|
2683
|
+
message: "Set _triage.skill_name (or _triage.prompt) in .santree/metadata.json to enable investigate",
|
|
2684
|
+
});
|
|
2685
|
+
return;
|
|
2686
|
+
}
|
|
2687
|
+
const mux = getMultiplexer();
|
|
2688
|
+
if (!mux.isActive()) {
|
|
2689
|
+
dispatch({
|
|
2690
|
+
type: "SET_ACTION_MESSAGE",
|
|
2691
|
+
message: "Investigate needs a multiplexer (tmux or cmux)",
|
|
2692
|
+
});
|
|
2693
|
+
return;
|
|
2694
|
+
}
|
|
2695
|
+
const command = buildInvestigateCommand(resolveClaudeBinary() ?? "claude", prompt);
|
|
2696
|
+
const windowName = `investigate-${di.issue.identifier}`;
|
|
2697
|
+
void (async () => {
|
|
2698
|
+
const created = await mux.createWindow({ name: windowName, cwd: root, command });
|
|
2699
|
+
dispatch({
|
|
2700
|
+
type: "SET_ACTION_MESSAGE",
|
|
2701
|
+
message: created.ok
|
|
2702
|
+
? "Launched investigation in new window"
|
|
2703
|
+
: `Failed to launch investigation${created.message ? `: ${created.message}` : ""}`,
|
|
2704
|
+
});
|
|
2705
|
+
})();
|
|
2706
|
+
return;
|
|
2707
|
+
}
|
|
2661
2708
|
if (input === "o") {
|
|
2662
2709
|
if (!di.issue.url) {
|
|
2663
2710
|
dispatch({ type: "SET_ACTION_MESSAGE", message: "No issue URL available" });
|
|
@@ -2805,7 +2852,7 @@ export default function Dashboard() {
|
|
|
2805
2852
|
const selected = await mux.selectWindow(windowName);
|
|
2806
2853
|
if (selected.ok)
|
|
2807
2854
|
return;
|
|
2808
|
-
const cmd = resumeCmd ?? "
|
|
2855
|
+
const cmd = resumeCmd ?? "santree worktree work";
|
|
2809
2856
|
const created = await mux.createWindow({
|
|
2810
2857
|
name: windowName,
|
|
2811
2858
|
cwd: worktreePath,
|
|
@@ -2821,7 +2868,7 @@ export default function Dashboard() {
|
|
|
2821
2868
|
}
|
|
2822
2869
|
else {
|
|
2823
2870
|
leaveAltScreen();
|
|
2824
|
-
|
|
2871
|
+
printCdHint({ path: di.worktree.path });
|
|
2825
2872
|
exit();
|
|
2826
2873
|
}
|
|
2827
2874
|
return;
|
|
@@ -2880,7 +2927,7 @@ export default function Dashboard() {
|
|
|
2880
2927
|
const created = await mux.createWindow({
|
|
2881
2928
|
name: windowName,
|
|
2882
2929
|
cwd,
|
|
2883
|
-
command: "
|
|
2930
|
+
command: "santree pr review",
|
|
2884
2931
|
});
|
|
2885
2932
|
dispatch({
|
|
2886
2933
|
type: "SET_ACTION_MESSAGE",
|
|
@@ -2892,7 +2939,7 @@ export default function Dashboard() {
|
|
|
2892
2939
|
}
|
|
2893
2940
|
else {
|
|
2894
2941
|
leaveAltScreen();
|
|
2895
|
-
|
|
2942
|
+
printCdHint({ path: di.worktree.path });
|
|
2896
2943
|
exit();
|
|
2897
2944
|
}
|
|
2898
2945
|
return;
|
|
@@ -2949,7 +2996,7 @@ export default function Dashboard() {
|
|
|
2949
2996
|
const created = await mux.createWindow({
|
|
2950
2997
|
name: windowName,
|
|
2951
2998
|
cwd,
|
|
2952
|
-
command: "
|
|
2999
|
+
command: "santree pr fix",
|
|
2953
3000
|
});
|
|
2954
3001
|
dispatch({
|
|
2955
3002
|
type: "SET_ACTION_MESSAGE",
|
|
@@ -2961,7 +3008,7 @@ export default function Dashboard() {
|
|
|
2961
3008
|
}
|
|
2962
3009
|
else {
|
|
2963
3010
|
leaveAltScreen();
|
|
2964
|
-
|
|
3011
|
+
printCdHint({ path: di.worktree.path });
|
|
2965
3012
|
exit();
|
|
2966
3013
|
}
|
|
2967
3014
|
return;
|
|
@@ -3008,7 +3055,7 @@ export default function Dashboard() {
|
|
|
3008
3055
|
});
|
|
3009
3056
|
// ── Render ─────────────────────────────────────────────────────────
|
|
3010
3057
|
if (state.loading) {
|
|
3011
|
-
return (_jsx(Box, { width: columns, height: rows, flexDirection: "column", children: _jsx(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, children: _jsx(SquirrelLoader, { text: "Loading dashboard..." }) }) }));
|
|
3058
|
+
return (_jsx(Box, { width: columns, height: rows, flexDirection: "column", children: _jsx(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, children: _jsx(SquirrelLoader, { text: "Loading dashboard...", version: version }) }) }));
|
|
3012
3059
|
}
|
|
3013
3060
|
if (state.error) {
|
|
3014
3061
|
return (_jsx(Box, { width: columns, height: rows, flexDirection: "column", children: _jsxs(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, flexDirection: "column", children: [_jsxs(Text, { color: "red", bold: true, children: ["Error: ", state.error] }), _jsx(Text, { dimColor: true, children: "Press R to retry or q to quit" })] }) }));
|
|
@@ -3108,14 +3155,14 @@ export default function Dashboard() {
|
|
|
3108
3155
|
? state.treeDetailScrollOffset
|
|
3109
3156
|
: state.activeTab === "triage"
|
|
3110
3157
|
? state.triageDetailScrollOffset
|
|
3111
|
-
: state.detailScrollOffset, height: contentHeight, width: rightWidth, creatingForTicket: state.creatingForTicket, creationLogs: state.creationLogs, deleteStatus: selectedDeleteStatus, triage: state.activeTab === "triage", comments: triageComments, onCall: onCall })) })] })), _jsx(Box, { children: state.overlay === "diff" ? (_jsx(Box, { width: innerWidth, paddingX: 1, children: _jsx(CommandBar, { showWorkspace: hasWorkspaceFile, mode: "diff" }) })) : state.overlay === "triage-schedule" ? (_jsx(Box, { width: innerWidth, paddingX: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: "cyan", bold: true, children: "j/k" }), _jsx(Text, { dimColor: true, children: " scroll" }), _jsx(Text, { dimColor: true, children: " · " }), _jsx(Text, { color: "cyan", bold: true, children: "q" }), _jsx(Text, { dimColor: true, children: " close" })] }) })) : (_jsxs(_Fragment, { children: [_jsx(Box, { width: leftWidth + separatorWidth, paddingX: 1, children: _jsx(CommandBar, { showWorkspace: hasWorkspaceFile, mode: "default" }) }), _jsx(Box, { width: rightWidth, children: _jsx(ActionRow, { activeTab: state.activeTab, selectedIssue: selectedIssue, selectedReview: selectedReview, overlay: state.overlay, trackerName: activeTracker.displayName, canMutate: activeTracker.canMutate === true }) })] })) })] })] }));
|
|
3158
|
+
: state.detailScrollOffset, height: contentHeight, width: rightWidth, creatingForTicket: state.creatingForTicket, creationLogs: state.creationLogs, deleteStatus: selectedDeleteStatus, prSyncing: !!selectedIssue && !!state.pendingPrs[selectedIssue.issue.identifier], triage: state.activeTab === "triage", comments: triageComments, onCall: onCall })) })] })), _jsx(Box, { children: state.overlay === "diff" ? (_jsx(Box, { width: innerWidth, paddingX: 1, children: _jsx(CommandBar, { showWorkspace: hasWorkspaceFile, mode: "diff" }) })) : state.overlay === "triage-schedule" ? (_jsx(Box, { width: innerWidth, paddingX: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: "cyan", bold: true, children: "j/k" }), _jsx(Text, { dimColor: true, children: " scroll" }), _jsx(Text, { dimColor: true, children: " · " }), _jsx(Text, { color: "cyan", bold: true, children: "q" }), _jsx(Text, { dimColor: true, children: " close" })] }) })) : (_jsxs(_Fragment, { children: [_jsx(Box, { width: leftWidth + separatorWidth, paddingX: 1, children: _jsx(CommandBar, { showWorkspace: hasWorkspaceFile, mode: "default" }) }), _jsx(Box, { width: rightWidth, children: _jsx(ActionRow, { activeTab: state.activeTab, selectedIssue: selectedIssue, selectedReview: selectedReview, overlay: state.overlay, trackerName: activeTracker.displayName, canMutate: activeTracker.canMutate === true, triageInvestigateConfigured: triageInvestigateConfigured }) })] })) })] })] }));
|
|
3112
3159
|
}
|
|
3113
3160
|
/**
|
|
3114
3161
|
* Renders the per-issue action key hints (Resume / Editor / View diff / …)
|
|
3115
3162
|
* lifted out of the detail panels so they sit on the same row as the global
|
|
3116
3163
|
* command bar. Empty when nothing is selected.
|
|
3117
3164
|
*/
|
|
3118
|
-
function ActionRow({ activeTab, selectedIssue, selectedReview, overlay, trackerName, canMutate, }) {
|
|
3165
|
+
function ActionRow({ activeTab, selectedIssue, selectedReview, overlay, trackerName, canMutate, triageInvestigateConfigured, }) {
|
|
3119
3166
|
// During the diff overlay, none of the per-issue actions apply (View diff
|
|
3120
3167
|
// is what got us here, Commit/PR/etc. need the detail panel context). Keep
|
|
3121
3168
|
// the row blank so the diff-specific CommandBar reads cleanly.
|
|
@@ -3126,7 +3173,11 @@ function ActionRow({ activeTab, selectedIssue, selectedReview, overlay, trackerN
|
|
|
3126
3173
|
? buildReviewActions(selectedReview)
|
|
3127
3174
|
: []
|
|
3128
3175
|
: selectedIssue
|
|
3129
|
-
? buildIssueActions(selectedIssue, trackerName, {
|
|
3176
|
+
? buildIssueActions(selectedIssue, trackerName, {
|
|
3177
|
+
tab: activeTab,
|
|
3178
|
+
canMutate,
|
|
3179
|
+
triageInvestigateConfigured,
|
|
3180
|
+
})
|
|
3130
3181
|
: [];
|
|
3131
3182
|
if (items.length === 0)
|
|
3132
3183
|
return _jsx(Text, { children: " " });
|
package/dist/commands/doctor.js
CHANGED
|
@@ -13,6 +13,7 @@ import { findMainRepoRoot, getSantreeDir, getInitScriptPath } from "../lib/git.j
|
|
|
13
13
|
import { getIssueTracker } from "../lib/trackers/index.js";
|
|
14
14
|
import { getMultiplexer } from "../lib/multiplexer/index.js";
|
|
15
15
|
import { resolveClaudeBinary } from "../lib/ai.js";
|
|
16
|
+
import { isStatuslineConfigured, getStatuslineCommand, isRemoteControlEnabled, missingSessionSignalHooks, } from "../lib/claude-config.js";
|
|
16
17
|
import { CURRENT_VERSION, CLAUDE_CODE_PACKAGE, SANTREE_PACKAGE, getLatestVersionFor, isUpdateAvailable, detectPackageManager, getInstallCommandFor, } from "../lib/version.js";
|
|
17
18
|
const execAsync = promisify(exec);
|
|
18
19
|
export const description = "Check system requirements and integrations";
|
|
@@ -214,16 +215,6 @@ async function checkTrackerAuth() {
|
|
|
214
215
|
hint: status.hint,
|
|
215
216
|
};
|
|
216
217
|
}
|
|
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
218
|
/**
|
|
228
219
|
* Checks if Claude Code Remote Control is enabled for all sessions.
|
|
229
220
|
* Remote Control lets you continue local sessions from any device.
|
|
@@ -233,23 +224,12 @@ function checkShellIntegration() {
|
|
|
233
224
|
* See: https://code.claude.com/docs/en/settings#settings-files
|
|
234
225
|
*/
|
|
235
226
|
function checkRemoteControl() {
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
227
|
+
if (isRemoteControlEnabled()) {
|
|
228
|
+
return { enabled: true };
|
|
249
229
|
}
|
|
250
230
|
return {
|
|
251
231
|
enabled: false,
|
|
252
|
-
hint: 'Run /config in Claude Code
|
|
232
|
+
hint: 'Run `santree setup` (or /config in Claude Code → "Enable Remote Control for all sessions")',
|
|
253
233
|
};
|
|
254
234
|
}
|
|
255
235
|
/**
|
|
@@ -257,30 +237,12 @@ function checkRemoteControl() {
|
|
|
257
237
|
* If ~/.claude/settings.json has statusLine pointing to santree
|
|
258
238
|
*/
|
|
259
239
|
async function checkStatusline() {
|
|
260
|
-
const
|
|
261
|
-
const
|
|
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
|
-
}
|
|
240
|
+
const claudeSettingsConfigured = isStatuslineConfigured();
|
|
241
|
+
const currentCommand = getStatuslineCommand();
|
|
280
242
|
let hint;
|
|
281
243
|
if (!claudeSettingsConfigured) {
|
|
282
244
|
hint =
|
|
283
|
-
'
|
|
245
|
+
'Run `santree setup`, or add to ~/.claude/settings.json: "statusLine": { "type": "command", "command": "santree helpers statusline" }';
|
|
284
246
|
}
|
|
285
247
|
return {
|
|
286
248
|
claudeSettingsConfigured,
|
|
@@ -294,44 +256,14 @@ async function checkStatusline() {
|
|
|
294
256
|
* that run "santree helpers session-signal".
|
|
295
257
|
*/
|
|
296
258
|
function checkSessionSignalHooks() {
|
|
297
|
-
const
|
|
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
|
-
}
|
|
259
|
+
const missingHooks = missingSessionSignalHooks();
|
|
328
260
|
if (missingHooks.length === 0) {
|
|
329
261
|
return { configured: true, missingHooks: [] };
|
|
330
262
|
}
|
|
331
263
|
return {
|
|
332
264
|
configured: false,
|
|
333
265
|
missingHooks,
|
|
334
|
-
hint: `Missing: ${missingHooks.join(", ")}. Run: santree helpers session-signal install`,
|
|
266
|
+
hint: `Missing: ${missingHooks.join(", ")}. Run: santree setup (or santree helpers session-signal install)`,
|
|
335
267
|
};
|
|
336
268
|
}
|
|
337
269
|
/**
|
|
@@ -470,9 +402,6 @@ function TrackerRow({ tracker }) {
|
|
|
470
402
|
const isOk = tracker.authenticated && !tracker.hint;
|
|
471
403
|
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
404
|
}
|
|
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
405
|
function StatuslineRow({ status }) {
|
|
477
406
|
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
407
|
}
|
|
@@ -508,7 +437,6 @@ function SantreeSetupRow({ status }) {
|
|
|
508
437
|
export default function Doctor() {
|
|
509
438
|
const [tools, setTools] = useState([]);
|
|
510
439
|
const [tracker, setTracker] = useState(null);
|
|
511
|
-
const [shellStatus, setShellStatus] = useState(null);
|
|
512
440
|
const [remoteControl, setRemoteControl] = useState(null);
|
|
513
441
|
const [statusline, setStatusline] = useState(null);
|
|
514
442
|
const [sessionSignal, setSessionSignal] = useState(null);
|
|
@@ -559,7 +487,7 @@ export default function Doctor() {
|
|
|
559
487
|
}
|
|
560
488
|
}
|
|
561
489
|
}
|
|
562
|
-
// Optional: a syntax-highlighting diff pager — used by `
|
|
490
|
+
// Optional: a syntax-highlighting diff pager — used by `santree worktree diff`
|
|
563
491
|
// and the dashboard `v` overlay when SANTREE_DIFF_TOOL is set. Any
|
|
564
492
|
// diff pager works (delta, diff-so-fancy, …); without one set, the
|
|
565
493
|
// dashboard renders inline with santree's own colorizer and the CLI
|
|
@@ -597,7 +525,6 @@ export default function Doctor() {
|
|
|
597
525
|
const statuslineResult = await checkStatusline();
|
|
598
526
|
setTools(results);
|
|
599
527
|
setTracker(trackerResult);
|
|
600
|
-
setShellStatus(checkShellIntegration());
|
|
601
528
|
setRemoteControl(checkRemoteControl());
|
|
602
529
|
setStatusline(statuslineResult);
|
|
603
530
|
setSessionSignal(checkSessionSignalHooks());
|
|
@@ -613,6 +540,6 @@ export default function Doctor() {
|
|
|
613
540
|
const requiredMissing = tools.filter((t) => t.required && (!t.installed || t.hint));
|
|
614
541
|
const optionalMissing = tools.filter((t) => !t.required && !t.installed);
|
|
615
542
|
const trackerOk = tracker?.authenticated && !tracker?.hint;
|
|
616
|
-
const allRequired = requiredMissing.length === 0 && trackerOk
|
|
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 }),
|
|
543
|
+
const allRequired = requiredMissing.length === 0 && trackerOk;
|
|
544
|
+
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 }), 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), " required item(s) need attention"] }), optionalMissing.length > 0 && (_jsxs(Text, { dimColor: true, children: [optionalMissing.length, " optional item(s) not installed"] }))] })) })] }));
|
|
618
545
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const description = "Guided setup \u2014 configure editor, diff tool, Claude Code & this repo";
|
|
3
|
+
export declare const options: z.ZodObject<{
|
|
4
|
+
dryRun: z.ZodDefault<z.ZodBoolean>;
|
|
5
|
+
yes: z.ZodDefault<z.ZodBoolean>;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
type Props = {
|
|
8
|
+
options: z.infer<typeof options>;
|
|
9
|
+
};
|
|
10
|
+
export default function Setup({ options: opts }: Props): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import Spinner from "ink-spinner";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { buildContext, buildSteps, } from "../lib/setup/steps.js";
|
|
7
|
+
export const description = "Guided setup — configure editor, diff tool, Claude Code & this repo";
|
|
8
|
+
export const options = z.object({
|
|
9
|
+
dryRun: z.boolean().default(false).describe("Preview what would change without writing anything"),
|
|
10
|
+
yes: z.boolean().default(false).describe("Apply all recommended steps non-interactively"),
|
|
11
|
+
});
|
|
12
|
+
export default function Setup({ options: opts }) {
|
|
13
|
+
const dryRun = opts.dryRun;
|
|
14
|
+
const [phase, setPhase] = useState("detecting");
|
|
15
|
+
const [steps, setSteps] = useState([]);
|
|
16
|
+
const [ctx, setCtx] = useState(null);
|
|
17
|
+
// select phase
|
|
18
|
+
const [cursor, setCursor] = useState(0);
|
|
19
|
+
const [selected, setSelected] = useState(new Set());
|
|
20
|
+
// configure phase
|
|
21
|
+
const [configQueue, setConfigQueue] = useState([]);
|
|
22
|
+
const [configPos, setConfigPos] = useState(0);
|
|
23
|
+
const [optionCursor, setOptionCursor] = useState(0);
|
|
24
|
+
const [choices, setChoices] = useState({});
|
|
25
|
+
// applying phase
|
|
26
|
+
const [applyIdx, setApplyIdx] = useState(0);
|
|
27
|
+
const [applyOrder, setApplyOrder] = useState([]);
|
|
28
|
+
const [applied, setApplied] = useState([]);
|
|
29
|
+
const [running, setRunning] = useState(null);
|
|
30
|
+
// ── Detect ────────────────────────────────────────────────────────────────
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
(async () => {
|
|
33
|
+
await new Promise((r) => setTimeout(r, 80)); // let the spinner paint first
|
|
34
|
+
const c = buildContext(dryRun);
|
|
35
|
+
const all = buildSteps(c).filter((s) => s.detect === "actionable");
|
|
36
|
+
setCtx(c);
|
|
37
|
+
setSteps(all);
|
|
38
|
+
if (all.length === 0) {
|
|
39
|
+
setPhase("done");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const rec = new Set();
|
|
43
|
+
all.forEach((s, i) => {
|
|
44
|
+
if (s.recommended)
|
|
45
|
+
rec.add(i);
|
|
46
|
+
});
|
|
47
|
+
setSelected(rec);
|
|
48
|
+
if (opts.yes) {
|
|
49
|
+
beginApply(all, rec, {});
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
setPhase("select");
|
|
53
|
+
}
|
|
54
|
+
})();
|
|
55
|
+
}, []);
|
|
56
|
+
function beginApply(allSteps, sel, picked) {
|
|
57
|
+
const order = [...sel].sort((a, b) => a - b);
|
|
58
|
+
// For steps with options that weren't interactively chosen, default to the
|
|
59
|
+
// first option (used by --yes and single-option steps).
|
|
60
|
+
const merged = { ...picked };
|
|
61
|
+
for (const i of order) {
|
|
62
|
+
const s = allSteps[i];
|
|
63
|
+
if (s.options && s.options.length > 0 && merged[s.id] === undefined) {
|
|
64
|
+
merged[s.id] = s.options[0].value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
setChoices(merged);
|
|
68
|
+
setApplyOrder(order);
|
|
69
|
+
setApplyIdx(0);
|
|
70
|
+
setApplied([]);
|
|
71
|
+
setPhase("applying");
|
|
72
|
+
}
|
|
73
|
+
// ── Select phase input ──────────────────────────────────────────────────────
|
|
74
|
+
useInput((input, key) => {
|
|
75
|
+
if (key.escape)
|
|
76
|
+
process.exit(0);
|
|
77
|
+
else if (key.upArrow)
|
|
78
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
79
|
+
else if (key.downArrow)
|
|
80
|
+
setCursor((c) => Math.min(steps.length - 1, c + 1));
|
|
81
|
+
else if (input === " ") {
|
|
82
|
+
setSelected((s) => {
|
|
83
|
+
const next = new Set(s);
|
|
84
|
+
if (next.has(cursor))
|
|
85
|
+
next.delete(cursor);
|
|
86
|
+
else
|
|
87
|
+
next.add(cursor);
|
|
88
|
+
return next;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
else if (key.return) {
|
|
92
|
+
// Steps needing an interactive choice (>1 option).
|
|
93
|
+
const queue = [...selected]
|
|
94
|
+
.sort((a, b) => a - b)
|
|
95
|
+
.filter((i) => (steps[i].options?.length ?? 0) > 1);
|
|
96
|
+
if (queue.length > 0) {
|
|
97
|
+
setConfigQueue(queue);
|
|
98
|
+
setConfigPos(0);
|
|
99
|
+
setOptionCursor(0);
|
|
100
|
+
setPhase("configure");
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
beginApply(steps, selected, {});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}, { isActive: phase === "select" });
|
|
107
|
+
// ── Configure phase input ───────────────────────────────────────────────────
|
|
108
|
+
useInput((_input, key) => {
|
|
109
|
+
if (key.escape) {
|
|
110
|
+
setPhase("select");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const stepIdx = configQueue[configPos];
|
|
114
|
+
const step = steps[stepIdx];
|
|
115
|
+
const optCount = step.options.length;
|
|
116
|
+
if (key.upArrow)
|
|
117
|
+
setOptionCursor((c) => Math.max(0, c - 1));
|
|
118
|
+
else if (key.downArrow)
|
|
119
|
+
setOptionCursor((c) => Math.min(optCount - 1, c + 1));
|
|
120
|
+
else if (key.return) {
|
|
121
|
+
const value = step.options[optionCursor].value;
|
|
122
|
+
const nextChoices = { ...choices, [step.id]: value };
|
|
123
|
+
setChoices(nextChoices);
|
|
124
|
+
if (configPos + 1 < configQueue.length) {
|
|
125
|
+
setConfigPos((p) => p + 1);
|
|
126
|
+
setOptionCursor(0);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
beginApply(steps, selected, nextChoices);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}, { isActive: phase === "configure" });
|
|
133
|
+
// ── Applying phase ──────────────────────────────────────────────────────────
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (phase !== "applying")
|
|
136
|
+
return;
|
|
137
|
+
if (applyIdx >= applyOrder.length) {
|
|
138
|
+
setRunning(null);
|
|
139
|
+
setPhase("done");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const step = steps[applyOrder[applyIdx]];
|
|
143
|
+
setRunning(step.title);
|
|
144
|
+
let cancelled = false;
|
|
145
|
+
(async () => {
|
|
146
|
+
await new Promise((r) => setTimeout(r, 60)); // paint "running" frame
|
|
147
|
+
let result;
|
|
148
|
+
try {
|
|
149
|
+
result = await step.apply(choices[step.id]);
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
result = { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
153
|
+
}
|
|
154
|
+
if (cancelled)
|
|
155
|
+
return;
|
|
156
|
+
setApplied((prev) => [...prev, { step, result }]);
|
|
157
|
+
setApplyIdx((i) => i + 1);
|
|
158
|
+
})();
|
|
159
|
+
return () => {
|
|
160
|
+
cancelled = true;
|
|
161
|
+
};
|
|
162
|
+
}, [phase, applyIdx]);
|
|
163
|
+
// ── Exit ────────────────────────────────────────────────────────────────────
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
if (phase === "done") {
|
|
166
|
+
const t = setTimeout(() => process.exit(0), 120);
|
|
167
|
+
return () => clearTimeout(t);
|
|
168
|
+
}
|
|
169
|
+
}, [phase]);
|
|
170
|
+
// ── Render ──────────────────────────────────────────────────────────────────
|
|
171
|
+
const title = (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "santree setup" }), dryRun && _jsx(Text, { color: "yellow", children: " (dry run \u2014 nothing will be written)" })] }));
|
|
172
|
+
if (phase === "detecting") {
|
|
173
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [title, _jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Detecting your setup\u2026" })] })] }));
|
|
174
|
+
}
|
|
175
|
+
if (phase === "done" && steps.length === 0) {
|
|
176
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [title, _jsx(Text, { color: "green", children: "\u2713 Everything's already configured \u2014 you're all set." }), !ctx?.repoRoot && (_jsx(Text, { dimColor: true, children: "Tip: run this again inside a git repo to configure repo-specific bits." }))] }));
|
|
177
|
+
}
|
|
178
|
+
if (phase === "select") {
|
|
179
|
+
let lastScope = null;
|
|
180
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [title, _jsx(Text, { children: "What should I set up? (Space toggles, Enter applies)" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: steps.map((s, i) => {
|
|
181
|
+
const head = s.scope !== lastScope;
|
|
182
|
+
lastScope = s.scope;
|
|
183
|
+
const box = selected.has(i) ? "[x]" : "[ ]";
|
|
184
|
+
const isCur = i === cursor;
|
|
185
|
+
return (_jsxs(Box, { flexDirection: "column", children: [head && _jsx(Text, { dimColor: true, children: s.scope === "global" ? "Global" : "This repo" }), _jsxs(Text, { children: [_jsxs(Text, { color: isCur ? "cyan" : undefined, bold: isCur, children: [isCur ? "> " : " ", box] }), " ", s.title, s.kind === "spawn" && _jsx(Text, { color: "yellow", children: " \u2937 runs a command" })] }), isCur && _jsx(Text, { dimColor: true, children: " " + s.detail })] }, s.id));
|
|
186
|
+
}) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191/\u2193 move \u00B7 Space toggle \u00B7 Enter apply \u00B7 Esc cancel" }) })] }));
|
|
187
|
+
}
|
|
188
|
+
if (phase === "configure") {
|
|
189
|
+
const step = steps[configQueue[configPos]];
|
|
190
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [title, _jsx(Text, { bold: true, children: step.title }), _jsx(Text, { children: step.optionPrompt || "Pick one:" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: step.options.map((o, i) => (_jsxs(Text, { children: [_jsx(Text, { color: i === optionCursor ? "cyan" : undefined, bold: i === optionCursor, children: i === optionCursor ? "> " : " " }), o.label] }, o.value))) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["\u2191/\u2193 select \u00B7 Enter confirm", configQueue.length > 1 ? ` (${configPos + 1}/${configQueue.length})` : ""] }) })] }));
|
|
191
|
+
}
|
|
192
|
+
// applying / done
|
|
193
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [title, applied.map(({ step, result }, i) => (_jsxs(Text, { color: result.ok ? "green" : "red", children: [result.ok ? "✓" : "✗", " ", step.title, " ", _jsxs(Text, { dimColor: true, children: ["\u2014 ", result.message] })] }, i))), phase === "applying" && running && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { children: [" ", running, "\u2026"] })] })), phase === "done" && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "green", bold: true, children: "Done. Run `santree doctor` to verify." }), !dryRun && ctx?.shell && (_jsxs(Text, { dimColor: true, children: ["Restart your shell (or `source ", ctx.shell.rcPath, "`) to load the changes."] }))] }))] }));
|
|
194
|
+
}
|