Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
# Curse 🕯️

![Made with TypeScript](https://img.shields.io/badge/Made%20with-TypeScript-3178C6?style=flat-square&logo=typescript&logoColor=white)
![Runs on Bun](https://img.shields.io/badge/Runs%20on-Bun-f9f1e1?style=flat-square&logo=bun&logoColor=black)
![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg?style=flat-square)
![Females Attracted](https://img.shields.io/badge/Females%20Attracted-0-red?style=flat-square)
# Curse

Curse is a dead simple Terminal UI for running processes, configured through a single `curse.toml` file.

<img width="1146" height="885" alt="スクリーンショット 2025-09-27 午後7 49 04" src="https://github.com/user-attachments/assets/580b87e6-8823-4c18-b05e-6de15b810d2c" />
<img alt="Curse TUI screenshot" src="./curse.png" />

## Installation & Setup

Expand Down
29 changes: 17 additions & 12 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added curse.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
"type": "module",
"module": "src/index.ts",
"dependencies": {
"@types/react": "19.1.5",
"arktype": "^2.1.22",
"gunshi": "^0.26.3",
"ink": "^6.6.0",
"ink": "6.6.0",
"minisearch": "^7.1.2",
"react": "^19.1.1",
"react": "19.1.1",
"strip-ansi": "^7.1.2",
"toml": "^3.0.0"
},
Expand Down
15 changes: 8 additions & 7 deletions src/lib/Colors.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
export const Colors = {
primary: "#8b5cf6", // violet
white: "#ffffff",
darkGray: "#4b5563",
mutedCyan: "#5f8a8b",
indigo: "#6366f1",
purple: "#a78bfa",
green: "#10b981",
brightGreen: "#34d399",
brightPink: "#ec4899",
brightOrange: "#f97316",
gray: "#6b7280",
lightGray: "#e5e7eb",
mutedGreen: "#6b9e7e",
softGreen: "#7dab8e",
silver: "#9ca3af",
amber: "#d4a054",
mutedRed: "#b07070",
};
10 changes: 4 additions & 6 deletions src/ui/View.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,17 @@ function View(props: { config: CurseConfig }) {
{displayMode === "normal" && (
<Box flexDirection="row" justifyContent="space-between">
<Box flexDirection="row">
<Text bold color={Colors.primary}>
Curse 🕯
<Text bold color={Colors.white}>
Curse
</Text>
<Text color={Colors.darkGray}> v{version}</Text>
</Box>
{status === ProgramStatus.Quitting && (
<Text color={Colors.brightOrange}>Quitting...</Text>
)}
{status === ProgramStatus.Quitting && <Text color={Colors.amber}>Quitting...</Text>}
</Box>
)}
{displayMode === "normal" && (
<Box flexDirection="row">
<Text color={Colors.primary}>Config: </Text>
<Text color={Colors.white}>Config: </Text>
<Text>{props.config.fileName}</Text>
</Box>
)}
Expand Down
16 changes: 8 additions & 8 deletions src/ui/components/AggregatedSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { useRenderTick } from "../../hooks/useRenderTick";
import { Colors } from "../../lib/Colors";

const statusColorMap: Record<string, string> = {
running: Colors.green,
starting: Colors.indigo,
running: Colors.mutedGreen,
starting: Colors.gray,
pending: Colors.darkGray,
error: "red",
error: Colors.mutedRed,
success: Colors.darkGray,
killing: Colors.brightOrange,
killed: Colors.brightOrange,
killing: Colors.amber,
killed: Colors.amber,
};

export function AggregatedSummary(props: { numberPrefix: string; waitingForSecondG: boolean }) {
Expand Down Expand Up @@ -65,12 +65,12 @@ export function AggregatedSummary(props: { numberPrefix: string; waitingForSecon
</Box>
)}
<Box>
<Text backgroundColor={Colors.indigo} color="white" bold>
<Text backgroundColor={Colors.gray} color="white" bold>
{" "}
{selectedDetail}{" "}
</Text>
{props.numberPrefix && <Text color={Colors.brightPink}> [{props.numberPrefix}]</Text>}
{props.waitingForSecondG && <Text color={Colors.brightGreen}> [g]</Text>}
{props.numberPrefix && <Text color={Colors.silver}> [{props.numberPrefix}]</Text>}
{props.waitingForSecondG && <Text color={Colors.softGreen}> [g]</Text>}
</Box>
</Box>
);
Expand Down
5 changes: 4 additions & 1 deletion src/ui/components/LogTailPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ export function LogTailPreview(props: { height: number }) {
) : (
logs.map((log: string, index: number) => (
<Box key={index}>
<Text color={log.includes("stderr") ? "red" : Colors.purple} wrap="truncate">
<Text
color={log.includes("stderr") ? Colors.mutedRed : Colors.lightGray}
wrap="truncate"
>
{preprocessLog(log)}
</Text>
</Box>
Expand Down
46 changes: 22 additions & 24 deletions src/ui/views/LogPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -588,9 +588,9 @@ function LogTable(props: {
// Helper functions for text colors
const getTextColor = (isCursor: boolean, isSelected: boolean, log: string) => {
if (isCursor) return "white";
if (isSelected) return Colors.purple;
if (log.includes("stderr")) return "red";
return Colors.purple;
if (isSelected) return Colors.lightGray;
if (log.includes("stderr")) return Colors.mutedRed;
return Colors.lightGray;
};

const getPartTextColor = (
Expand All @@ -599,7 +599,7 @@ function LogTable(props: {
isSelected: boolean,
log: string,
) => {
if (isHighlight) return Colors.brightOrange;
if (isHighlight) return Colors.amber;
return getTextColor(isCursor, isSelected, log);
};

Expand All @@ -614,20 +614,18 @@ function LogTable(props: {
<Box justifyContent="center" borderBottom borderColor={Colors.darkGray}>
<Text color={Colors.darkGray}>
Autoscroll:
<Text color={autoScroll ? Colors.brightGreen : Colors.darkGray}>
<Text color={autoScroll ? Colors.softGreen : Colors.darkGray}>
{autoScroll ? "on" : "off"}
</Text>
{positionLost && (
<Text color={Colors.brightOrange}> (position lost, returned to tail)</Text>
)}
{numberPrefix && <Text color={Colors.brightPink}> [{numberPrefix}]</Text>}
{waitingForSecondG && <Text color={Colors.brightGreen}> [g]</Text>}
{isSelectMode && <Text color={Colors.brightOrange}> [SELECT]</Text>}
{isSearchMode && <Text color={Colors.brightGreen}> &lt;/{searchQuery}&gt;</Text>}
{positionLost && <Text color={Colors.amber}> (position lost, returned to tail)</Text>}
{numberPrefix && <Text color={Colors.silver}> [{numberPrefix}]</Text>}
{waitingForSecondG && <Text color={Colors.softGreen}> [g]</Text>}
{isSelectMode && <Text color={Colors.amber}> [SELECT]</Text>}
{isSearchMode && <Text color={Colors.softGreen}> &lt;/{searchQuery}&gt;</Text>}
{!isSearchMode && appliedSearchQuery && (
<Text color={Colors.brightGreen}> &lt;/{appliedSearchQuery}&gt;</Text>
<Text color={Colors.softGreen}> &lt;/{appliedSearchQuery}&gt;</Text>
)}
{showCopyIndicator && <Text color={Colors.brightGreen}> ✓ {copyIndicatorText}</Text>}
{showCopyIndicator && <Text color={Colors.softGreen}> ✓ {copyIndicatorText}</Text>}
</Text>
</Box>
{logs.map((log, index) => {
Expand All @@ -637,7 +635,7 @@ function LogTable(props: {
// Determine background color: cursor takes priority, then selection
let backgroundColor;
if (isCursor) {
backgroundColor = Colors.indigo; // Cursor color
backgroundColor = Colors.gray; // Cursor color
} else if (isSelected) {
backgroundColor = Colors.darkGray; // Charcoal for selection
}
Expand Down Expand Up @@ -800,23 +798,23 @@ export function LogPage() {
<>
<Box justifyContent="center">
<Text>
<Text color={Colors.green}>Logs</Text>
<Text color={Colors.green}>(</Text>
<Text color={Colors.brightPink} bold>
<Text color={Colors.mutedGreen}>Logs</Text>
<Text color={Colors.mutedGreen}>(</Text>
<Text color={Colors.silver} bold>
{selectedProcess.name}
</Text>
<Text color={Colors.green}>)</Text>
<Text color={Colors.green}>[</Text>
<Text color={Colors.brightGreen}>tail</Text>
<Text color={Colors.green}>]</Text>
<Text color={Colors.mutedGreen}>)</Text>
<Text color={Colors.mutedGreen}>[</Text>
<Text color={Colors.softGreen}>tail</Text>
<Text color={Colors.mutedGreen}>]</Text>
</Text>
</Box>
{isSearchMode && (
<Box paddingX={1}>
<Text color={Colors.brightGreen}>Search: </Text>
<Text color={Colors.softGreen}>Search: </Text>
<Text color="white">
{searchQuery}
<Text color={Colors.brightGreen}>█</Text>
<Text color={Colors.softGreen}>█</Text>
</Text>
</Box>
)}
Expand Down
26 changes: 13 additions & 13 deletions src/ui/views/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ function getReadinessDisplay(process: Process): {

const color =
char === "x" || char === "✗"
? "red"
? Colors.mutedRed
: char === "✓"
? Colors.green
? Colors.mutedGreen
: char === "-"
? Colors.darkGray
: Colors.indigo;
: Colors.gray;

return { char, color };
}
Expand Down Expand Up @@ -76,8 +76,8 @@ function ProcessTable(props: {
<Text bold dimColor>
READY
</Text>
{props.numberPrefix && <Text color={Colors.brightPink}> [{props.numberPrefix}]</Text>}
{props.waitingForSecondG && <Text color={Colors.brightGreen}> [g]</Text>}
{props.numberPrefix && <Text color={Colors.silver}> [{props.numberPrefix}]</Text>}
{props.waitingForSecondG && <Text color={Colors.softGreen}> [g]</Text>}
</Box>
</Box>
{processes.map((process: Process, index: number) => {
Expand All @@ -93,13 +93,13 @@ function ProcessTable(props: {
? Colors.darkGray
: isShutdownHookPending
? Colors.mutedCyan
: Colors.indigo;
: Colors.gray;
return (
<Box
key={process.name}
flexDirection="row"
paddingX={1}
backgroundColor={isSelected ? Colors.indigo : undefined}
backgroundColor={isSelected ? Colors.gray : undefined}
>
<Box width={compactNameColumnWidth}>
<Text color={textColor} bold={isSelected}>
Expand Down Expand Up @@ -157,8 +157,8 @@ function ProcessTable(props: {
</Box>
<Box width={8} marginLeft={2}>
<Text bold>CPU</Text>
{props.numberPrefix && <Text color={Colors.brightPink}> [{props.numberPrefix}]</Text>}
{props.waitingForSecondG && <Text color={Colors.brightGreen}> [g]</Text>}
{props.numberPrefix && <Text color={Colors.silver}> [{props.numberPrefix}]</Text>}
{props.waitingForSecondG && <Text color={Colors.softGreen}> [g]</Text>}
</Box>
</Box>
{processes.map((process: Process, index: number) => {
Expand All @@ -174,13 +174,13 @@ function ProcessTable(props: {
? Colors.darkGray
: isShutdownHookPending
? Colors.mutedCyan
: Colors.indigo;
: Colors.gray;
return (
<Box
key={process.name}
flexDirection="row"
paddingX={1}
backgroundColor={isSelected ? Colors.indigo : undefined}
backgroundColor={isSelected ? Colors.gray : undefined}
>
<Box width={nameColumnWidth}>
<Text color={textColor} bold={isSelected}>
Expand Down Expand Up @@ -237,7 +237,7 @@ function ProcessTable(props: {
: isSuccess
? Colors.darkGray
: process.profile?.memoryUsageMB !== undefined
? Colors.indigo
? Colors.gray
: Colors.darkGray
}
bold={isSelected}
Expand All @@ -255,7 +255,7 @@ function ProcessTable(props: {
: isSuccess
? Colors.darkGray
: process.profile?.cpuUsagePercent !== undefined
? Colors.indigo
? Colors.gray
: Colors.darkGray
}
bold={isSelected}
Expand Down
Loading