@abyrd9/harbor-cli 1.1.1 → 2.0.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 +46 -2
- package/dist/harbor +0 -0
- package/dist/index.js +22 -0
- package/package.json +10 -6
- package/scripts/dev.sh +48 -8
package/README.md
CHANGED
|
@@ -10,9 +10,14 @@ A CLI tool for managing local development services with ease. Harbor helps you o
|
|
|
10
10
|
- 🔄 **Dependency Management**: Automatically checks for required system dependencies
|
|
11
11
|
- 🎯 **Multi-Language Support**: Works with Node.js, Go, Rust, Python, PHP, Java, and more
|
|
12
12
|
- 🖥️ **Tmux Integration**: Professional terminal multiplexing for service management
|
|
13
|
+
- 🤖 **AI Agent Friendly**: Stream service logs to files for AI coding assistants to monitor
|
|
13
14
|
|
|
14
15
|
## Installation
|
|
15
16
|
|
|
17
|
+
```bash
|
|
18
|
+
bun add -g @abyrd9/harbor-cli
|
|
19
|
+
```
|
|
20
|
+
|
|
16
21
|
```bash
|
|
17
22
|
npm install -g @abyrd9/harbor-cli
|
|
18
23
|
```
|
|
@@ -60,12 +65,15 @@ Create a dedicated configuration file:
|
|
|
60
65
|
{
|
|
61
66
|
"name": "frontend",
|
|
62
67
|
"path": "./vite-frontend",
|
|
63
|
-
"command": "npm run dev"
|
|
68
|
+
"command": "npm run dev",
|
|
69
|
+
"log": true
|
|
64
70
|
},
|
|
65
71
|
{
|
|
66
72
|
"name": "api",
|
|
67
73
|
"path": "./go-api",
|
|
68
|
-
"command": "go run ."
|
|
74
|
+
"command": "go run .",
|
|
75
|
+
"log": true,
|
|
76
|
+
"maxLogLines": 500
|
|
69
77
|
},
|
|
70
78
|
{
|
|
71
79
|
"name": "database",
|
|
@@ -202,6 +210,42 @@ Override auto-detected commands by editing your configuration:
|
|
|
202
210
|
}
|
|
203
211
|
```
|
|
204
212
|
|
|
213
|
+
### Service Logging for AI Agents
|
|
214
|
+
|
|
215
|
+
Enable logging to stream service output to files in `.harbor/`. This is particularly useful when working with AI coding assistants (like Opencode, Codex, or Claude) that can read log files to understand what's happening in your services.
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"services": [
|
|
220
|
+
{
|
|
221
|
+
"name": "api",
|
|
222
|
+
"path": "./api",
|
|
223
|
+
"command": "go run .",
|
|
224
|
+
"log": true,
|
|
225
|
+
"maxLogLines": 500
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"name": "frontend",
|
|
229
|
+
"path": "./frontend",
|
|
230
|
+
"command": "npm run dev",
|
|
231
|
+
"log": true
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Options:**
|
|
238
|
+
- `log`: `true` to enable logging (default: `false`)
|
|
239
|
+
- `maxLogLines`: Maximum lines to keep in log file (default: `1000`)
|
|
240
|
+
|
|
241
|
+
**Log files are:**
|
|
242
|
+
- Stored in `.harbor/` directory (automatically added to `.gitignore`)
|
|
243
|
+
- Named `{session}-{service}.log` (e.g., `local-dev-test-api.log`)
|
|
244
|
+
- Automatically trimmed to prevent unbounded growth
|
|
245
|
+
- Stripped of ANSI escape codes for clean, readable output
|
|
246
|
+
|
|
247
|
+
**Use case:** Point your AI assistant to the `.harbor/` folder so it can monitor service logs, spot errors, and understand runtime behavior while helping you develop.
|
|
248
|
+
|
|
205
249
|
### Before/After Scripts
|
|
206
250
|
Run custom scripts before and after your services start:
|
|
207
251
|
|
package/dist/harbor
ADDED
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -511,6 +511,7 @@ async function runServices() {
|
|
|
511
511
|
console.error('Error reading config:', err);
|
|
512
512
|
process.exit(1);
|
|
513
513
|
}
|
|
514
|
+
ensureLogSetup(config);
|
|
514
515
|
// Execute before scripts
|
|
515
516
|
try {
|
|
516
517
|
await execute(config.before || [], 'before');
|
|
@@ -548,6 +549,27 @@ async function runServices() {
|
|
|
548
549
|
});
|
|
549
550
|
});
|
|
550
551
|
}
|
|
552
|
+
function ensureLogSetup(config) {
|
|
553
|
+
const shouldLog = config.services.some((service) => service.log);
|
|
554
|
+
if (!shouldLog) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
const harborDir = path.join(process.cwd(), '.harbor');
|
|
558
|
+
if (!fs.existsSync(harborDir)) {
|
|
559
|
+
fs.mkdirSync(harborDir, { recursive: true });
|
|
560
|
+
}
|
|
561
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
562
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
566
|
+
if (gitignore.includes('.harbor')) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
const needsNewline = gitignore.length > 0 && !gitignore.endsWith('\n');
|
|
570
|
+
const entry = `${needsNewline ? '\n' : ''}.harbor/\n`;
|
|
571
|
+
fs.appendFileSync(gitignorePath, entry, 'utf-8');
|
|
572
|
+
}
|
|
551
573
|
// Get the package root directory
|
|
552
574
|
function getPackageRoot() {
|
|
553
575
|
const __filename = fileURLToPath(import.meta.url);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abyrd9/harbor-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "A CLI tool for orchestrating local development services in tmux sessions. Perfect for microservices and polyglot projects with automatic service discovery and before/after script support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
14
|
"check": "bunx npm-check -u",
|
|
15
|
-
"build": "tsc",
|
|
15
|
+
"build": "bunx tsc",
|
|
16
16
|
"prepare": "bun run build",
|
|
17
17
|
"start": "bun dist/index.js",
|
|
18
18
|
"harbor": "bun dist/index.js",
|
|
@@ -49,14 +49,18 @@
|
|
|
49
49
|
"harbor": {
|
|
50
50
|
"services": [
|
|
51
51
|
{
|
|
52
|
-
"name": "
|
|
53
|
-
"path": "test-services/
|
|
54
|
-
"command": "
|
|
52
|
+
"name": "web",
|
|
53
|
+
"path": "test-services/web",
|
|
54
|
+
"command": "bun run dev",
|
|
55
|
+
"log": true,
|
|
56
|
+
"maxLogLines": 500
|
|
55
57
|
},
|
|
56
58
|
{
|
|
57
59
|
"name": "go-api",
|
|
58
60
|
"path": "test-services/go-api",
|
|
59
|
-
"command": "go run ."
|
|
61
|
+
"command": "go run .",
|
|
62
|
+
"log": true,
|
|
63
|
+
"maxLogLines": 500
|
|
60
64
|
}
|
|
61
65
|
]
|
|
62
66
|
}
|
package/scripts/dev.sh
CHANGED
|
@@ -6,6 +6,26 @@ if tmux has-session -t local-dev-test 2>/dev/null; then
|
|
|
6
6
|
tmux kill-session -t local-dev-test
|
|
7
7
|
fi
|
|
8
8
|
|
|
9
|
+
session_name="local-dev-test"
|
|
10
|
+
repo_root="$(pwd)"
|
|
11
|
+
max_log_lines=1000
|
|
12
|
+
log_pids=()
|
|
13
|
+
|
|
14
|
+
cleanup_logs() {
|
|
15
|
+
for pid in "${log_pids[@]}"; do
|
|
16
|
+
kill "$pid" 2>/dev/null
|
|
17
|
+
done
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
start_log_trim() {
|
|
21
|
+
local log_file="$1"
|
|
22
|
+
local max_lines="$2"
|
|
23
|
+
# Run trimmer inside tmux so it survives script exit
|
|
24
|
+
tmux send-keys -t "$session_name":0 "( while true; do sleep 5; if [ -f \"$log_file\" ]; then lines=\$(wc -l < \"$log_file\"); if [ \"\$lines\" -gt $max_lines ]; then tail -n $max_lines \"$log_file\" > \"${log_file}.tmp\" && mv \"${log_file}.tmp\" \"$log_file\"; fi; fi; done ) &" C-m
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
trap cleanup_logs EXIT
|
|
28
|
+
|
|
9
29
|
# Function to get harbor config
|
|
10
30
|
get_harbor_config() {
|
|
11
31
|
if [ -f "harbor.json" ]; then
|
|
@@ -18,7 +38,7 @@ get_harbor_config() {
|
|
|
18
38
|
}
|
|
19
39
|
|
|
20
40
|
# Start a new tmux session named 'local-dev-test' and rename the initial window
|
|
21
|
-
tmux new-session -d -s
|
|
41
|
+
tmux new-session -d -s "$session_name"
|
|
22
42
|
|
|
23
43
|
# Set tmux options
|
|
24
44
|
tmux set-option -g prefix C-a
|
|
@@ -77,31 +97,51 @@ tmux set-option -g 'status-format[0]' ''
|
|
|
77
97
|
|
|
78
98
|
# Create a new window for the interactive shell
|
|
79
99
|
echo "Creating window for interactive shell"
|
|
80
|
-
tmux rename-window -t
|
|
100
|
+
tmux rename-window -t "$session_name":0 'Terminal'
|
|
81
101
|
|
|
82
102
|
window_index=1 # Start from index 1
|
|
83
103
|
|
|
104
|
+
if get_harbor_config | jq -e '.services[] | select(.log == true)' >/dev/null 2>&1; then
|
|
105
|
+
mkdir -p "$repo_root/.harbor"
|
|
106
|
+
rm -f "$repo_root/.harbor/${session_name}-"*.log
|
|
107
|
+
fi
|
|
108
|
+
|
|
84
109
|
# Create windows dynamically based on harbor config
|
|
85
|
-
|
|
110
|
+
while read service; do
|
|
86
111
|
name=$(echo $service | jq -r '.name')
|
|
87
112
|
path=$(echo $service | jq -r '.path')
|
|
88
113
|
command=$(echo $service | jq -r '.command')
|
|
114
|
+
log=$(echo $service | jq -r '.log // false')
|
|
115
|
+
service_max_lines=$(echo $service | jq -r '.maxLogLines // empty')
|
|
116
|
+
|
|
117
|
+
# Use service-specific maxLogLines or fall back to default
|
|
118
|
+
effective_max_lines="${service_max_lines:-$max_log_lines}"
|
|
89
119
|
|
|
90
120
|
echo "Creating window for service: $name"
|
|
91
121
|
echo "Path: $path"
|
|
92
122
|
echo "Command: $command"
|
|
93
123
|
|
|
94
|
-
|
|
95
|
-
|
|
124
|
+
if [ "$log" = "true" ]; then
|
|
125
|
+
log_file="$repo_root/.harbor/${session_name}-${name}.log"
|
|
126
|
+
: > "$log_file"
|
|
127
|
+
# Run command directly (not via send-keys) to avoid shell echo
|
|
128
|
+
# Strip ANSI escape sequences and control chars before writing to log file
|
|
129
|
+
tmux new-window -t "$session_name":$window_index -n "$name" "cd \"$path\" && $command 2>&1 | sed -u 's/\\x1b\\[[0-9;]*[mGKHJ]//g' | tee -a \"$log_file\"; exec bash"
|
|
130
|
+
# Start background process to trim logs if they get too large
|
|
131
|
+
start_log_trim "$log_file" "$effective_max_lines"
|
|
132
|
+
else
|
|
133
|
+
tmux new-window -t "$session_name":$window_index -n "$name"
|
|
134
|
+
tmux send-keys -t "$session_name":$window_index "cd \"$path\" && $command" C-m
|
|
135
|
+
fi
|
|
96
136
|
|
|
97
137
|
((window_index++))
|
|
98
|
-
done
|
|
138
|
+
done < <(get_harbor_config | jq -c '.services[]')
|
|
99
139
|
|
|
100
140
|
# Bind 'Home' key to switch to the terminal window
|
|
101
141
|
tmux bind-key -n Home select-window -t :0
|
|
102
142
|
|
|
103
143
|
# Select the terminal window
|
|
104
|
-
tmux select-window -t
|
|
144
|
+
tmux select-window -t "$session_name":0
|
|
105
145
|
|
|
106
146
|
# Attach to the tmux session
|
|
107
|
-
tmux attach-session -t
|
|
147
|
+
tmux attach-session -t "$session_name"
|