6 files changed,
+345,
-0
+1,
-0
1@@ -0,0 +1 @@
2+result/
A
LICENSE
+21,
-0
1@@ -0,0 +1,21 @@
2+MIT License
3+
4+Copyright (c) 2026 [email protected]
5+
6+Permission is hereby granted, free of charge, to any person obtaining a copy
7+of this software and associated documentation files (the "Software"), to deal
8+in the Software without restriction, including without limitation the rights
9+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+copies of the Software, and to permit persons to whom the Software is
11+furnished to do so, subject to the following conditions:
12+
13+The above copyright notice and this permission notice shall be included in all
14+copies or substantial portions of the Software.
15+
16+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+SOFTWARE.
+61,
-0
1@@ -0,0 +1,61 @@
2+# concurrently
3+
4+Run multiple commands with multiplexed, colorized output.
5+
6+## Usage
7+
8+```bash
9+concurrently NAME1 'COMMAND1' [NAME2 'COMMAND2' ...]
10+```
11+
12+## Example
13+
14+```bash
15+concurrently Vite 'pnpm exec vite' Go 'find . -name "*.go" | entr go build'
16+```
17+
18+Output:
19+```
20+[Vite] Dev server running on port 5173...
21+[Go] Compiled output
22+```
23+
24+## Features
25+
26+- Multiple commands run in parallel
27+- Each process output prefixed with `[NAME]` in a distinct color
28+- Colors cycle through 8 vibrant colors for dark terminals
29+- SIGINT (Ctrl+C) terminates all processes cleanly
30+- Line-buffered output for real-time display
31+
32+## Installation
33+
34+### Using Nix Flakes
35+
36+```bash
37+nix run git+https://github.com/yourusername/concurrently -- Vite 'npm run dev'
38+```
39+
40+Or add to your `flake.nix`:
41+
42+```nix
43+{
44+ inputs.concurrently.url = "git+https://github.com/yourusername/concurrently";
45+
46+ outputs = { self, nixpkgs, concurrently }: {
47+ # Use in your packages
48+ };
49+}
50+```
51+
52+### Manual Installation
53+
54+Copy the `concurrently` script to your `$PATH`.
55+
56+## Color Palette
57+
58+Colors cycle through: Cyan, Green, Yellow, Magenta, Blue, Red, Orange, White
59+
60+## License
61+
62+MIT
+150,
-0
1@@ -0,0 +1,150 @@
2+#!/usr/bin/env bash
3+#
4+# concurrently - Run multiple commands with multiplexed output
5+# Usage: concurrently NAME1 'COMMAND1' [NAME2 'COMMAND2' ...]
6+#
7+# Runs all commands in parallel with output prefixed by [NAME].
8+# Press Ctrl+C to terminate all processes.
9+#
10+
11+set -euo pipefail
12+
13+# Color palette for dark terminal themes
14+# Format: \e[38;2;R;G;Bm (256-color true color)
15+declare -a COLORS=(
16+ '\e[38;2;0;255;255m' # Cyan
17+ '\e[38;2;0;255;0m' # Green
18+ '\e[38;2;255;255;0m' # Yellow
19+ '\e[38;2;255;0;255m' # Magenta
20+ '\e[38;2;0;160;255m' # Blue
21+ '\e[38;2;255;100;100m' # Red (soft)
22+ '\e[38;2;255;165;0m' # Orange
23+)
24+readonly RESET='\e[0m'
25+readonly NUM_COLORS=${#COLORS[@]}
26+
27+# Global exit code for trap
28+EXIT_CODE=0
29+
30+# Validate we have at least one name-command pair
31+if [[ $# -lt 2 ]]; then
32+ echo "Usage: concurrently NAME1 'COMMAND1' [NAME2 'COMMAND2' ...]" >&2
33+ exit 1
34+fi
35+
36+# Validate we have an even number of arguments (name-cmd pairs)
37+if [[ $(( $# % 2 )) -ne 0 ]]; then
38+ echo "Error: Arguments must be name-command pairs" >&2
39+ exit 1
40+fi
41+
42+# Create temp directory for FIFOs
43+readonly TMPDIR=$(mktemp -d /tmp/concurrently.XXXXXX)
44+readonly FIFODIR="$TMPDIR/fifos"
45+readonly PIDDIR="$TMPDIR/pids"
46+mkdir -p "$FIFODIR" "$PIDDIR"
47+
48+# Track PIDs
49+declare -a JOB_PIDS=()
50+declare -a READER_PIDS=()
51+declare -a JOB_NAMES=()
52+
53+# Cleanup function
54+cleanup() {
55+ local exit_code=${1:-0}
56+
57+ # Kill all reader processes
58+ for pid in "${READER_PIDS[@]}"; do
59+ kill "$pid" 2>/dev/null || true
60+ done
61+
62+ # Kill all job processes
63+ for pid in "${JOB_PIDS[@]}"; do
64+ if kill -0 "$pid" 2>/dev/null; then
65+ kill -- -"$pid" 2>/dev/null || kill "$pid" 2>/dev/null || true
66+ fi
67+ done
68+
69+ # Remove temp directory
70+ rm -rf "$TMPDIR"
71+
72+ exit "$exit_code"
73+}
74+
75+trap 'cleanup $EXIT_CODE' INT TERM EXIT
76+
77+# Start reader process for a FIFO
78+# Reads lines from FIFO and prefixes with colored [NAME]
79+start_reader() {
80+ local name="$1"
81+ local fifo="$2"
82+ local color="$3"
83+
84+ (
85+ while IFS= read -r line; do
86+ printf "$color[%s]$RESET %s\n" "$name" "$line"
87+ done < "$fifo"
88+ ) &
89+
90+ READER_PIDS+=($!)
91+}
92+
93+# Start all jobs
94+job_index=0
95+while [[ $# -gt 0 ]]; do
96+ name="$1"
97+ cmd="$2"
98+ shift 2
99+
100+ # Validate name
101+ if [[ "$name" == */* ]]; then
102+ echo "Error: Name cannot contain '/': $name" >&2
103+ EXIT_CODE=1
104+ cleanup 1
105+ fi
106+
107+ # Check for duplicates
108+ for existing_name in "${JOB_NAMES[@]}"; do
109+ if [[ "$existing_name" == "$name" ]]; then
110+ echo "Error: Duplicate name: $name" >&2
111+ EXIT_CODE=1
112+ cleanup 1
113+ fi
114+ done
115+
116+ JOB_NAMES+=("$name")
117+ fifo="$FIFODIR/$name"
118+
119+ # Create FIFO
120+ mkfifo "$fifo"
121+
122+ # Assign color (modulo to cycle through palette)
123+ color_index=$((job_index % NUM_COLORS))
124+ color="${COLORS[$color_index]}"
125+
126+ # Start reader for this FIFO with assigned color
127+ start_reader "$name" "$fifo" "$color"
128+
129+ # Start job writing to FIFO (line buffered)
130+ (
131+ exec stdbuf -oL bash -c "$cmd" > "$fifo" 2>&1
132+ ) &
133+
134+ JOB_PIDS+=($!)
135+ job_index=$((job_index + 1))
136+done
137+
138+# Wait for all jobs to exit
139+for i in "${!JOB_NAMES[@]}"; do
140+ name="${JOB_NAMES[$i]}"
141+ pid="${JOB_PIDS[$i]}"
142+
143+ # Wait for this specific job
144+ if kill -0 "$pid" 2>/dev/null; then
145+ wait "$pid" 2>/dev/null || true
146+ echo "[$name] (process exited)"
147+ fi
148+done
149+
150+# Give readers time to flush
151+cleanup 0
+61,
-0
1@@ -0,0 +1,61 @@
2+{
3+ "nodes": {
4+ "flake-utils": {
5+ "inputs": {
6+ "systems": "systems"
7+ },
8+ "locked": {
9+ "lastModified": 1731533236,
10+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
11+ "owner": "numtide",
12+ "repo": "flake-utils",
13+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
14+ "type": "github"
15+ },
16+ "original": {
17+ "owner": "numtide",
18+ "repo": "flake-utils",
19+ "type": "github"
20+ }
21+ },
22+ "nixpkgs": {
23+ "locked": {
24+ "lastModified": 1776949667,
25+ "narHash": "sha256-GMSVw35Q+294GlrTUKlx087E31z7KurReQ1YHSKp5iw=",
26+ "owner": "NixOS",
27+ "repo": "nixpkgs",
28+ "rev": "01fbdeef22b76df85ea168fbfe1bfd9e63681b30",
29+ "type": "github"
30+ },
31+ "original": {
32+ "owner": "NixOS",
33+ "ref": "nixpkgs-unstable",
34+ "repo": "nixpkgs",
35+ "type": "github"
36+ }
37+ },
38+ "root": {
39+ "inputs": {
40+ "flake-utils": "flake-utils",
41+ "nixpkgs": "nixpkgs"
42+ }
43+ },
44+ "systems": {
45+ "locked": {
46+ "lastModified": 1681028828,
47+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
48+ "owner": "nix-systems",
49+ "repo": "default",
50+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
51+ "type": "github"
52+ },
53+ "original": {
54+ "owner": "nix-systems",
55+ "repo": "default",
56+ "type": "github"
57+ }
58+ }
59+ },
60+ "root": "root",
61+ "version": 7
62+}
+51,
-0
1@@ -0,0 +1,51 @@
2+{
3+ description = "concurrently - Run multiple commands with multiplexed output";
4+
5+ inputs = {
6+ nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
7+ flake-utils.url = "github:numtide/flake-utils";
8+ };
9+
10+ outputs = { self, nixpkgs, flake-utils }:
11+ flake-utils.lib.eachDefaultSystem (system:
12+ let
13+ pkgs = nixpkgs.legacyPackages.${system};
14+ in
15+ {
16+ packages = {
17+ default = pkgs.stdenvNoCC.mkDerivation {
18+ pname = "concurrently";
19+ version = "1.0.0";
20+
21+ src = ./.;
22+
23+ dontBuild = true;
24+ dontConfigure = true;
25+
26+ installPhase = ''
27+ mkdir -p $out/bin
28+ cp concurrently $out/bin/concurrently
29+ chmod +x $out/bin/concurrently
30+
31+ # Patch shebang to use nixpkgs bash
32+ substituteInPlace $out/bin/concurrently \
33+ --replace "#!/usr/bin/env bash" "#!${pkgs.bash}/bin/bash"
34+ '';
35+
36+ meta = with pkgs.lib; {
37+ description = "Run multiple commands with multiplexed, colorized output";
38+ homepage = "https://kilimanjaro.io/concurrently";
39+ license = licenses.mit;
40+ platforms = platforms.all;
41+ mainProgram = "concurrently";
42+ };
43+ };
44+ };
45+
46+ apps = {
47+ default = flake-utils.lib.mkApp {
48+ drv = self.packages.${system}.default;
49+ };
50+ };
51+ });
52+}