santree 0.7.5 → 0.7.7
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 +165 -61
- 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 +10 -2
- package/dist/commands/pr/fix.js +61 -12
- package/dist/commands/worktree/create.js +6 -1
- package/dist/commands/worktree/open.js +3 -2
- package/dist/lib/ai.d.ts +11 -0
- package/dist/lib/ai.js +71 -2
- package/dist/lib/claude-config.d.ts +18 -4
- package/dist/lib/claude-config.js +58 -23
- 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 +4 -2
- package/dist/lib/dashboard/DetailPanel.js +44 -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 +13 -2
- package/dist/lib/dashboard/types.js +33 -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 +2 -2
- package/dist/lib/setup/apply.js +2 -2
- package/dist/lib/setup/gitignore.d.ts +7 -0
- package/dist/lib/setup/gitignore.js +39 -1
- package/dist/lib/setup/steps.d.ts +6 -2
- package/dist/lib/setup/steps.js +65 -60
- package/dist/lib/setup/tools.d.ts +1 -1
- package/dist/lib/setup/tools.js +1 -1
- package/dist/lib/trackers/github/index.js +2 -2
- package/dist/lib/trackers/local/store.js +1 -1
- package/package.json +1 -1
- 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 -545
- 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/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/commands/setup.d.ts +0 -11
- package/dist/commands/setup.js +0 -194
- 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/dist/lib/setup/shell-config.d.ts +0 -38
- package/dist/lib/setup/shell-config.js +0 -131
- package/prompts/english-tutor-prompt.njk +0 -15
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
import { renderPrompt } from "../../../lib/prompts.js";
|
|
3
|
-
import { getLogPath } from "../../../lib/english-tutor.js";
|
|
4
|
-
export const description = "English-tutor instruction (UserPromptSubmit hook)";
|
|
5
|
-
export default function Prompt() {
|
|
6
|
-
const hasRun = useRef(false);
|
|
7
|
-
useEffect(() => {
|
|
8
|
-
if (hasRun.current)
|
|
9
|
-
return;
|
|
10
|
-
hasRun.current = true;
|
|
11
|
-
const text = renderPrompt("english-tutor-prompt", { logPath: getLogPath() });
|
|
12
|
-
process.stdout.write(text);
|
|
13
|
-
process.exit(0);
|
|
14
|
-
}, []);
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import { useEffect, useRef } from "react";
|
|
3
|
-
import { getLogPath } from "../../../lib/english-tutor.js";
|
|
4
|
-
export const description = "Replay practice log (SessionStart hook)";
|
|
5
|
-
export default function SessionStart() {
|
|
6
|
-
const hasRun = useRef(false);
|
|
7
|
-
useEffect(() => {
|
|
8
|
-
if (hasRun.current)
|
|
9
|
-
return;
|
|
10
|
-
hasRun.current = true;
|
|
11
|
-
try {
|
|
12
|
-
const logPath = getLogPath();
|
|
13
|
-
if (fs.existsSync(logPath)) {
|
|
14
|
-
const contents = fs.readFileSync(logPath, "utf-8").trim();
|
|
15
|
-
const entryCount = (contents.match(/^- /gm) || []).length;
|
|
16
|
-
if (contents.length > 0) {
|
|
17
|
-
process.stdout.write(`[ENGLISH TUTOR — PRACTICE LOG REPLAY]\n\n${contents}\n\n--- LOG END ---\n`);
|
|
18
|
-
// Only ask for a summary once there are enough entries to be useful.
|
|
19
|
-
// Below that threshold the log is too sparse to surface real patterns,
|
|
20
|
-
// and a forced summary would just noise up new sessions.
|
|
21
|
-
if (entryCount >= 3) {
|
|
22
|
-
process.stdout.write(`\n[ENGLISH TUTOR — SESSION-START INSTRUCTION]\n` +
|
|
23
|
-
`At the very start of your first response in this session, briefly note any RECURRING patterns from the log above (1-3 short bullets, e.g. "you frequently drop articles", "watch for 'their' vs 'there'"). Skip this entirely if no clear patterns emerge or if the user's first message is purely a coding task. Do not list every entry — only patterns.\n`);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
// hook must never block the session — silently no-op on read error
|
|
30
|
-
}
|
|
31
|
-
process.exit(0);
|
|
32
|
-
}, []);
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
import { uninstallHooks } from "../../../lib/english-tutor.js";
|
|
3
|
-
export const description = "Remove English-tutor hooks from Claude Code settings";
|
|
4
|
-
export default function Uninstall() {
|
|
5
|
-
const hasRun = useRef(false);
|
|
6
|
-
useEffect(() => {
|
|
7
|
-
if (hasRun.current)
|
|
8
|
-
return;
|
|
9
|
-
hasRun.current = true;
|
|
10
|
-
const settingsPath = uninstallHooks();
|
|
11
|
-
process.stdout.write(`English-tutor hooks removed from ${settingsPath}\n`);
|
|
12
|
-
process.exit(0);
|
|
13
|
-
}, []);
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
import { signalState } from "../../../lib/session-signal.js";
|
|
3
|
-
export const description = "Signal exited state (SessionEnd hook)";
|
|
4
|
-
export default function End() {
|
|
5
|
-
const hasRun = useRef(false);
|
|
6
|
-
useEffect(() => {
|
|
7
|
-
if (hasRun.current)
|
|
8
|
-
return;
|
|
9
|
-
hasRun.current = true;
|
|
10
|
-
signalState("exited");
|
|
11
|
-
}, []);
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const description = "Signal session state for Claude Code hooks";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const description = "Signal session state for Claude Code hooks";
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
export declare const description = "Install session-signal hooks into Claude Code settings";
|
|
3
|
-
export declare const options: z.ZodObject<{
|
|
4
|
-
dry: z.ZodOptional<z.ZodBoolean>;
|
|
5
|
-
}, z.core.$strip>;
|
|
6
|
-
export default function Install({ options: opts }: {
|
|
7
|
-
options: z.infer<typeof options>;
|
|
8
|
-
}): null;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { getHooksJson, installHooks } from "../../../lib/session-signal.js";
|
|
4
|
-
export const description = "Install session-signal hooks into Claude Code settings";
|
|
5
|
-
export const options = z.object({
|
|
6
|
-
dry: z.boolean().optional().describe("Print the hooks JSON without writing"),
|
|
7
|
-
});
|
|
8
|
-
export default function Install({ options: opts }) {
|
|
9
|
-
const hasRun = useRef(false);
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
if (hasRun.current)
|
|
12
|
-
return;
|
|
13
|
-
hasRun.current = true;
|
|
14
|
-
if (opts?.dry) {
|
|
15
|
-
const snippet = { hooks: getHooksJson() };
|
|
16
|
-
process.stdout.write(JSON.stringify(snippet, null, 2) + "\n");
|
|
17
|
-
process.exit(0);
|
|
18
|
-
}
|
|
19
|
-
const settingsPath = installHooks();
|
|
20
|
-
process.stdout.write(`Session-signal hooks installed in ${settingsPath}\n`);
|
|
21
|
-
process.exit(0);
|
|
22
|
-
}, []);
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
import { signalState } from "../../../lib/session-signal.js";
|
|
3
|
-
export const description = "Signal waiting state (Notification hook)";
|
|
4
|
-
export default function Notification() {
|
|
5
|
-
const hasRun = useRef(false);
|
|
6
|
-
useEffect(() => {
|
|
7
|
-
if (hasRun.current)
|
|
8
|
-
return;
|
|
9
|
-
hasRun.current = true;
|
|
10
|
-
signalState("waiting");
|
|
11
|
-
}, []);
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
import { signalState } from "../../../lib/session-signal.js";
|
|
3
|
-
export const description = "Signal active state (UserPromptSubmit hook)";
|
|
4
|
-
export default function Prompt() {
|
|
5
|
-
const hasRun = useRef(false);
|
|
6
|
-
useEffect(() => {
|
|
7
|
-
if (hasRun.current)
|
|
8
|
-
return;
|
|
9
|
-
hasRun.current = true;
|
|
10
|
-
signalState("active");
|
|
11
|
-
}, []);
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
import { signalState } from "../../../lib/session-signal.js";
|
|
3
|
-
export const description = "Signal idle state (Stop hook)";
|
|
4
|
-
export default function Stop() {
|
|
5
|
-
const hasRun = useRef(false);
|
|
6
|
-
useEffect(() => {
|
|
7
|
-
if (hasRun.current)
|
|
8
|
-
return;
|
|
9
|
-
hasRun.current = true;
|
|
10
|
-
signalState("idle");
|
|
11
|
-
}, []);
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
3
|
-
import { Text, Box, useInput } from "ink";
|
|
4
|
-
import Spinner from "ink-spinner";
|
|
5
|
-
import { findMainRepoRoot } from "../../lib/git.js";
|
|
6
|
-
import { setRepoTracker, getIssueTracker } from "../../lib/trackers/index.js";
|
|
7
|
-
import { setRepoLinearOrg } from "../../lib/trackers/linear/index.js";
|
|
8
|
-
import { readLinearAuthStore } from "../../lib/trackers/auth-store.js";
|
|
9
|
-
import { getAuthenticatedUser, getCurrentRepoNwo } from "../../lib/trackers/github/auth.js";
|
|
10
|
-
export const description = "Pick and configure the issue tracker for this repo";
|
|
11
|
-
const TRACKERS = [
|
|
12
|
-
{ kind: "local", label: "Local", hint: "built-in, file-based — no account needed" },
|
|
13
|
-
{ kind: "linear", label: "Linear", hint: "OAuth workspace" },
|
|
14
|
-
{ kind: "github", label: "GitHub", hint: "GitHub Issues via gh CLI" },
|
|
15
|
-
];
|
|
16
|
-
export default function IssueSetup() {
|
|
17
|
-
const [phase, setPhase] = useState("checking");
|
|
18
|
-
const [message, setMessage] = useState("");
|
|
19
|
-
const [error, setError] = useState(null);
|
|
20
|
-
const [trackerIdx, setTrackerIdx] = useState(0);
|
|
21
|
-
const [orgs, setOrgs] = useState([]);
|
|
22
|
-
const [orgIdx, setOrgIdx] = useState(0);
|
|
23
|
-
const [repoRoot, setRepoRoot] = useState(null);
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
const root = findMainRepoRoot();
|
|
26
|
-
if (!root) {
|
|
27
|
-
setError("Not inside a git repository");
|
|
28
|
-
setPhase("error");
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
setRepoRoot(root);
|
|
32
|
-
setPhase("pick-tracker");
|
|
33
|
-
}, []);
|
|
34
|
-
function finish(label) {
|
|
35
|
-
setMessage(label);
|
|
36
|
-
setPhase("done");
|
|
37
|
-
}
|
|
38
|
-
async function chooseTracker(kind) {
|
|
39
|
-
const root = repoRoot;
|
|
40
|
-
if (kind === "local") {
|
|
41
|
-
setRepoTracker(root, "local");
|
|
42
|
-
finish(`Active tracker for this repo: ${getIssueTracker(root).displayName}`);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
if (kind === "linear") {
|
|
46
|
-
const store = readLinearAuthStore();
|
|
47
|
-
const list = Object.entries(store).map(([slug, tokens]) => ({
|
|
48
|
-
slug,
|
|
49
|
-
name: tokens.org_name,
|
|
50
|
-
}));
|
|
51
|
-
if (list.length === 0) {
|
|
52
|
-
setError("No authenticated Linear workspaces. Run: santree linear auth");
|
|
53
|
-
setPhase("error");
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
if (list.length === 1) {
|
|
57
|
-
setRepoLinearOrg(root, list[0].slug);
|
|
58
|
-
setRepoTracker(root, "linear");
|
|
59
|
-
finish(`Linked to ${list[0].name} (${list[0].slug})`);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
setOrgs(list);
|
|
63
|
-
setOrgIdx(0);
|
|
64
|
-
setPhase("pick-org");
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
// github
|
|
68
|
-
setPhase("checking");
|
|
69
|
-
const user = await getAuthenticatedUser();
|
|
70
|
-
if (!user) {
|
|
71
|
-
setError("GitHub CLI not authenticated. Run: santree github auth");
|
|
72
|
-
setPhase("error");
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
setRepoTracker(root, "github");
|
|
76
|
-
const nwo = await getCurrentRepoNwo(root);
|
|
77
|
-
finish(`Active tracker: GitHub (@${user.login}${nwo ? ` · ${nwo}` : ""})`);
|
|
78
|
-
}
|
|
79
|
-
useInput((_input, key) => {
|
|
80
|
-
if (phase === "pick-tracker") {
|
|
81
|
-
if (key.upArrow)
|
|
82
|
-
setTrackerIdx((i) => Math.max(0, i - 1));
|
|
83
|
-
else if (key.downArrow)
|
|
84
|
-
setTrackerIdx((i) => Math.min(TRACKERS.length - 1, i + 1));
|
|
85
|
-
else if (key.return)
|
|
86
|
-
void chooseTracker(TRACKERS[trackerIdx].kind);
|
|
87
|
-
}
|
|
88
|
-
else if (phase === "pick-org") {
|
|
89
|
-
if (key.upArrow)
|
|
90
|
-
setOrgIdx((i) => Math.max(0, i - 1));
|
|
91
|
-
else if (key.downArrow)
|
|
92
|
-
setOrgIdx((i) => Math.min(orgs.length - 1, i + 1));
|
|
93
|
-
else if (key.return) {
|
|
94
|
-
const org = orgs[orgIdx];
|
|
95
|
-
setRepoLinearOrg(repoRoot, org.slug);
|
|
96
|
-
setRepoTracker(repoRoot, "linear");
|
|
97
|
-
finish(`Linked to ${org.name} (${org.slug})`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
useEffect(() => {
|
|
102
|
-
if (phase === "done" || phase === "error") {
|
|
103
|
-
const timer = setTimeout(() => process.exit(phase === "error" ? 1 : 0), 100);
|
|
104
|
-
return () => clearTimeout(timer);
|
|
105
|
-
}
|
|
106
|
-
}, [phase]);
|
|
107
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Issue tracker setup" }) }), phase === "checking" && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Checking\u2026" })] })), phase === "pick-tracker" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Select the issue tracker for this repo:" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: TRACKERS.map((t, i) => (_jsxs(Text, { children: [i === trackerIdx ? (_jsx(Text, { color: "cyan", bold: true, children: "> " })) : (_jsx(Text, { children: " " })), t.label, " ", _jsxs(Text, { dimColor: true, children: ["\u2014 ", t.hint] })] }, t.kind))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191/\u2193 to select, Enter to confirm" }) })] })), phase === "pick-org" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Select a Linear workspace to link:" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: orgs.map((org, i) => (_jsxs(Text, { children: [i === orgIdx ? (_jsx(Text, { color: "cyan", bold: true, children: "> " })) : (_jsx(Text, { children: " " })), org.name, " (", org.slug, ")"] }, org.slug))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191/\u2193 to select, Enter to confirm" }) })] })), phase === "done" && _jsxs(Text, { color: "green", children: ["\u2713 ", message] }), phase === "error" && (_jsxs(Text, { color: "red", bold: true, children: ["\u2717 ", error] }))] }));
|
|
108
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { z } from "zod/v4";
|
|
2
|
-
export declare const description = "Switch the active issue tracker for this repo";
|
|
3
|
-
export declare const args: z.ZodTuple<[z.ZodEnum<{
|
|
4
|
-
linear: "linear";
|
|
5
|
-
github: "github";
|
|
6
|
-
local: "local";
|
|
7
|
-
}>], null>;
|
|
8
|
-
type Props = {
|
|
9
|
-
args: z.infer<typeof args>;
|
|
10
|
-
};
|
|
11
|
-
export default function IssueSwitch({ args }: Props): import("react/jsx-runtime").JSX.Element;
|
|
12
|
-
export {};
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
3
|
-
import { Text, Box } from "ink";
|
|
4
|
-
import { argument } from "pastel";
|
|
5
|
-
import { z } from "zod/v4";
|
|
6
|
-
import { findMainRepoRoot } from "../../lib/git.js";
|
|
7
|
-
import { setRepoTracker, getIssueTracker } from "../../lib/trackers/index.js";
|
|
8
|
-
export const description = "Switch the active issue tracker for this repo";
|
|
9
|
-
export const args = z.tuple([
|
|
10
|
-
z.enum(["linear", "github", "local"]).describe(argument({
|
|
11
|
-
name: "kind",
|
|
12
|
-
description: "Tracker kind: linear, github, or local (built-in)",
|
|
13
|
-
})),
|
|
14
|
-
]);
|
|
15
|
-
export default function IssueSwitch({ args }) {
|
|
16
|
-
const [kind] = args;
|
|
17
|
-
const [status, setStatus] = useState("switching");
|
|
18
|
-
const [message, setMessage] = useState("");
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
const repoRoot = findMainRepoRoot();
|
|
21
|
-
if (!repoRoot) {
|
|
22
|
-
setMessage("Not inside a git repository");
|
|
23
|
-
setStatus("error");
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
setRepoTracker(repoRoot, kind);
|
|
27
|
-
const tracker = getIssueTracker(repoRoot);
|
|
28
|
-
setMessage(`Active tracker for this repo: ${tracker.displayName}`);
|
|
29
|
-
setStatus("done");
|
|
30
|
-
}, [kind]);
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
if (status === "done" || status === "error") {
|
|
33
|
-
const timer = setTimeout(() => process.exit(status === "error" ? 1 : 0), 50);
|
|
34
|
-
return () => clearTimeout(timer);
|
|
35
|
-
}
|
|
36
|
-
}, [status]);
|
|
37
|
-
return (_jsxs(Box, { padding: 1, children: [status === "done" && (_jsxs(Text, { color: "green", bold: true, children: ["\u2713 ", message] })), status === "error" && (_jsxs(Text, { color: "red", bold: true, children: ["\u2717 ", message] }))] }));
|
|
38
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
3
|
-
import { Text, Box, useInput } from "ink";
|
|
4
|
-
import Spinner from "ink-spinner";
|
|
5
|
-
import { findMainRepoRoot } from "../../lib/git.js";
|
|
6
|
-
import { setRepoLinearOrg, getRepoLinearOrg } from "../../lib/trackers/linear/index.js";
|
|
7
|
-
import { readLinearAuthStore } from "../../lib/trackers/auth-store.js";
|
|
8
|
-
import { setRepoTracker } from "../../lib/trackers/index.js";
|
|
9
|
-
export const description = "Switch Linear workspace for this repo";
|
|
10
|
-
export default function LinearSwitch() {
|
|
11
|
-
const [status, setStatus] = useState("checking");
|
|
12
|
-
const [message, setMessage] = useState("");
|
|
13
|
-
const [error, setError] = useState(null);
|
|
14
|
-
const [choices, setChoices] = useState([]);
|
|
15
|
-
const [selected, setSelected] = useState(0);
|
|
16
|
-
const [currentOrg, setCurrentOrg] = useState(null);
|
|
17
|
-
useInput((input, key) => {
|
|
18
|
-
if (status !== "choosing")
|
|
19
|
-
return;
|
|
20
|
-
if (key.upArrow) {
|
|
21
|
-
setSelected((s) => Math.max(0, s - 1));
|
|
22
|
-
}
|
|
23
|
-
else if (key.downArrow) {
|
|
24
|
-
setSelected((s) => Math.min(choices.length - 1, s + 1));
|
|
25
|
-
}
|
|
26
|
-
else if (key.return) {
|
|
27
|
-
const choice = choices[selected];
|
|
28
|
-
const repoRoot = findMainRepoRoot();
|
|
29
|
-
setRepoLinearOrg(repoRoot, choice.slug);
|
|
30
|
-
setRepoTracker(repoRoot, "linear");
|
|
31
|
-
setMessage(`Switched to ${choice.name} (${choice.slug})`);
|
|
32
|
-
setStatus("done");
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
async function run() {
|
|
37
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
38
|
-
const repoRoot = findMainRepoRoot();
|
|
39
|
-
if (!repoRoot) {
|
|
40
|
-
setError("Not inside a git repository");
|
|
41
|
-
setStatus("error");
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
const store = readLinearAuthStore();
|
|
45
|
-
const orgs = Object.entries(store).map(([slug, tokens]) => ({
|
|
46
|
-
slug,
|
|
47
|
-
name: tokens.org_name,
|
|
48
|
-
}));
|
|
49
|
-
if (orgs.length === 0) {
|
|
50
|
-
setError("No authenticated workspaces. Run: santree linear auth");
|
|
51
|
-
setStatus("error");
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
if (orgs.length === 1) {
|
|
55
|
-
const org = orgs[0];
|
|
56
|
-
setRepoLinearOrg(repoRoot, org.slug);
|
|
57
|
-
setRepoTracker(repoRoot, "linear");
|
|
58
|
-
setMessage(`Linked to ${org.name} (${org.slug})`);
|
|
59
|
-
setStatus("done");
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
setCurrentOrg(getRepoLinearOrg(repoRoot));
|
|
63
|
-
setChoices(orgs);
|
|
64
|
-
setStatus("choosing");
|
|
65
|
-
}
|
|
66
|
-
run();
|
|
67
|
-
}, []);
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
if (status === "done" || status === "error") {
|
|
70
|
-
const timer = setTimeout(() => process.exit(status === "error" ? 1 : 0), 100);
|
|
71
|
-
return () => clearTimeout(timer);
|
|
72
|
-
}
|
|
73
|
-
}, [status]);
|
|
74
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Linear Switch" }) }), status === "checking" && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Checking..." })] })), status === "choosing" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Select a workspace to link to this repo:" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: choices.map((org, i) => (_jsxs(Text, { children: [i === selected ? (_jsx(Text, { color: "cyan", bold: true, children: "> " })) : (_jsx(Text, { children: " " })), org.name, " (", org.slug, ")", org.slug === currentOrg && _jsx(Text, { dimColor: true, children: " (current)" })] }, org.slug))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191/\u2193 to select, Enter to confirm" }) })] })), status === "done" && _jsxs(Text, { color: "green", children: ["\u2713 ", message] }), status === "error" && (_jsxs(Text, { color: "red", bold: true, children: ["\u2717 ", error] }))] }));
|
|
75
|
-
}
|
package/dist/commands/setup.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
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 {};
|
package/dist/commands/setup.js
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
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
|
-
}
|