santree 0.1.5 → 0.2.1
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 +34 -2
- package/dist/cli.js +0 -0
- package/dist/commands/dashboard.d.ts +2 -0
- package/dist/commands/dashboard.js +945 -0
- package/dist/commands/doctor.js +4 -1
- package/dist/commands/helpers/template.d.ts +1 -1
- package/dist/commands/pr/fix.js +2 -2
- package/dist/commands/pr/review.js +2 -2
- package/dist/commands/worktree/work.js +19 -3
- package/dist/lib/ai.d.ts +7 -0
- package/dist/lib/ai.js +9 -1
- package/dist/lib/dashboard/DetailPanel.d.ts +11 -0
- package/dist/lib/dashboard/DetailPanel.js +230 -0
- package/dist/lib/dashboard/IssueList.d.ts +13 -0
- package/dist/lib/dashboard/IssueList.js +126 -0
- package/dist/lib/dashboard/Overlays.d.ts +25 -0
- package/dist/lib/dashboard/Overlays.js +25 -0
- package/dist/lib/dashboard/data.d.ts +5 -0
- package/dist/lib/dashboard/data.js +103 -0
- package/dist/lib/dashboard/types.d.ts +161 -0
- package/dist/lib/dashboard/types.js +164 -0
- package/dist/lib/git.d.ts +19 -0
- package/dist/lib/git.js +32 -1
- package/dist/lib/github.d.ts +2 -1
- package/dist/lib/github.js +3 -2
- package/dist/lib/linear.d.ts +20 -0
- package/dist/lib/linear.js +53 -0
- package/package.json +2 -2
package/dist/commands/doctor.js
CHANGED
|
@@ -4,8 +4,11 @@ import Spinner from "ink-spinner";
|
|
|
4
4
|
import { useEffect, useState } from "react";
|
|
5
5
|
import { exec, execSync } from "child_process";
|
|
6
6
|
import { promisify } from "util";
|
|
7
|
+
import { createRequire } from "module";
|
|
7
8
|
import * as fs from "fs";
|
|
8
9
|
import * as path from "path";
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const { version } = require("../../package.json");
|
|
9
12
|
import { findMainRepoRoot, getSantreeDir, getInitScriptPath } from "../lib/git.js";
|
|
10
13
|
import { getAuthStatus, getValidTokens } from "../lib/linear.js";
|
|
11
14
|
const execAsync = promisify(exec);
|
|
@@ -336,5 +339,5 @@ export default function Doctor() {
|
|
|
336
339
|
const optionalMissing = tools.filter((t) => !t.required && !t.installed);
|
|
337
340
|
const linearOk = linear?.authenticated && linear?.tokenValid && linear?.repoLinked;
|
|
338
341
|
const allRequired = requiredMissing.length === 0 && linearOk && shellStatus?.configured;
|
|
339
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [
|
|
342
|
+
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" }) }), linear && _jsx(LinearRow, { linear: linear }), 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: "Aesthetics" }) }), statusline && _jsx(StatuslineRow, { status: statusline }), _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 + (linearOk ? 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"] }))] })) })] }));
|
|
340
343
|
}
|
|
@@ -2,8 +2,8 @@ import { z } from "zod/v4";
|
|
|
2
2
|
export declare const description = "Render a template to stdout";
|
|
3
3
|
export declare const args: z.ZodTuple<[z.ZodEnum<{
|
|
4
4
|
linear: "linear";
|
|
5
|
-
"git-changes": "git-changes";
|
|
6
5
|
pr: "pr";
|
|
6
|
+
"git-changes": "git-changes";
|
|
7
7
|
"fix-pr": "fix-pr";
|
|
8
8
|
review: "review";
|
|
9
9
|
}>], null>;
|
package/dist/commands/pr/fix.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
3
|
import { Text, Box } from "ink";
|
|
4
4
|
import Spinner from "ink-spinner";
|
|
5
|
-
import { resolveAIContext, renderAIPrompt, launchAgent, cleanupImages, fetchAndRenderPR, fetchAndRenderDiff, } from "../../lib/ai.js";
|
|
5
|
+
import { resolveAIContext, renderAIPrompt, launchAgent, resolveAgentBinary, cleanupImages, fetchAndRenderPR, fetchAndRenderDiff, } from "../../lib/ai.js";
|
|
6
6
|
export const description = "Fix PR review comments";
|
|
7
7
|
export default function Fix() {
|
|
8
8
|
const [status, setStatus] = useState("loading");
|
|
@@ -54,5 +54,5 @@ export default function Fix() {
|
|
|
54
54
|
}
|
|
55
55
|
init();
|
|
56
56
|
}, []);
|
|
57
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, width: "100%", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Fix PR" }) }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: status === "error" ? "red" : "magenta", paddingX: 1, width: "100%", children: [branch && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "branch:" }), _jsx(Text, { color: "cyan", bold: true, children: branch })] })), ticketId && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "ticket:" }), _jsx(Text, { color: "blue", bold: true, children: ticketId })] })), _jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "mode:" }), _jsx(Text, { backgroundColor: "magenta", color: "white", bold: true, children: " fix PR " })] })] }), _jsxs(Box, { marginTop: 1, children: [(status === "loading" || status === "fetching") && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { children: [" ", status === "loading" ? "Loading..." : "Fetching ticket and PR feedback..."] })] })), status === "launching" && (_jsxs(Box, { flexDirection: "column", children: [
|
|
57
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, width: "100%", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Fix PR" }) }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: status === "error" ? "red" : "magenta", paddingX: 1, width: "100%", children: [branch && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "branch:" }), _jsx(Text, { color: "cyan", bold: true, children: branch })] })), ticketId && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "ticket:" }), _jsx(Text, { color: "blue", bold: true, children: ticketId })] })), _jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "mode:" }), _jsx(Text, { backgroundColor: "magenta", color: "white", bold: true, children: " fix PR " })] })] }), _jsxs(Box, { marginTop: 1, children: [(status === "loading" || status === "fetching") && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { children: [" ", status === "loading" ? "Loading..." : "Fetching ticket and PR feedback..."] })] })), status === "launching" && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "green", bold: true, children: ["\u2713 Launching ", resolveAgentBinary() === "happy" ? "Claude (through Happy)" : "Claude", "..."] }), _jsxs(Text, { dimColor: true, children: [" ", resolveAgentBinary(), " ", `"<fix-pr prompt for ${ticketId}>"`] })] })), status === "error" && (_jsxs(Text, { color: "red", bold: true, children: ["\u2717 ", error] }))] })] }));
|
|
58
58
|
}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
3
|
import { Text, Box } from "ink";
|
|
4
4
|
import Spinner from "ink-spinner";
|
|
5
|
-
import { resolveAIContext, renderAIPrompt, launchAgent, cleanupImages, fetchAndRenderDiff, } from "../../lib/ai.js";
|
|
5
|
+
import { resolveAIContext, renderAIPrompt, launchAgent, resolveAgentBinary, cleanupImages, fetchAndRenderDiff, } from "../../lib/ai.js";
|
|
6
6
|
export const description = "Review changes against ticket requirements";
|
|
7
7
|
export default function Review() {
|
|
8
8
|
const [status, setStatus] = useState("loading");
|
|
@@ -47,5 +47,5 @@ export default function Review() {
|
|
|
47
47
|
}
|
|
48
48
|
init();
|
|
49
49
|
}, []);
|
|
50
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, width: "100%", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Review" }) }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: status === "error" ? "red" : "yellow", paddingX: 1, width: "100%", children: [branch && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "branch:" }), _jsx(Text, { color: "cyan", bold: true, children: branch })] })), ticketId && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "ticket:" }), _jsx(Text, { color: "blue", bold: true, children: ticketId })] })), _jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "mode:" }), _jsx(Text, { backgroundColor: "yellow", color: "white", bold: true, children: " review " })] })] }), _jsxs(Box, { marginTop: 1, children: [(status === "loading" || status === "fetching") && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { children: [" ", status === "loading" ? "Loading..." : "Fetching ticket, diff, and PR feedback..."] })] })), status === "launching" && (_jsxs(Box, { flexDirection: "column", children: [
|
|
50
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, width: "100%", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Review" }) }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: status === "error" ? "red" : "yellow", paddingX: 1, width: "100%", children: [branch && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "branch:" }), _jsx(Text, { color: "cyan", bold: true, children: branch })] })), ticketId && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "ticket:" }), _jsx(Text, { color: "blue", bold: true, children: ticketId })] })), _jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "mode:" }), _jsx(Text, { backgroundColor: "yellow", color: "white", bold: true, children: " review " })] })] }), _jsxs(Box, { marginTop: 1, children: [(status === "loading" || status === "fetching") && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { children: [" ", status === "loading" ? "Loading..." : "Fetching ticket, diff, and PR feedback..."] })] })), status === "launching" && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "green", bold: true, children: ["\u2713 Launching ", resolveAgentBinary() === "happy" ? "Claude (through Happy)" : "Claude", "..."] }), _jsxs(Text, { dimColor: true, children: [" ", resolveAgentBinary(), " ", `"<review prompt for ${ticketId}>"`] })] })), status === "error" && (_jsxs(Text, { color: "red", bold: true, children: ["\u2717 ", error] }))] })] }));
|
|
51
51
|
}
|
|
@@ -3,7 +3,9 @@ import { useEffect, useState } from "react";
|
|
|
3
3
|
import { Text, Box } from "ink";
|
|
4
4
|
import Spinner from "ink-spinner";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
import { resolveAIContext, renderAIPrompt, launchAgent, cleanupImages, } from "../../lib/ai.js";
|
|
6
|
+
import { resolveAIContext, renderAIPrompt, launchAgent, resolveAgentBinary, cleanupImages, } from "../../lib/ai.js";
|
|
7
|
+
import { randomUUID } from "crypto";
|
|
8
|
+
import { getSessionId, setSessionId } from "../../lib/git.js";
|
|
7
9
|
export const description = "Launch Claude to work on current ticket";
|
|
8
10
|
export const options = z.object({
|
|
9
11
|
plan: z.boolean().optional().describe("Only create implementation plan"),
|
|
@@ -45,8 +47,22 @@ export default function Work({ options }) {
|
|
|
45
47
|
return;
|
|
46
48
|
setStatus("launching");
|
|
47
49
|
const prompt = renderAIPrompt("work", aiContext, { mode });
|
|
50
|
+
// Get or create a session ID for this ticket
|
|
51
|
+
let sessionId;
|
|
52
|
+
let isResume = false;
|
|
53
|
+
if (aiContext.ticketId) {
|
|
54
|
+
const existing = getSessionId(aiContext.mainRoot, aiContext.ticketId);
|
|
55
|
+
if (existing) {
|
|
56
|
+
sessionId = existing;
|
|
57
|
+
isResume = true;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
sessionId = randomUUID();
|
|
61
|
+
setSessionId(aiContext.mainRoot, aiContext.ticketId, sessionId);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
48
64
|
try {
|
|
49
|
-
const child = launchAgent(prompt, { planMode: mode === "plan" });
|
|
65
|
+
const child = launchAgent(prompt, { planMode: mode === "plan", sessionId, resume: isResume });
|
|
50
66
|
child.on("error", (err) => {
|
|
51
67
|
setStatus("error");
|
|
52
68
|
setError(`Failed to launch agent: ${err.message}`);
|
|
@@ -62,5 +78,5 @@ export default function Work({ options }) {
|
|
|
62
78
|
setError(err instanceof Error ? err.message : "Failed to launch agent");
|
|
63
79
|
}
|
|
64
80
|
}, [status, aiContext, mode]);
|
|
65
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, width: "100%", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Work" }) }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: status === "error" ? "red" : getModeColor(mode), paddingX: 1, width: "100%", children: [branch && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "branch:" }), _jsx(Text, { color: "cyan", bold: true, children: branch })] })), ticketId && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "ticket:" }), _jsx(Text, { color: "blue", bold: true, children: ticketId })] })), _jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "mode:" }), _jsx(Text, { backgroundColor: getModeColor(mode), color: "white", bold: true, children: ` ${getModeLabel(mode)} ` })] })] }), _jsxs(Box, { marginTop: 1, children: [status === "loading" && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Loading..." })] })), status === "fetching" && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Fetching ticket from Linear..." })] })), status === "launching" && (_jsxs(Box, { flexDirection: "column", children: [
|
|
81
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, width: "100%", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Work" }) }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: status === "error" ? "red" : getModeColor(mode), paddingX: 1, width: "100%", children: [branch && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "branch:" }), _jsx(Text, { color: "cyan", bold: true, children: branch })] })), ticketId && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "ticket:" }), _jsx(Text, { color: "blue", bold: true, children: ticketId })] })), _jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "mode:" }), _jsx(Text, { backgroundColor: getModeColor(mode), color: "white", bold: true, children: ` ${getModeLabel(mode)} ` })] })] }), _jsxs(Box, { marginTop: 1, children: [status === "loading" && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Loading..." })] })), status === "fetching" && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Fetching ticket from Linear..." })] })), status === "launching" && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "green", bold: true, children: ["\u2713 Launching ", resolveAgentBinary() === "happy" ? "Claude (through Happy)" : "Claude", "..."] }), _jsxs(Text, { dimColor: true, children: [" ", resolveAgentBinary(), mode === "plan" ? " --permission-mode plan" : "", " ", `"<${getModeLabel(mode)} prompt for ${ticketId}>"`] })] })), status === "error" && (_jsxs(Text, { color: "red", bold: true, children: ["\u2717 ", error] }))] })] }));
|
|
66
82
|
}
|
package/dist/lib/ai.d.ts
CHANGED
|
@@ -36,6 +36,11 @@ export declare function fetchAndRenderPR(branch: string): Promise<string | null>
|
|
|
36
36
|
* Returns rendered markdown.
|
|
37
37
|
*/
|
|
38
38
|
export declare function fetchAndRenderDiff(branch: string): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Resolve which agent binary to use (happy if installed, otherwise claude).
|
|
41
|
+
* Returns the binary name, or null if neither is installed.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveAgentBinary(): string | null;
|
|
39
44
|
/**
|
|
40
45
|
* Launch an interactive agent session with a prompt.
|
|
41
46
|
* Resolves the agent binary (happy > claude), passes prompt directly
|
|
@@ -44,6 +49,8 @@ export declare function fetchAndRenderDiff(branch: string): Promise<string>;
|
|
|
44
49
|
*/
|
|
45
50
|
export declare function launchAgent(prompt: string, opts?: {
|
|
46
51
|
planMode?: boolean;
|
|
52
|
+
sessionId?: string;
|
|
53
|
+
resume?: boolean;
|
|
47
54
|
}): ChildProcess;
|
|
48
55
|
export interface RunAgentResult {
|
|
49
56
|
success: boolean;
|
package/dist/lib/ai.js
CHANGED
|
@@ -108,7 +108,7 @@ export async function fetchAndRenderDiff(branch) {
|
|
|
108
108
|
* Resolve which agent binary to use (happy if installed, otherwise claude).
|
|
109
109
|
* Returns the binary name, or null if neither is installed.
|
|
110
110
|
*/
|
|
111
|
-
function resolveAgentBinary() {
|
|
111
|
+
export function resolveAgentBinary() {
|
|
112
112
|
for (const bin of ["happy", "claude"]) {
|
|
113
113
|
try {
|
|
114
114
|
execSync(`which ${bin}`, { stdio: "ignore" });
|
|
@@ -150,6 +150,14 @@ export function launchAgent(prompt, opts) {
|
|
|
150
150
|
if (opts?.planMode) {
|
|
151
151
|
args.push("--permission-mode", "plan");
|
|
152
152
|
}
|
|
153
|
+
if (opts?.sessionId) {
|
|
154
|
+
if (opts.resume) {
|
|
155
|
+
args.push("--resume", opts.sessionId);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
args.push("--session-id", opts.sessionId);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
153
161
|
args.push("--", promptArg(prompt));
|
|
154
162
|
return spawn(bin, args, { stdio: "inherit" });
|
|
155
163
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DashboardIssue } from "./types.js";
|
|
2
|
+
interface Props {
|
|
3
|
+
issue: DashboardIssue | null;
|
|
4
|
+
scrollOffset: number;
|
|
5
|
+
height: number;
|
|
6
|
+
width: number;
|
|
7
|
+
creatingForTicket: string | null;
|
|
8
|
+
creationLogs: string;
|
|
9
|
+
}
|
|
10
|
+
export default function DetailPanel({ issue, scrollOffset, height, width, creatingForTicket, creationLogs, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
function stateColor(type) {
|
|
4
|
+
switch (type) {
|
|
5
|
+
case "started":
|
|
6
|
+
return "green";
|
|
7
|
+
case "unstarted":
|
|
8
|
+
return "blue";
|
|
9
|
+
case "backlog":
|
|
10
|
+
return "gray";
|
|
11
|
+
default:
|
|
12
|
+
return "yellow";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function parseGitStatus(raw) {
|
|
16
|
+
if (!raw)
|
|
17
|
+
return { staged: 0, unstaged: 0, untracked: 0, files: [] };
|
|
18
|
+
const lines = raw.split("\n").filter(Boolean);
|
|
19
|
+
let staged = 0;
|
|
20
|
+
let unstaged = 0;
|
|
21
|
+
let untracked = 0;
|
|
22
|
+
const files = [];
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
if (line.length < 2)
|
|
25
|
+
continue;
|
|
26
|
+
const x = line[0];
|
|
27
|
+
const y = line[1];
|
|
28
|
+
const file = line.slice(3);
|
|
29
|
+
if (x === "?") {
|
|
30
|
+
untracked++;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
if (x !== " ")
|
|
34
|
+
staged++;
|
|
35
|
+
if (y !== " ")
|
|
36
|
+
unstaged++;
|
|
37
|
+
}
|
|
38
|
+
files.push({ xy: line.slice(0, 2), file });
|
|
39
|
+
}
|
|
40
|
+
return { staged, unstaged, untracked, files };
|
|
41
|
+
}
|
|
42
|
+
function fileColor(xy) {
|
|
43
|
+
const x = xy[0];
|
|
44
|
+
if (x !== " " && x !== "?")
|
|
45
|
+
return "green";
|
|
46
|
+
if (xy.startsWith("??"))
|
|
47
|
+
return "gray";
|
|
48
|
+
return "yellow";
|
|
49
|
+
}
|
|
50
|
+
function buildActions(worktree, pr) {
|
|
51
|
+
const items = [];
|
|
52
|
+
// Work/Resume
|
|
53
|
+
if (worktree?.sessionId) {
|
|
54
|
+
items.push({ key: "↵", label: "Resume", color: "cyan" });
|
|
55
|
+
}
|
|
56
|
+
else if (worktree) {
|
|
57
|
+
items.push({ key: "w", label: "Work", color: "cyan" });
|
|
58
|
+
items.push({ key: "↵", label: "Switch", color: "cyan" });
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
items.push({ key: "w", label: "Work", color: "cyan" });
|
|
62
|
+
}
|
|
63
|
+
// Editor
|
|
64
|
+
if (worktree) {
|
|
65
|
+
items.push({ key: "e", label: "Editor", color: "cyan" });
|
|
66
|
+
}
|
|
67
|
+
// Commit
|
|
68
|
+
if (worktree?.dirty) {
|
|
69
|
+
items.push({ key: "C", label: "Commit", color: "cyan" });
|
|
70
|
+
}
|
|
71
|
+
// PR actions
|
|
72
|
+
if (worktree && !pr) {
|
|
73
|
+
items.push({ key: "c", label: "Create PR", color: "cyan" });
|
|
74
|
+
}
|
|
75
|
+
if (pr) {
|
|
76
|
+
items.push({ key: "f", label: "Fix PR", color: "cyan" });
|
|
77
|
+
items.push({ key: "r", label: "Review", color: "cyan" });
|
|
78
|
+
}
|
|
79
|
+
// Links
|
|
80
|
+
items.push({ key: "o", label: "Linear", color: "gray" });
|
|
81
|
+
if (pr)
|
|
82
|
+
items.push({ key: "p", label: "Open PR", color: "gray" });
|
|
83
|
+
// Destructive
|
|
84
|
+
if (worktree) {
|
|
85
|
+
items.push({ key: "d", label: "Remove", color: "red" });
|
|
86
|
+
}
|
|
87
|
+
return [items];
|
|
88
|
+
}
|
|
89
|
+
export default function DetailPanel({ issue, scrollOffset, height, width, creatingForTicket, creationLogs, }) {
|
|
90
|
+
// Show creation logs when selected issue is being created
|
|
91
|
+
if (issue && issue.issue.identifier === creatingForTicket) {
|
|
92
|
+
const logLines = creationLogs.split("\n");
|
|
93
|
+
const contentRows = height - 1;
|
|
94
|
+
const startIdx = Math.max(0, logLines.length - contentRows);
|
|
95
|
+
const visible = logLines.slice(startIdx, startIdx + contentRows);
|
|
96
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, height: height, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["Setting up worktree for ", creatingForTicket, "..."] }), visible.map((line, i) => (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: line }) }, i)))] }));
|
|
97
|
+
}
|
|
98
|
+
if (!issue) {
|
|
99
|
+
return (_jsx(Box, { width: width, height: height, justifyContent: "center", alignItems: "center", children: _jsx(Text, { dimColor: true, children: "No issue selected" }) }));
|
|
100
|
+
}
|
|
101
|
+
const { issue: li, worktree, pr } = issue;
|
|
102
|
+
const lines = [];
|
|
103
|
+
const rule = "─".repeat(width);
|
|
104
|
+
// ── Hero: identifier + title ──────────────────────────────────────
|
|
105
|
+
lines.push({ text: `${li.identifier} ${li.title}`, bold: true });
|
|
106
|
+
const meta = [];
|
|
107
|
+
meta.push(li.state.name);
|
|
108
|
+
meta.push(li.priorityLabel);
|
|
109
|
+
if (li.labels.length > 0)
|
|
110
|
+
meta.push(li.labels.join(", "));
|
|
111
|
+
lines.push({ text: meta.join(" · "), color: stateColor(li.state.type) });
|
|
112
|
+
// ── Description ───────────────────────────────────────────────────
|
|
113
|
+
if (li.description) {
|
|
114
|
+
lines.push({ text: rule, dim: true });
|
|
115
|
+
lines.push({ text: "" });
|
|
116
|
+
for (const dLine of li.description.trimEnd().split("\n")) {
|
|
117
|
+
lines.push({ text: dLine });
|
|
118
|
+
}
|
|
119
|
+
lines.push({ text: "" });
|
|
120
|
+
}
|
|
121
|
+
// ── Worktree (enhanced) ───────────────────────────────────────────
|
|
122
|
+
lines.push({ text: rule, dim: true });
|
|
123
|
+
lines.push({ text: "WORKTREE", dim: true });
|
|
124
|
+
if (worktree) {
|
|
125
|
+
lines.push({ text: ` ${worktree.branch}` });
|
|
126
|
+
lines.push({ text: ` ${worktree.path}`, dim: true });
|
|
127
|
+
const gs = parseGitStatus(worktree.gitStatus);
|
|
128
|
+
const statusParts = [];
|
|
129
|
+
if (gs.staged > 0)
|
|
130
|
+
statusParts.push(`+${gs.staged} staged`);
|
|
131
|
+
if (gs.unstaged > 0)
|
|
132
|
+
statusParts.push(`~${gs.unstaged} unstaged`);
|
|
133
|
+
if (gs.untracked > 0)
|
|
134
|
+
statusParts.push(`?${gs.untracked} untracked`);
|
|
135
|
+
if (worktree.commitsAhead > 0)
|
|
136
|
+
statusParts.push(`+${worktree.commitsAhead} ahead`);
|
|
137
|
+
if (statusParts.length > 0) {
|
|
138
|
+
lines.push({
|
|
139
|
+
text: ` ${statusParts.join(" ")}`,
|
|
140
|
+
color: worktree.dirty ? "yellow" : "green",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
lines.push({ text: " ✓ clean", color: "green" });
|
|
145
|
+
}
|
|
146
|
+
// Show individual files (up to 8)
|
|
147
|
+
const maxFiles = 8;
|
|
148
|
+
for (let i = 0; i < Math.min(gs.files.length, maxFiles); i++) {
|
|
149
|
+
const f = gs.files[i];
|
|
150
|
+
lines.push({ text: ` ${f.xy} ${f.file}`, color: fileColor(f.xy) });
|
|
151
|
+
}
|
|
152
|
+
if (gs.files.length > maxFiles) {
|
|
153
|
+
lines.push({ text: ` +${gs.files.length - maxFiles} more`, dim: true });
|
|
154
|
+
}
|
|
155
|
+
if (worktree.sessionId) {
|
|
156
|
+
lines.push({ text: ` session: ${worktree.sessionId}`, color: "cyan" });
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
lines.push({ text: " session: none", color: "red" });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
lines.push({ text: " –", dim: true });
|
|
164
|
+
}
|
|
165
|
+
// ── Pull Request ──────────────────────────────────────────────────
|
|
166
|
+
const { checks, reviews } = issue;
|
|
167
|
+
lines.push({ text: rule, dim: true });
|
|
168
|
+
lines.push({ text: "PULL REQUEST", dim: true });
|
|
169
|
+
if (pr) {
|
|
170
|
+
const sc = pr.state === "MERGED" ? "magenta" : pr.state === "OPEN" ? "green" : "red";
|
|
171
|
+
const draft = pr.isDraft ? " draft" : "";
|
|
172
|
+
lines.push({ text: ` #${pr.number} ${pr.state}${draft}`, color: sc });
|
|
173
|
+
if (pr.url) {
|
|
174
|
+
lines.push({ text: ` ${pr.url}`, dim: true });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
lines.push({ text: " –", dim: true });
|
|
179
|
+
}
|
|
180
|
+
// ── Checks ────────────────────────────────────────────────────────
|
|
181
|
+
if (checks && checks.length > 0) {
|
|
182
|
+
const passCount = checks.filter((c) => c.bucket === "pass").length;
|
|
183
|
+
lines.push({ text: rule, dim: true });
|
|
184
|
+
lines.push({ text: `CHECKS ${passCount}/${checks.length} passing`, dim: true });
|
|
185
|
+
for (const check of checks) {
|
|
186
|
+
if (check.bucket === "pass") {
|
|
187
|
+
lines.push({ text: ` ✓ ${check.name}`, color: "green" });
|
|
188
|
+
}
|
|
189
|
+
else if (check.bucket === "fail") {
|
|
190
|
+
const desc = check.description ? ` — ${check.description}` : "";
|
|
191
|
+
lines.push({ text: ` ✗ ${check.name}${desc}`, color: "red" });
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
lines.push({ text: ` ● ${check.name} (pending)`, color: "yellow" });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// ── Reviews ───────────────────────────────────────────────────────
|
|
199
|
+
if (reviews && reviews.length > 0) {
|
|
200
|
+
lines.push({ text: rule, dim: true });
|
|
201
|
+
lines.push({ text: "REVIEWS", dim: true });
|
|
202
|
+
for (const review of reviews) {
|
|
203
|
+
const author = review.author.login;
|
|
204
|
+
const rc = review.state === "APPROVED"
|
|
205
|
+
? "green"
|
|
206
|
+
: review.state === "CHANGES_REQUESTED"
|
|
207
|
+
? "red"
|
|
208
|
+
: "yellow";
|
|
209
|
+
lines.push({ text: ` ${author} ${review.state}`, color: rc });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// ── Build actions footer ──────────────────────────────────────────
|
|
213
|
+
const actionRows = buildActions(worktree, pr);
|
|
214
|
+
// +1 for the separator line
|
|
215
|
+
const actionsHeight = actionRows.length + 1;
|
|
216
|
+
const scrollableHeight = height - actionsHeight;
|
|
217
|
+
// ── Render scrollable content ─────────────────────────────────────
|
|
218
|
+
const totalLines = lines.length;
|
|
219
|
+
const canScroll = totalLines > scrollableHeight;
|
|
220
|
+
const contentRows = canScroll ? scrollableHeight - 2 : scrollableHeight;
|
|
221
|
+
const clampedOffset = Math.min(scrollOffset, Math.max(0, totalLines - contentRows));
|
|
222
|
+
const visible = lines.slice(clampedOffset, clampedOffset + contentRows);
|
|
223
|
+
let scrollArrow = null;
|
|
224
|
+
if (canScroll) {
|
|
225
|
+
const atTop = clampedOffset === 0;
|
|
226
|
+
const atBottom = clampedOffset + contentRows >= totalLines;
|
|
227
|
+
scrollArrow = atTop ? "↓ scroll" : atBottom ? "↑ scroll" : "↑↓ scroll";
|
|
228
|
+
}
|
|
229
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, height: height, children: [visible.map((line, i) => (_jsx(Box, { children: _jsx(Text, { color: line.color, bold: line.bold, dimColor: line.dim, children: line.text || " " }) }, i))), scrollArrow && (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: " " }) })), scrollArrow && (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: scrollArrow }) })), _jsx(Box, { flexGrow: 1 }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: rule }) }), actionRows.map((row, i) => (_jsx(Box, { children: row.map((item, j) => (_jsxs(Text, { children: [" ", _jsx(Text, { color: item.color, bold: true, children: item.key }), _jsxs(Text, { color: item.color === "gray" ? "gray" : "white", children: [" ", item.label] })] }, j))) }, `a-${i}`)))] }));
|
|
230
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ProjectGroup, DashboardIssue } from "./types.js";
|
|
2
|
+
interface Props {
|
|
3
|
+
groups: ProjectGroup[];
|
|
4
|
+
flatIssues: DashboardIssue[];
|
|
5
|
+
selectedIndex: number;
|
|
6
|
+
scrollOffset: number;
|
|
7
|
+
height: number;
|
|
8
|
+
width: number;
|
|
9
|
+
creatingForTicket: string | null;
|
|
10
|
+
deletingForTicket: string | null;
|
|
11
|
+
}
|
|
12
|
+
export default function IssueList({ groups, flatIssues, selectedIndex, scrollOffset, height, width, creatingForTicket, deletingForTicket, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
function stateColor(type, name) {
|
|
4
|
+
const n = name?.toLowerCase();
|
|
5
|
+
if (n === "blocked")
|
|
6
|
+
return "red";
|
|
7
|
+
if (n === "in review")
|
|
8
|
+
return "green";
|
|
9
|
+
if (n === "in progress")
|
|
10
|
+
return "yellow";
|
|
11
|
+
switch (type) {
|
|
12
|
+
case "started":
|
|
13
|
+
return "green";
|
|
14
|
+
case "unstarted":
|
|
15
|
+
return "blue";
|
|
16
|
+
case "backlog":
|
|
17
|
+
return "gray";
|
|
18
|
+
default:
|
|
19
|
+
return "yellow";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function priorityIndicator(priority) {
|
|
23
|
+
switch (priority) {
|
|
24
|
+
case 1:
|
|
25
|
+
return { text: "!!!", color: "red" };
|
|
26
|
+
case 2:
|
|
27
|
+
return { text: "!! ", color: "yellow" };
|
|
28
|
+
case 3:
|
|
29
|
+
return { text: "! ", color: "blue" };
|
|
30
|
+
case 4:
|
|
31
|
+
return { text: "· ", color: "gray" };
|
|
32
|
+
default:
|
|
33
|
+
return { text: " ", color: "gray" };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function checksIndicator(checks) {
|
|
37
|
+
if (!checks || checks.length === 0)
|
|
38
|
+
return { text: "-", color: "gray" };
|
|
39
|
+
if (checks.some((c) => c.bucket === "fail"))
|
|
40
|
+
return { text: "✗", color: "red" };
|
|
41
|
+
if (checks.every((c) => c.bucket === "pass"))
|
|
42
|
+
return { text: "✓", color: "green" };
|
|
43
|
+
return { text: "●", color: "yellow" };
|
|
44
|
+
}
|
|
45
|
+
function prIndicator(pr) {
|
|
46
|
+
if (!pr)
|
|
47
|
+
return { text: "-", color: "gray" };
|
|
48
|
+
const label = `#${pr.number}`;
|
|
49
|
+
if (pr.state === "MERGED")
|
|
50
|
+
return { text: label, color: "magenta" };
|
|
51
|
+
if (pr.state === "CLOSED")
|
|
52
|
+
return { text: label, color: "red" };
|
|
53
|
+
if (pr.isDraft)
|
|
54
|
+
return { text: label, color: "gray" };
|
|
55
|
+
return { text: label, color: "green" };
|
|
56
|
+
}
|
|
57
|
+
function sessionIndicator(wt, isCreating, isDeleting) {
|
|
58
|
+
if (isDeleting)
|
|
59
|
+
return { text: " deleting", color: "red" };
|
|
60
|
+
if (isCreating)
|
|
61
|
+
return { text: " creating", color: "yellow" };
|
|
62
|
+
if (!wt)
|
|
63
|
+
return { text: " -", color: "gray" };
|
|
64
|
+
if (wt.sessionId)
|
|
65
|
+
return { text: " " + wt.sessionId.slice(0, 8), color: "cyan" };
|
|
66
|
+
return { text: " none", color: "red" };
|
|
67
|
+
}
|
|
68
|
+
function buildRows(groups, flatIssues) {
|
|
69
|
+
const rows = [{ kind: "columns" }];
|
|
70
|
+
// Build a map from issue identifier to flat index
|
|
71
|
+
const indexMap = new Map();
|
|
72
|
+
flatIssues.forEach((di, i) => indexMap.set(di.issue.identifier, i));
|
|
73
|
+
for (const group of groups) {
|
|
74
|
+
const totalIssues = group.statusGroups.reduce((sum, sg) => sum + sg.issues.length, 0);
|
|
75
|
+
rows.push({ kind: "header", name: group.name, count: totalIssues });
|
|
76
|
+
for (const sg of group.statusGroups) {
|
|
77
|
+
rows.push({ kind: "status-header", name: sg.name, type: sg.type, count: sg.issues.length });
|
|
78
|
+
for (const di of sg.issues) {
|
|
79
|
+
rows.push({ kind: "issue", issue: di, flatIndex: indexMap.get(di.issue.identifier) ?? -1 });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return rows;
|
|
84
|
+
}
|
|
85
|
+
const FOOTER_HEIGHT = 2;
|
|
86
|
+
export default function IssueList({ groups, flatIssues, selectedIndex, scrollOffset, height, width, creatingForTicket, deletingForTicket, }) {
|
|
87
|
+
const rows = buildRows(groups, flatIssues);
|
|
88
|
+
const listHeight = height - FOOTER_HEIGHT;
|
|
89
|
+
const visible = rows.slice(scrollOffset, scrollOffset + listHeight);
|
|
90
|
+
// 2 cursor + 2 dot + 4 priority + 11 id + title + 9 session + 1 space + 6 pr + 1 space + 2 checks
|
|
91
|
+
const prColWidth = 6;
|
|
92
|
+
const checksColWidth = 2;
|
|
93
|
+
const sessionColWidth = 9;
|
|
94
|
+
const priorityColWidth = 4;
|
|
95
|
+
const fixedWidth = 2 + 2 + priorityColWidth + 11 + sessionColWidth + 1 + prColWidth + 1 + checksColWidth;
|
|
96
|
+
const titleMaxWidth = Math.max(width - fixedWidth, 10);
|
|
97
|
+
const footerRule = "─".repeat(width);
|
|
98
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, height: height, children: [_jsx(Box, { flexDirection: "column", height: listHeight, children: visible.map((row, i) => {
|
|
99
|
+
if (row.kind === "columns") {
|
|
100
|
+
const labelPad = 14 + priorityColWidth + titleMaxWidth;
|
|
101
|
+
return (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "".padEnd(labelPad) }), _jsx(Text, { dimColor: true, children: "session".padStart(sessionColWidth) }), _jsx(Text, { dimColor: true, children: " " }), _jsx(Text, { dimColor: true, children: "pr".padStart(prColWidth) }), _jsx(Text, { dimColor: true, children: " " }), _jsx(Text, { dimColor: true, children: "ci".padStart(checksColWidth) })] }, "col-header"));
|
|
102
|
+
}
|
|
103
|
+
if (row.kind === "header") {
|
|
104
|
+
return (_jsx(Box, { children: _jsxs(Text, { dimColor: true, bold: true, children: ["── ", row.name, " (", row.count, ")", " ──"] }) }, `h-${i}`));
|
|
105
|
+
}
|
|
106
|
+
if (row.kind === "status-header") {
|
|
107
|
+
return (_jsx(Box, { children: _jsxs(Text, { color: stateColor(row.type, row.name), dimColor: true, children: [" ", row.name, " (", row.count, ")"] }) }, `sh-${i}`));
|
|
108
|
+
}
|
|
109
|
+
const { issue, flatIndex } = row;
|
|
110
|
+
const selected = flatIndex === selectedIndex;
|
|
111
|
+
const di = issue;
|
|
112
|
+
const sc = stateColor(di.issue.state.type, di.issue.state.name);
|
|
113
|
+
const isCreating = di.issue.identifier === creatingForTicket;
|
|
114
|
+
const isDeleting = di.issue.identifier === deletingForTicket;
|
|
115
|
+
const sess = sessionIndicator(di.worktree, isCreating, isDeleting);
|
|
116
|
+
const ci = checksIndicator(di.checks);
|
|
117
|
+
const pr = prIndicator(di.pr);
|
|
118
|
+
const prio = priorityIndicator(di.issue.priority);
|
|
119
|
+
const cursor = selected ? ">" : " ";
|
|
120
|
+
const title = di.issue.title.length > titleMaxWidth
|
|
121
|
+
? di.issue.title.slice(0, titleMaxWidth - 1) + "…"
|
|
122
|
+
: di.issue.title;
|
|
123
|
+
const bg = selected ? "#1e3a5f" : undefined;
|
|
124
|
+
return (_jsxs(Box, { width: width, children: [_jsxs(Text, { backgroundColor: bg, color: selected ? "cyan" : undefined, bold: selected, children: [cursor, " "] }), _jsx(Text, { backgroundColor: bg, color: sc, children: "\u25CF" }), _jsxs(Text, { backgroundColor: bg, color: prio.color, children: [" ", prio.text] }), _jsx(Text, { backgroundColor: bg, color: selected ? "cyan" : undefined, bold: selected, children: di.issue.identifier.padEnd(10) }), _jsx(Text, { backgroundColor: bg, color: selected ? "white" : undefined, bold: selected, children: title.padEnd(titleMaxWidth) }), _jsx(Text, { backgroundColor: bg, color: selected ? (sess.color === "gray" ? "gray" : sess.color) : sess.color, children: sess.text.padStart(sessionColWidth) }), _jsx(Text, { backgroundColor: bg, children: " " }), _jsx(Text, { backgroundColor: bg, color: selected ? (pr.color === "gray" ? "gray" : pr.color) : pr.color, children: pr.text.padStart(prColWidth) }), _jsx(Text, { backgroundColor: bg, children: " " }), _jsx(Text, { backgroundColor: bg, color: selected ? (ci.color === "gray" ? "gray" : ci.color) : ci.color, children: ci.text.padStart(checksColWidth) })] }, di.issue.identifier));
|
|
125
|
+
}) }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: footerRule }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", bold: true, children: "j/k" }), _jsx(Text, { color: "white", children: " Navigate" }), " ", _jsx(Text, { color: "cyan", bold: true, children: "Shift + \u2191\u2193" }), _jsx(Text, { color: "white", children: " Scroll detail" }), " ", _jsx(Text, { color: "cyan", bold: true, children: "E" }), _jsx(Text, { color: "white", children: " Workspace" }), " ", _jsx(Text, { color: "cyan", bold: true, children: "R" }), _jsx(Text, { color: "white", children: " Refresh" }), " ", _jsx(Text, { color: "cyan", bold: true, children: "q" }), _jsx(Text, { color: "white", children: " Quit" })] })] })] }));
|
|
126
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CommitPhase, PrCreatePhase, DashboardAction } from "./types.js";
|
|
2
|
+
interface CommitOverlayProps {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
branch: string | null;
|
|
6
|
+
ticketId: string | null;
|
|
7
|
+
gitStatus: string;
|
|
8
|
+
phase: CommitPhase;
|
|
9
|
+
message: string;
|
|
10
|
+
error: string | null;
|
|
11
|
+
dispatch: React.Dispatch<DashboardAction>;
|
|
12
|
+
onSubmit: (value: string) => void;
|
|
13
|
+
}
|
|
14
|
+
export declare function CommitOverlay({ width, height, branch, ticketId, gitStatus, phase, message, error, dispatch, onSubmit, }: CommitOverlayProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
interface PrCreateOverlayProps {
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
branch: string | null;
|
|
19
|
+
ticketId: string | null;
|
|
20
|
+
phase: PrCreatePhase;
|
|
21
|
+
error: string | null;
|
|
22
|
+
url: string | null;
|
|
23
|
+
}
|
|
24
|
+
export declare function PrCreateOverlay({ width, height, branch, ticketId, phase, error, url, }: PrCreateOverlayProps): import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import Spinner from "ink-spinner";
|
|
4
|
+
import TextInput from "ink-text-input";
|
|
5
|
+
export function CommitOverlay({ width, height, branch, ticketId, gitStatus, phase, message, error, dispatch, onSubmit, }) {
|
|
6
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, height: height, children: [_jsx(Text, { bold: true, color: "cyan", children: "Commit & Push" }), _jsx(Text, { dimColor: true, children: "─".repeat(Math.min(width, 50)) }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "branch: " }), _jsx(Text, { children: branch })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "ticket: " }), _jsx(Text, { children: ticketId })] }), _jsx(Text, { children: " " }), gitStatus ? (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: "Changes:" }), gitStatus
|
|
7
|
+
.split("\n")
|
|
8
|
+
.slice(0, 8)
|
|
9
|
+
.map((line, i) => {
|
|
10
|
+
let color;
|
|
11
|
+
if (line.length >= 2 && line[0] !== " " && line[0] !== "?") {
|
|
12
|
+
color = "green";
|
|
13
|
+
}
|
|
14
|
+
else if (line.startsWith("??")) {
|
|
15
|
+
color = "gray";
|
|
16
|
+
}
|
|
17
|
+
else if (line.startsWith(" ")) {
|
|
18
|
+
color = "yellow";
|
|
19
|
+
}
|
|
20
|
+
return (_jsxs(Text, { color: color, children: [" ", line] }, i));
|
|
21
|
+
}), gitStatus.split("\n").length > 8 && (_jsxs(Text, { dimColor: true, children: [" +", gitStatus.split("\n").length - 8, " more"] }))] })) : null, _jsx(Text, { children: " " }), phase === "confirm-stage" && (_jsxs(Text, { children: ["Stage all changes?", " ", _jsx(Text, { color: "cyan", bold: true, children: "y" }), "/", _jsx(Text, { color: "cyan", bold: true, children: "n" })] })), phase === "awaiting-message" && (_jsxs(Box, { children: [_jsx(Text, { children: "Message: " }), _jsx(TextInput, { value: message, onChange: (v) => dispatch({ type: "COMMIT_MESSAGE", message: v }), onSubmit: onSubmit })] })), phase === "committing" && (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), " ", "Committing..."] })), phase === "pushing" && (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), " ", "Pushing..."] })), phase === "done" && (_jsx(Text, { color: "green", bold: true, children: "Committed and pushed!" })), phase === "error" && _jsx(Text, { color: "red", children: error }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "ESC to cancel" })] }));
|
|
22
|
+
}
|
|
23
|
+
export function PrCreateOverlay({ width, height, branch, ticketId, phase, error, url, }) {
|
|
24
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, height: height, children: [_jsx(Text, { bold: true, color: "cyan", children: "Create Pull Request" }), _jsx(Text, { dimColor: true, children: "─".repeat(Math.min(width, 50)) }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "branch: " }), _jsx(Text, { children: branch })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "ticket: " }), _jsx(Text, { children: ticketId })] }), _jsx(Text, { children: " " }), phase === "choose-mode" && (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "How do you want to create this PR?" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", bold: true, children: "f" }), " ", "Fill \u2014 auto-fill title & body from commits"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", bold: true, children: "w" }), " ", "Web \u2014 open in browser to edit manually"] })] })), phase === "pushing" && (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), " ", "Pushing branch..."] })), phase === "creating" && (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), " ", "Creating PR..."] })), phase === "done" && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "green", bold: true, children: "PR created!" }), url ? _jsx(Text, { dimColor: true, children: url }) : null] })), phase === "error" && _jsx(Text, { color: "red", children: error }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "ESC to cancel" })] }));
|
|
25
|
+
}
|