@abyrd9/harbor-cli 2.0.1 โ†’ 2.1.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Harbor CLI
2
2
 
3
- A CLI tool for managing local development services with ease. Harbor helps you orchestrate multiple development services in tmux sessions, perfect for microservices and polyglot projects.
3
+ A CLI tool for managing local development services with ease. Harbor helps you orchestrate multiple development services in a tmux session, perfect for microservices and polyglot projects.
4
4
 
5
5
  ## โœจ Features
6
6
 
@@ -10,7 +10,8 @@ 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
+ - ๐Ÿ“ **Service Logging**: Stream service output to log files for monitoring and debugging
14
+ - ๐Ÿท๏ธ **Custom Session Names**: Configure unique tmux session names
14
15
 
15
16
  ## Installation
16
17
 
@@ -63,22 +64,33 @@ Create a dedicated configuration file:
63
64
  {
64
65
  "services": [
65
66
  {
66
- "name": "frontend",
67
- "path": "./vite-frontend",
67
+ "name": "database",
68
+ "path": "./db",
69
+ "preStage": {
70
+ "command": "docker-compose up -d postgres",
71
+ "wait": 5,
72
+ "timeout": 60
73
+ },
68
74
  "command": "npm run dev",
69
- "log": true
75
+ "log": true,
76
+ "maxLogLines": 500
70
77
  },
71
78
  {
72
79
  "name": "api",
73
80
  "path": "./go-api",
81
+ "preStage": {
82
+ "command": "go mod download",
83
+ "wait": 2
84
+ },
74
85
  "command": "go run .",
75
86
  "log": true,
76
87
  "maxLogLines": 500
77
88
  },
78
89
  {
79
- "name": "database",
80
- "path": "./db",
81
- "command": "docker-compose up"
90
+ "name": "frontend",
91
+ "path": "./vite-frontend",
92
+ "command": "npm run dev",
93
+ "log": true
82
94
  }
83
95
  ],
84
96
  "before": [
@@ -96,7 +108,8 @@ Create a dedicated configuration file:
96
108
  "path": "./",
97
109
  "command": "echo 'Development environment ready!'"
98
110
  }
99
- ]
111
+ ],
112
+ "sessionName": "my-project-dev"
100
113
  }
101
114
  ```
102
115
 
@@ -137,14 +150,16 @@ Store configuration directly in your `package.json`:
137
150
  }
138
151
  ```
139
152
 
153
+
154
+
140
155
  ## Commands
141
156
 
142
157
  | Command | Description |
143
158
  |---------|-------------|
144
- | `harbor dock` | Initialize harbor configuration by scanning project directories |
145
- | `harbor moor` | Scan for and add new services to existing configuration |
146
- | `harbor launch` | Start all services in tmux sessions |
147
- | `harbor --help` | Show help and available commands |
159
+ | `harbor dock` | Initialize Harbor config by auto-discovering services in your project |
160
+ | `harbor moor` | Scan for and add new services to your existing Harbor configuration |
161
+ | `harbor launch` | Start all services in a tmux session with pre-stage commands |
162
+ | `harbor --help` | Show comprehensive help with feature descriptions |
148
163
  | `harbor --version` | Show version information |
149
164
 
150
165
  ### Command Options
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { chmodSync } from 'node:fs';
7
7
  import { readFileSync } from 'node:fs';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import os from 'node:os';
10
+ import readline from 'node:readline';
10
11
  // Read version from package.json
11
12
  const __filename = fileURLToPath(import.meta.url);
12
13
  const __dirname = path.dirname(__filename);
@@ -160,6 +161,35 @@ async function checkDependencies() {
160
161
  throw new Error('Please install missing dependencies before continuing');
161
162
  }
162
163
  }
164
+ function promptConfigLocation() {
165
+ const rl = readline.createInterface({
166
+ input: process.stdin,
167
+ output: process.stdout,
168
+ });
169
+ return new Promise((resolve) => {
170
+ console.log('\nFound package.json. Where would you like to store harbor config?');
171
+ console.log(' 1. package.json (keeps everything in one place)');
172
+ console.log(' 2. harbor.json (separate config file, auto-IntelliSense)\n');
173
+ const ask = () => {
174
+ rl.question('Enter choice (1 or 2): ', (answer) => {
175
+ const choice = answer.trim();
176
+ if (choice === '1') {
177
+ rl.close();
178
+ resolve('package.json');
179
+ }
180
+ else if (choice === '2') {
181
+ rl.close();
182
+ resolve('harbor.json');
183
+ }
184
+ else {
185
+ console.log('Please enter 1 or 2');
186
+ ask();
187
+ }
188
+ });
189
+ };
190
+ ask();
191
+ });
192
+ }
163
193
  const possibleProjectFiles = [
164
194
  'package.json', // Node.js projects
165
195
  'go.mod', // Go projects
@@ -176,12 +206,19 @@ program
176
206
  .description(`A CLI tool for managing your project's local development services
177
207
 
178
208
  Harbor helps you manage multiple local development services with ease.
179
- It provides a simple way to configure and run your services in tmux sessions.
209
+ It provides a simple way to configure and run your services in a tmux session.
210
+
211
+ Features:
212
+ โœ… Automatic service discovery for Node.js, Go, Rust, Python, PHP, Java
213
+ โœ… Pre-stage commands for setup before main service starts
214
+ โœ… Configurable tmux session names
215
+ โœ… Service logging with file output for monitoring and debugging
216
+ โœ… Before/after script execution
180
217
 
181
218
  Available Commands:
182
- dock Initialize a new Harbor project
183
- moor Add new services to your configuration
184
- launch Start all services in tmux sessions`)
219
+ dock Initialize a new Harbor project by scanning directories
220
+ moor Add new services to existing Harbor configuration
221
+ launch Start all services in a tmux session with pre-stage commands`)
185
222
  .version(packageJson.version)
186
223
  .action(async () => await checkDependencies())
187
224
  .addHelpCommand(false);
@@ -190,10 +227,7 @@ if (process.argv.length <= 2) {
190
227
  program.help();
191
228
  }
192
229
  program.command('dock')
193
- .description(`Prepares your development environment by creating a harbor configuration file
194
- - harbor.json configuration file (or harbor field in package.json)
195
-
196
- This is typically the first command you'll run in a new project.`)
230
+ .description('Initialize Harbor config by auto-discovering services in your project')
197
231
  .option('-p, --path <path>', 'The path to the root of your project', './')
198
232
  .action(async (options) => {
199
233
  try {
@@ -214,7 +248,7 @@ This is typically the first command you'll run in a new project.`)
214
248
  }
215
249
  });
216
250
  program.command('moor')
217
- .description('Add new services to your harbor configuration')
251
+ .description('Scan for and add new services to your existing Harbor configuration')
218
252
  .option('-p, --path <path>', 'The path to the root of your project', './')
219
253
  .action(async (options) => {
220
254
  try {
@@ -233,9 +267,7 @@ program.command('moor')
233
267
  }
234
268
  });
235
269
  program.command('launch')
236
- .description(`Launch your services in the harbor terminal multiplexer (Using tmux)
237
-
238
- Note: This command will stop any active Caddy processes, including those from other Harbor projects.`)
270
+ .description('Start all services in a tmux session with pre-stage commands')
239
271
  .action(async () => {
240
272
  try {
241
273
  await checkDependencies();
@@ -250,7 +282,7 @@ program.parse();
250
282
  function fileExists(path) {
251
283
  return fs.existsSync(`${process.cwd()}/${path}`);
252
284
  }
253
- function isProjectDirectory(dirPath) {
285
+ export function isProjectDirectory(dirPath) {
254
286
  return possibleProjectFiles.some(file => {
255
287
  try {
256
288
  return fs.existsSync(path.join(process.cwd(), dirPath, file));
@@ -260,7 +292,7 @@ function isProjectDirectory(dirPath) {
260
292
  }
261
293
  });
262
294
  }
263
- function validateConfig(config) {
295
+ export function validateConfig(config) {
264
296
  if (!Array.isArray(config.services)) {
265
297
  return 'Services must be an array';
266
298
  }
@@ -323,28 +355,21 @@ async function generateDevFile(dirPath) {
323
355
  const packageData = await fs.promises.readFile('package.json', 'utf-8');
324
356
  const packageJson = JSON.parse(packageData);
325
357
  if (packageJson.harbor) {
358
+ // Existing harbor config in package.json, use it
326
359
  config = packageJson.harbor;
327
360
  writeToPackageJson = true;
328
361
  console.log('Found existing harbor config in package.json, scanning for new services...');
329
362
  }
330
- else if (fileExists('package.json')) {
331
- // If package.json exists but no harbor config, use it
332
- writeToPackageJson = true;
333
- config = {
334
- services: [],
335
- before: [],
336
- after: [],
337
- };
338
- console.log('Creating new harbor config in package.json...');
339
- }
340
363
  else {
341
- // No package.json, create harbor.json
364
+ // package.json exists but no harbor config - ask user where to store it
365
+ const choice = await promptConfigLocation();
366
+ writeToPackageJson = choice === 'package.json';
342
367
  config = {
343
368
  services: [],
344
369
  before: [],
345
370
  after: [],
346
371
  };
347
- console.log('Creating new harbor.json...');
372
+ console.log(`Creating new harbor config in ${choice}...`);
348
373
  }
349
374
  }
350
375
  catch (err) {
@@ -404,10 +429,22 @@ async function generateDevFile(dirPath) {
404
429
  packageJson.harbor = config;
405
430
  await fs.promises.writeFile('package.json', JSON.stringify(packageJson, null, 2), 'utf-8');
406
431
  console.log('\npackage.json updated successfully with harbor configuration');
432
+ console.log('\n๐Ÿ’ก Tip: To enable IntelliSense for the harbor config in package.json,');
433
+ console.log(' add this to your .vscode/settings.json:');
434
+ console.log(' {');
435
+ console.log(' "json.schemas": [{');
436
+ console.log(' "fileMatch": ["package.json"],');
437
+ console.log(' "url": "https://raw.githubusercontent.com/Abyrd9/harbor-cli/main/harbor.package-json.schema.json"');
438
+ console.log(' }]');
439
+ console.log(' }');
407
440
  }
408
441
  else {
409
- // Write to harbor.json
410
- await fs.promises.writeFile('harbor.json', JSON.stringify(config, null, 2), 'utf-8');
442
+ // Write to harbor.json with $schema for IntelliSense
443
+ const configWithSchema = {
444
+ $schema: 'https://raw.githubusercontent.com/Abyrd9/harbor-cli/main/harbor.schema.json',
445
+ ...config,
446
+ };
447
+ await fs.promises.writeFile('harbor.json', JSON.stringify(configWithSchema, null, 2), 'utf-8');
411
448
  console.log('\nharbor.json created successfully');
412
449
  }
413
450
  console.log('\nImportant:');
@@ -0,0 +1,96 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://raw.githubusercontent.com/Abyrd9/harbor-cli/main/harbor.package-json.schema.json",
4
+ "title": "Harbor Configuration in package.json",
5
+ "description": "Schema extension for the 'harbor' property in package.json",
6
+ "type": "object",
7
+ "properties": {
8
+ "harbor": {
9
+ "type": "object",
10
+ "description": "Harbor CLI configuration for orchestrating local development services",
11
+ "required": ["services"],
12
+ "properties": {
13
+ "services": {
14
+ "type": "array",
15
+ "description": "List of development services to run",
16
+ "items": {
17
+ "type": "object",
18
+ "required": ["name", "path"],
19
+ "properties": {
20
+ "name": {
21
+ "type": "string",
22
+ "description": "Display name for the service (shown in the terminal UI)"
23
+ },
24
+ "path": {
25
+ "type": "string",
26
+ "description": "Relative path to the service directory from the project root"
27
+ },
28
+ "command": {
29
+ "type": "string",
30
+ "description": "Command to start the service (e.g., 'bun run dev', 'go run .')"
31
+ },
32
+ "log": {
33
+ "type": "boolean",
34
+ "description": "Whether to log output to a file in .harbor/logs/",
35
+ "default": false
36
+ },
37
+ "maxLogLines": {
38
+ "type": "integer",
39
+ "description": "Maximum number of lines to keep in the log file",
40
+ "minimum": 1
41
+ }
42
+ },
43
+ "additionalProperties": false
44
+ }
45
+ },
46
+ "sessionName": {
47
+ "type": "string",
48
+ "description": "Custom name for tmux session (default: local-dev-test)",
49
+ "pattern": "^[a-zA-Z0-9_-]+$"
50
+ },
51
+ "before": {
52
+ "type": "array",
53
+ "description": "Scripts to run before starting services",
54
+ "items": {
55
+ "type": "object",
56
+ "required": ["path", "command"],
57
+ "properties": {
58
+ "path": {
59
+ "type": "string",
60
+ "description": "Relative path to run the command from"
61
+ },
62
+ "command": {
63
+ "type": "string",
64
+ "description": "Command to execute"
65
+ }
66
+ },
67
+ "additionalProperties": false
68
+ }
69
+ },
70
+ "after": {
71
+ "type": "array",
72
+ "description": "Scripts to run after all services stop",
73
+ "items": {
74
+ "type": "object",
75
+ "required": ["path", "command"],
76
+ "properties": {
77
+ "path": {
78
+ "type": "string",
79
+ "description": "Relative path to run the command from"
80
+ },
81
+ "command": {
82
+ "type": "string",
83
+ "description": "Command to execute"
84
+ }
85
+ },
86
+ "additionalProperties": false
87
+ }
88
+ }
89
+ },
90
+ "additionalProperties": false
91
+ }
92
+ }
93
+ }
94
+
95
+
96
+
@@ -0,0 +1,92 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://raw.githubusercontent.com/Abyrd9/harbor-cli/main/harbor.schema.json",
4
+ "title": "Harbor Configuration",
5
+ "description": "Configuration schema for Harbor CLI - a tool for running multiple development services",
6
+ "type": "object",
7
+ "required": ["services"],
8
+ "properties": {
9
+ "$schema": {
10
+ "type": "string",
11
+ "description": "JSON Schema reference for IDE IntelliSense"
12
+ },
13
+ "services": {
14
+ "type": "array",
15
+ "description": "List of development services to run",
16
+ "items": {
17
+ "type": "object",
18
+ "required": ["name", "path"],
19
+ "properties": {
20
+ "name": {
21
+ "type": "string",
22
+ "description": "Display name for the service (shown in the terminal UI)"
23
+ },
24
+ "path": {
25
+ "type": "string",
26
+ "description": "Relative path to the service directory from the project root"
27
+ },
28
+ "command": {
29
+ "type": "string",
30
+ "description": "Command to start the service (e.g., 'bun run dev', 'go run .')"
31
+ },
32
+ "log": {
33
+ "type": "boolean",
34
+ "description": "Whether to log output to a file in .harbor/logs/",
35
+ "default": false
36
+ },
37
+ "maxLogLines": {
38
+ "type": "integer",
39
+ "description": "Maximum number of lines to keep in the log file",
40
+ "minimum": 1
41
+ }
42
+ },
43
+ "additionalProperties": false
44
+ }
45
+ },
46
+ "sessionName": {
47
+ "type": "string",
48
+ "description": "Custom name for the tmux session (default: local-dev-test)",
49
+ "pattern": "^[a-zA-Z0-9_-]+$"
50
+ },
51
+ "before": {
52
+ "type": "array",
53
+ "description": "Scripts to run before starting services",
54
+ "items": {
55
+ "type": "object",
56
+ "required": ["path", "command"],
57
+ "properties": {
58
+ "path": {
59
+ "type": "string",
60
+ "description": "Relative path to run the command from"
61
+ },
62
+ "command": {
63
+ "type": "string",
64
+ "description": "Command to execute"
65
+ }
66
+ },
67
+ "additionalProperties": false
68
+ }
69
+ },
70
+ "after": {
71
+ "type": "array",
72
+ "description": "Scripts to run after all services stop",
73
+ "items": {
74
+ "type": "object",
75
+ "required": ["path", "command"],
76
+ "properties": {
77
+ "path": {
78
+ "type": "string",
79
+ "description": "Relative path to run the command from"
80
+ },
81
+ "command": {
82
+ "type": "string",
83
+ "description": "Command to execute"
84
+ }
85
+ },
86
+ "additionalProperties": false
87
+ }
88
+ }
89
+ },
90
+ "additionalProperties": false
91
+ }
92
+
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "@abyrd9/harbor-cli",
3
- "version": "2.0.1",
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.",
3
+ "version": "2.1.0",
4
+ "description": "A CLI tool for orchestrating local development services in a tmux session. Perfect for microservices and polyglot projects with automatic service discovery and before/after script support.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "harbor": "dist/index.js"
8
8
  },
9
9
  "files": [
10
10
  "dist",
11
- "scripts"
11
+ "scripts",
12
+ "harbor.schema.json",
13
+ "harbor.package-json.schema.json"
12
14
  ],
13
15
  "scripts": {
14
16
  "check": "bunx npm-check -u",
@@ -16,6 +18,8 @@
16
18
  "prepare": "bun run build",
17
19
  "start": "bun dist/index.js",
18
20
  "harbor": "bun dist/index.js",
21
+ "test": "vitest",
22
+ "test:watch": "vitest --watch",
19
23
  "release": "bash release.sh"
20
24
  },
21
25
  "keywords": [
@@ -43,25 +47,8 @@
43
47
  "devDependencies": {
44
48
  "@types/node": "^24.0.3",
45
49
  "bun-types": "latest",
46
- "typescript": "^5.8.3"
50
+ "typescript": "^5.8.3",
51
+ "vitest": "^4.0.16"
47
52
  },
48
- "main": "index.js",
49
- "harbor": {
50
- "services": [
51
- {
52
- "name": "web",
53
- "path": "test-services/web",
54
- "command": "bun run dev",
55
- "log": true,
56
- "maxLogLines": 500
57
- },
58
- {
59
- "name": "go-api",
60
- "path": "test-services/go-api",
61
- "command": "go run .",
62
- "log": true,
63
- "maxLogLines": 500
64
- }
65
- ]
66
- }
53
+ "main": "index.js"
67
54
  }
package/scripts/dev.sh CHANGED
@@ -20,8 +20,20 @@ cleanup_logs() {
20
20
  start_log_trim() {
21
21
  local log_file="$1"
22
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
23
+ # Start background log trimmer process
24
+ {
25
+ while true; do
26
+ sleep 5
27
+ if [ -f "$log_file" ]; then
28
+ lines=$(wc -l < "$log_file")
29
+ if [ "$lines" -gt $max_lines ]; then
30
+ tail -n $max_lines "$log_file" > "${log_file}.tmp" && \
31
+ cat "${log_file}.tmp" > "$log_file" && \
32
+ rm "${log_file}.tmp"
33
+ fi
34
+ fi
35
+ done
36
+ } &
25
37
  }
26
38
 
27
39
  trap cleanup_logs EXIT
@@ -72,15 +84,15 @@ tmux bind-key -T copy-mode-vi v send-keys -X begin-selection
72
84
  tmux bind-key -T copy-mode-vi y send-keys -X copy-selection-and-cancel
73
85
  tmux bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"
74
86
 
75
- # Set easier window navigation shortcuts
76
- tmux bind-key -n Left select-window -t :-
77
- tmux bind-key -n Right select-window -t :+
87
+ # Set easier window navigation shortcuts (Shift+Left/Right to switch windows)
88
+ tmux bind-key -n S-Left select-window -t :-
89
+ tmux bind-key -n S-Right select-window -t :+
78
90
 
79
91
  # Configure status bar
80
92
  tmux set-option -g status-position top
81
93
  tmux set-option -g status-style bg="#1c1917",fg="#a8a29e"
82
94
  tmux set-option -g status-left ""
83
- tmux set-option -g status-right "#[fg=#a8a29e]Close with ctrl+q ยท #[fg=white]%H:%M#[default]"
95
+ tmux set-option -g status-right "#[fg=#a8a29e]shift+โ†/โ†’ switch ยท ctrl+q close ยท #[fg=white]%H:%M#[default]"
84
96
  tmux set-window-option -g window-status-current-format "\
85
97
  #[fg=#6366f1, bg=#1c1917] โ†’
86
98
  #[fg=#6366f1, bg=#1c1917, bold] #W
@@ -124,9 +136,10 @@ while read service; do
124
136
  if [ "$log" = "true" ]; then
125
137
  log_file="$repo_root/.harbor/${session_name}-${name}.log"
126
138
  : > "$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"
139
+ # Use pipe-pane to capture ALL terminal output (works with any program, no buffering issues)
140
+ tmux new-window -t "$session_name":$window_index -n "$name"
141
+ tmux pipe-pane -t "$session_name":$window_index "cat >> \"$log_file\""
142
+ tmux send-keys -t "$session_name":$window_index "cd \"$path\" && $command" C-m
130
143
  # Start background process to trim logs if they get too large
131
144
  start_log_trim "$log_file" "$effective_max_lines"
132
145
  else
package/dist/harbor DELETED
Binary file