@abyrd9/harbor-cli 2.1.0 → 2.2.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/dist/index.js +281 -25
- package/harbor.package-json.schema.json +2 -0
- package/package.json +1 -1
- package/scripts/dev.sh +45 -22
package/dist/index.js
CHANGED
|
@@ -151,7 +151,7 @@ async function checkDependencies() {
|
|
|
151
151
|
const instructions = getInstallInstructions(dep.name, osInfo);
|
|
152
152
|
if (instructions.length > 0) {
|
|
153
153
|
console.log(' Installation options:');
|
|
154
|
-
instructions.forEach(instruction => console.log(instruction));
|
|
154
|
+
instructions.forEach(instruction => { console.log(instruction); });
|
|
155
155
|
}
|
|
156
156
|
else {
|
|
157
157
|
console.log(` General instructions: ${dep.installMsg}`);
|
|
@@ -203,22 +203,35 @@ const possibleProjectFiles = [
|
|
|
203
203
|
const program = new Command();
|
|
204
204
|
program
|
|
205
205
|
.name('harbor')
|
|
206
|
-
.description(`A CLI tool for
|
|
206
|
+
.description(`A CLI tool for orchestrating multiple local development services in tmux.
|
|
207
207
|
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
WHAT IT DOES:
|
|
209
|
+
Harbor manages multiple services (APIs, frontends, databases) in a single tmux
|
|
210
|
+
session. It auto-discovers projects, starts them together, and provides logging.
|
|
210
211
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
✅ Configurable tmux session names
|
|
215
|
-
✅ Service logging with file output for monitoring and debugging
|
|
216
|
-
✅ Before/after script execution
|
|
212
|
+
REQUIREMENTS:
|
|
213
|
+
- tmux (terminal multiplexer)
|
|
214
|
+
- jq (JSON processor)
|
|
217
215
|
|
|
218
|
-
|
|
219
|
-
dock
|
|
220
|
-
|
|
221
|
-
|
|
216
|
+
TYPICAL WORKFLOW:
|
|
217
|
+
1. harbor dock # First time: scan and create harbor.json config
|
|
218
|
+
2. harbor launch # Start all services in interactive tmux session
|
|
219
|
+
harbor launch -d # Or start in headless/background mode
|
|
220
|
+
3. harbor bearings # Check status of running services
|
|
221
|
+
4. harbor anchor # Attach to running session (if headless)
|
|
222
|
+
5. harbor scuttle # Stop all services when done
|
|
223
|
+
|
|
224
|
+
CONFIGURATION:
|
|
225
|
+
Config is stored in harbor.json or package.json under "harbor" key.
|
|
226
|
+
Services can specify: name, path, command, log (boolean), maxLogLines.
|
|
227
|
+
|
|
228
|
+
COMMANDS:
|
|
229
|
+
dock Create new harbor.json by scanning for projects (package.json, go.mod, etc.)
|
|
230
|
+
moor Add newly discovered services to existing config
|
|
231
|
+
launch Start all configured services (use -d/--headless for background mode)
|
|
232
|
+
bearings Show status: running services, session info, log file locations
|
|
233
|
+
anchor Attach terminal to a running Harbor tmux session
|
|
234
|
+
scuttle Stop all services by killing the tmux session`)
|
|
222
235
|
.version(packageJson.version)
|
|
223
236
|
.action(async () => await checkDependencies())
|
|
224
237
|
.addHelpCommand(false);
|
|
@@ -227,8 +240,21 @@ if (process.argv.length <= 2) {
|
|
|
227
240
|
program.help();
|
|
228
241
|
}
|
|
229
242
|
program.command('dock')
|
|
230
|
-
.description(
|
|
231
|
-
|
|
243
|
+
.description(`Initialize a new Harbor project by scanning directories for services.
|
|
244
|
+
|
|
245
|
+
WHEN TO USE: Run this first in a new project that has no harbor.json yet.
|
|
246
|
+
|
|
247
|
+
WHAT IT DOES:
|
|
248
|
+
- Scans subdirectories for project files (package.json, go.mod, Cargo.toml, etc.)
|
|
249
|
+
- Creates harbor.json with discovered services
|
|
250
|
+
- Auto-detects run commands (npm run dev, go run ., etc.)
|
|
251
|
+
|
|
252
|
+
PREREQUISITES: No existing harbor.json or harbor config in package.json.
|
|
253
|
+
|
|
254
|
+
EXAMPLE:
|
|
255
|
+
harbor dock # Scan current directory
|
|
256
|
+
harbor dock -p ./apps # Scan specific subdirectory`)
|
|
257
|
+
.option('-p, --path <path>', 'Directory to scan for service projects (scans subdirectories)', './')
|
|
232
258
|
.action(async (options) => {
|
|
233
259
|
try {
|
|
234
260
|
await checkDependencies();
|
|
@@ -248,8 +274,21 @@ program.command('dock')
|
|
|
248
274
|
}
|
|
249
275
|
});
|
|
250
276
|
program.command('moor')
|
|
251
|
-
.description(
|
|
252
|
-
|
|
277
|
+
.description(`Add newly discovered services to an existing Harbor configuration.
|
|
278
|
+
|
|
279
|
+
WHEN TO USE: Run when you've added new service directories to your project.
|
|
280
|
+
|
|
281
|
+
WHAT IT DOES:
|
|
282
|
+
- Scans for new project directories not already in config
|
|
283
|
+
- Adds them to existing harbor.json or package.json harbor config
|
|
284
|
+
- Skips directories already configured
|
|
285
|
+
|
|
286
|
+
PREREQUISITES: Existing harbor.json or harbor config in package.json (run 'dock' first).
|
|
287
|
+
|
|
288
|
+
EXAMPLE:
|
|
289
|
+
harbor moor # Scan and add new services
|
|
290
|
+
harbor moor -p ./apps # Scan specific subdirectory for new services`)
|
|
291
|
+
.option('-p, --path <path>', 'Directory to scan for new service projects', './')
|
|
253
292
|
.action(async (options) => {
|
|
254
293
|
try {
|
|
255
294
|
await checkDependencies();
|
|
@@ -267,11 +306,223 @@ program.command('moor')
|
|
|
267
306
|
}
|
|
268
307
|
});
|
|
269
308
|
program.command('launch')
|
|
270
|
-
.description(
|
|
271
|
-
|
|
309
|
+
.description(`Start all configured services in a tmux session.
|
|
310
|
+
|
|
311
|
+
WHEN TO USE: Run to start your development environment.
|
|
312
|
+
|
|
313
|
+
WHAT IT DOES:
|
|
314
|
+
- Kills any existing Harbor tmux session
|
|
315
|
+
- Runs 'before' scripts if configured
|
|
316
|
+
- Creates tmux session with a window per service
|
|
317
|
+
- Starts each service with its configured command
|
|
318
|
+
- Enables logging to .harbor/*.log if log:true in config
|
|
319
|
+
- Runs 'after' scripts when session ends
|
|
320
|
+
|
|
321
|
+
MODES:
|
|
322
|
+
Interactive (default): Opens tmux session, use Shift+Left/Right to switch tabs
|
|
323
|
+
Headless (-d/--headless): Runs in background, use 'anchor' to attach later
|
|
324
|
+
|
|
325
|
+
PREREQUISITES: harbor.json or harbor config in package.json.
|
|
326
|
+
|
|
327
|
+
EXAMPLES:
|
|
328
|
+
harbor launch # Start and attach to tmux session
|
|
329
|
+
harbor launch -d # Start in background (headless mode)
|
|
330
|
+
harbor launch --headless # Same as -d`)
|
|
331
|
+
.option('-d, --detach', 'Run in background without attaching (headless mode). Use "anchor" to attach later.')
|
|
332
|
+
.option('--headless', 'Alias for --detach')
|
|
333
|
+
.action(async (options) => {
|
|
272
334
|
try {
|
|
273
335
|
await checkDependencies();
|
|
274
|
-
await runServices();
|
|
336
|
+
await runServices({ detach: options.detach || options.headless });
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
console.log('❌ Error:', err instanceof Error ? err.message : 'Unknown error');
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
program.command('anchor')
|
|
344
|
+
.description(`Attach your terminal to a running Harbor tmux session.
|
|
345
|
+
|
|
346
|
+
WHEN TO USE: After starting services with 'launch -d' (headless mode).
|
|
347
|
+
|
|
348
|
+
WHAT IT DOES:
|
|
349
|
+
- Checks if a Harbor tmux session is running
|
|
350
|
+
- Attaches your terminal to it
|
|
351
|
+
- You can then switch between service tabs with Shift+Left/Right
|
|
352
|
+
- Press Ctrl+q to kill session, or detach with Ctrl+b then d
|
|
353
|
+
|
|
354
|
+
PREREQUISITES: Services must be running (started with 'harbor launch').
|
|
355
|
+
|
|
356
|
+
EXAMPLE:
|
|
357
|
+
harbor launch -d # Start in background
|
|
358
|
+
harbor anchor # Attach to see the services`)
|
|
359
|
+
.action(async () => {
|
|
360
|
+
try {
|
|
361
|
+
const config = await readHarborConfig();
|
|
362
|
+
const sessionName = config.sessionName || 'harbor';
|
|
363
|
+
// Check if session exists
|
|
364
|
+
const checkSession = spawn('tmux', ['has-session', '-t', sessionName], {
|
|
365
|
+
stdio: 'pipe',
|
|
366
|
+
});
|
|
367
|
+
await new Promise((resolve) => {
|
|
368
|
+
checkSession.on('close', (code) => {
|
|
369
|
+
if (code !== 0) {
|
|
370
|
+
console.log(`❌ No running Harbor session found (looking for: ${sessionName})`);
|
|
371
|
+
console.log('\nTo start services, run:');
|
|
372
|
+
console.log(' harbor launch');
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
resolve();
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
// Attach to the session
|
|
379
|
+
const attach = spawn('tmux', ['attach-session', '-t', sessionName], {
|
|
380
|
+
stdio: 'inherit',
|
|
381
|
+
});
|
|
382
|
+
attach.on('close', (code) => {
|
|
383
|
+
process.exit(code || 0);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
catch (err) {
|
|
387
|
+
console.log('❌ Error:', err instanceof Error ? err.message : 'Unknown error');
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
program.command('scuttle')
|
|
392
|
+
.description(`Stop all running Harbor services by killing the tmux session.
|
|
393
|
+
|
|
394
|
+
WHEN TO USE: When you want to stop all services and free up resources.
|
|
395
|
+
|
|
396
|
+
WHAT IT DOES:
|
|
397
|
+
- Finds the running Harbor tmux session
|
|
398
|
+
- Kills the entire session (all service windows)
|
|
399
|
+
- All services stop immediately
|
|
400
|
+
|
|
401
|
+
SAFE TO RUN: If no session is running, it simply reports that and exits cleanly.
|
|
402
|
+
|
|
403
|
+
EXAMPLE:
|
|
404
|
+
harbor scuttle # Stop all services`)
|
|
405
|
+
.action(async () => {
|
|
406
|
+
try {
|
|
407
|
+
const config = await readHarborConfig();
|
|
408
|
+
const sessionName = config.sessionName || 'harbor';
|
|
409
|
+
// Check if session exists
|
|
410
|
+
const checkSession = spawn('tmux', ['has-session', '-t', sessionName], {
|
|
411
|
+
stdio: 'pipe',
|
|
412
|
+
});
|
|
413
|
+
const sessionExists = await new Promise((resolve) => {
|
|
414
|
+
checkSession.on('close', (code) => {
|
|
415
|
+
resolve(code === 0);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
if (!sessionExists) {
|
|
419
|
+
console.log(`ℹ️ No running Harbor session found (looking for: ${sessionName})`);
|
|
420
|
+
process.exit(0);
|
|
421
|
+
}
|
|
422
|
+
// Kill the session
|
|
423
|
+
const killSession = spawn('tmux', ['kill-session', '-t', sessionName], {
|
|
424
|
+
stdio: 'inherit',
|
|
425
|
+
});
|
|
426
|
+
killSession.on('close', (code) => {
|
|
427
|
+
if (code === 0) {
|
|
428
|
+
console.log(`✅ Harbor session '${sessionName}' stopped`);
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
console.log('❌ Failed to stop Harbor session');
|
|
432
|
+
}
|
|
433
|
+
process.exit(code || 0);
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
catch (err) {
|
|
437
|
+
console.log('❌ Error:', err instanceof Error ? err.message : 'Unknown error');
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
program.command('bearings')
|
|
442
|
+
.description(`Show status of running Harbor services and session information.
|
|
443
|
+
|
|
444
|
+
WHEN TO USE: To check if services are running, especially in headless mode.
|
|
445
|
+
|
|
446
|
+
WHAT IT SHOWS:
|
|
447
|
+
- Session name and running status
|
|
448
|
+
- List of service windows (with 📄 indicator if logging enabled)
|
|
449
|
+
- Log file paths and sizes
|
|
450
|
+
- Available commands (anchor, scuttle)
|
|
451
|
+
|
|
452
|
+
OUTPUT EXAMPLE:
|
|
453
|
+
⚓ Harbor Status
|
|
454
|
+
Session: harbor
|
|
455
|
+
Status: Running ✓
|
|
456
|
+
Services: [0] Terminal, [1] web 📄, [2] api 📄
|
|
457
|
+
Logs: .harbor/harbor-web.log (1.2 KB)
|
|
458
|
+
|
|
459
|
+
SAFE TO RUN: Works whether services are running or not.
|
|
460
|
+
|
|
461
|
+
EXAMPLE:
|
|
462
|
+
harbor bearings # Check what's running`)
|
|
463
|
+
.action(async () => {
|
|
464
|
+
try {
|
|
465
|
+
const config = await readHarborConfig();
|
|
466
|
+
const sessionName = config.sessionName || 'harbor';
|
|
467
|
+
// Check if session exists
|
|
468
|
+
const checkSession = spawn('tmux', ['has-session', '-t', sessionName], {
|
|
469
|
+
stdio: 'pipe',
|
|
470
|
+
});
|
|
471
|
+
const sessionExists = await new Promise((resolve) => {
|
|
472
|
+
checkSession.on('close', (code) => {
|
|
473
|
+
resolve(code === 0);
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
if (!sessionExists) {
|
|
477
|
+
console.log(`\n⚓ Harbor Status\n`);
|
|
478
|
+
console.log(` Session: ${sessionName}`);
|
|
479
|
+
console.log(` Status: Not running\n`);
|
|
480
|
+
console.log(` To start services, run:`);
|
|
481
|
+
console.log(` harbor launch # interactive mode`);
|
|
482
|
+
console.log(` harbor launch -d # headless mode\n`);
|
|
483
|
+
process.exit(0);
|
|
484
|
+
}
|
|
485
|
+
// Get list of windows (services)
|
|
486
|
+
const listWindows = spawn('tmux', ['list-windows', '-t', sessionName, '-F', '#{window_index}|#{window_name}|#{pane_current_command}'], {
|
|
487
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
488
|
+
});
|
|
489
|
+
let windowOutput = '';
|
|
490
|
+
listWindows.stdout.on('data', (data) => {
|
|
491
|
+
windowOutput += data.toString();
|
|
492
|
+
});
|
|
493
|
+
await new Promise((resolve) => {
|
|
494
|
+
listWindows.on('close', () => resolve());
|
|
495
|
+
});
|
|
496
|
+
const windows = windowOutput.trim().split('\n').filter(Boolean);
|
|
497
|
+
console.log(`\n⚓ Harbor Status\n`);
|
|
498
|
+
console.log(` Session: ${sessionName}`);
|
|
499
|
+
console.log(` Status: Running ✓`);
|
|
500
|
+
console.log(` Windows: ${windows.length}\n`);
|
|
501
|
+
console.log(` Services:`);
|
|
502
|
+
for (const window of windows) {
|
|
503
|
+
const [index, name, cmd] = window.split('|');
|
|
504
|
+
const logFile = `.harbor/${sessionName}-${name}.log`;
|
|
505
|
+
const hasLog = fs.existsSync(path.join(process.cwd(), logFile));
|
|
506
|
+
const logIndicator = hasLog ? ` 📄` : '';
|
|
507
|
+
console.log(` [${index}] ${name}${logIndicator}`);
|
|
508
|
+
}
|
|
509
|
+
// Check for log files
|
|
510
|
+
const harborDir = path.join(process.cwd(), '.harbor');
|
|
511
|
+
if (fs.existsSync(harborDir)) {
|
|
512
|
+
const logFiles = fs.readdirSync(harborDir).filter(f => f.endsWith('.log'));
|
|
513
|
+
if (logFiles.length > 0) {
|
|
514
|
+
console.log(`\n Logs:`);
|
|
515
|
+
for (const logFile of logFiles) {
|
|
516
|
+
const logPath = path.join(harborDir, logFile);
|
|
517
|
+
const stats = fs.statSync(logPath);
|
|
518
|
+
const sizeKB = (stats.size / 1024).toFixed(1);
|
|
519
|
+
console.log(` .harbor/${logFile} (${sizeKB} KB)`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
console.log(`\n Commands:`);
|
|
524
|
+
console.log(` harbor anchor - Anchor to the session`);
|
|
525
|
+
console.log(` harbor scuttle - Stop all services\n`);
|
|
275
526
|
}
|
|
276
527
|
catch (err) {
|
|
277
528
|
console.log('❌ Error:', err instanceof Error ? err.message : 'Unknown error');
|
|
@@ -526,7 +777,7 @@ async function execute(scripts, scriptType) {
|
|
|
526
777
|
}
|
|
527
778
|
console.log(`\n✅ All ${scriptType} scripts completed successfully`);
|
|
528
779
|
}
|
|
529
|
-
async function runServices() {
|
|
780
|
+
async function runServices(options = {}) {
|
|
530
781
|
const hasHarborConfig = checkHasHarborConfig();
|
|
531
782
|
if (!hasHarborConfig) {
|
|
532
783
|
console.log('❌ No harbor configuration found');
|
|
@@ -553,7 +804,7 @@ async function runServices() {
|
|
|
553
804
|
try {
|
|
554
805
|
await execute(config.before || [], 'before');
|
|
555
806
|
}
|
|
556
|
-
catch
|
|
807
|
+
catch {
|
|
557
808
|
console.error('❌ Before scripts failed, aborting launch');
|
|
558
809
|
process.exit(1);
|
|
559
810
|
}
|
|
@@ -561,10 +812,15 @@ async function runServices() {
|
|
|
561
812
|
await ensureScriptsExist();
|
|
562
813
|
// Execute the script directly using spawn to handle I/O streams
|
|
563
814
|
const scriptPath = path.join(getScriptsDir(), 'dev.sh');
|
|
815
|
+
const env = {
|
|
816
|
+
...process.env,
|
|
817
|
+
HARBOR_DETACH: options.detach ? '1' : '0',
|
|
818
|
+
};
|
|
564
819
|
const command = spawn('bash', [scriptPath], {
|
|
565
820
|
stdio: 'inherit', // This will pipe stdin/stdout/stderr to the parent process
|
|
821
|
+
env,
|
|
566
822
|
});
|
|
567
|
-
return new Promise((resolve
|
|
823
|
+
return new Promise((resolve) => {
|
|
568
824
|
command.on('error', (err) => {
|
|
569
825
|
console.error(`Error running dev.sh: ${err}`);
|
|
570
826
|
process.exit(1);
|
|
@@ -579,7 +835,7 @@ async function runServices() {
|
|
|
579
835
|
await execute(config.after || [], 'after');
|
|
580
836
|
resolve();
|
|
581
837
|
}
|
|
582
|
-
catch
|
|
838
|
+
catch {
|
|
583
839
|
console.error('❌ After scripts failed');
|
|
584
840
|
process.exit(1);
|
|
585
841
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abyrd9/harbor-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
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": {
|
package/scripts/dev.sh
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
+
# Function to get harbor config
|
|
4
|
+
get_harbor_config() {
|
|
5
|
+
if [ -f "harbor.json" ]; then
|
|
6
|
+
cat harbor.json
|
|
7
|
+
elif [ -f "package.json" ]; then
|
|
8
|
+
jq '.harbor' package.json
|
|
9
|
+
else
|
|
10
|
+
echo "{}"
|
|
11
|
+
fi
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# Get session name from config or use default
|
|
15
|
+
session_name=$(get_harbor_config | jq -r '.sessionName // "harbor"')
|
|
16
|
+
|
|
3
17
|
# Check if the session already exists and kill it
|
|
4
|
-
if tmux has-session -t
|
|
5
|
-
echo "Killing existing tmux session '
|
|
6
|
-
tmux kill-session -t
|
|
18
|
+
if tmux has-session -t "$session_name" 2>/dev/null; then
|
|
19
|
+
echo "Killing existing tmux session '$session_name'"
|
|
20
|
+
tmux kill-session -t "$session_name"
|
|
7
21
|
fi
|
|
8
|
-
|
|
9
|
-
session_name="local-dev-test"
|
|
10
22
|
repo_root="$(pwd)"
|
|
11
23
|
max_log_lines=1000
|
|
12
24
|
log_pids=()
|
|
@@ -38,18 +50,7 @@ start_log_trim() {
|
|
|
38
50
|
|
|
39
51
|
trap cleanup_logs EXIT
|
|
40
52
|
|
|
41
|
-
#
|
|
42
|
-
get_harbor_config() {
|
|
43
|
-
if [ -f "harbor.json" ]; then
|
|
44
|
-
cat harbor.json
|
|
45
|
-
elif [ -f "package.json" ]; then
|
|
46
|
-
jq '.harbor' package.json
|
|
47
|
-
else
|
|
48
|
-
echo "{}"
|
|
49
|
-
fi
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
# Start a new tmux session named 'local-dev-test' and rename the initial window
|
|
53
|
+
# Start a new tmux session and rename the initial window
|
|
53
54
|
tmux new-session -d -s "$session_name"
|
|
54
55
|
|
|
55
56
|
# Set tmux options
|
|
@@ -59,6 +60,10 @@ tmux set-option -g mouse on
|
|
|
59
60
|
tmux set-option -g history-limit 50000
|
|
60
61
|
tmux set-window-option -g mode-keys vi
|
|
61
62
|
|
|
63
|
+
# Enable extended keys so modifier combinations (like Shift+Enter) pass through to applications
|
|
64
|
+
tmux set-option -g extended-keys on
|
|
65
|
+
tmux set-option -g xterm-keys on
|
|
66
|
+
|
|
62
67
|
# Add binding to kill session with Ctrl+q
|
|
63
68
|
tmux bind-key -n C-q kill-session
|
|
64
69
|
|
|
@@ -94,11 +99,11 @@ tmux set-option -g status-style bg="#1c1917",fg="#a8a29e"
|
|
|
94
99
|
tmux set-option -g status-left ""
|
|
95
100
|
tmux set-option -g status-right "#[fg=#a8a29e]shift+←/→ switch · ctrl+q close · #[fg=white]%H:%M#[default]"
|
|
96
101
|
tmux set-window-option -g window-status-current-format "\
|
|
97
|
-
#[fg=#6366f1, bg=#1c1917]
|
|
98
|
-
#[fg=#6366f1, bg=#1c1917, bold] #W
|
|
102
|
+
#[fg=#6366f1, bg=#1c1917] →\
|
|
103
|
+
#[fg=#6366f1, bg=#1c1917, bold] #W\
|
|
99
104
|
#[fg=#6366f1, bg=#1c1917] "
|
|
100
105
|
tmux set-window-option -g window-status-format "\
|
|
101
|
-
#[fg=#a8a29e, bg=#1c1917]
|
|
106
|
+
#[fg=#a8a29e, bg=#1c1917] \
|
|
102
107
|
#[fg=#a8a29e, bg=#1c1917] #W \
|
|
103
108
|
#[fg=#a8a29e, bg=#1c1917] "
|
|
104
109
|
|
|
@@ -156,5 +161,23 @@ tmux bind-key -n Home select-window -t :0
|
|
|
156
161
|
# Select the terminal window
|
|
157
162
|
tmux select-window -t "$session_name":0
|
|
158
163
|
|
|
159
|
-
# Attach to the tmux session
|
|
160
|
-
|
|
164
|
+
# Attach to the tmux session (unless running in detached/headless mode)
|
|
165
|
+
if [ "${HARBOR_DETACH:-0}" = "1" ]; then
|
|
166
|
+
echo ""
|
|
167
|
+
echo "🚀 Harbor services started in detached mode"
|
|
168
|
+
echo ""
|
|
169
|
+
echo " Session: $session_name"
|
|
170
|
+
echo " Services: $((window_index - 1))"
|
|
171
|
+
echo ""
|
|
172
|
+
echo " Commands:"
|
|
173
|
+
echo " harbor anchor - Anchor to the tmux session"
|
|
174
|
+
echo " harbor scuttle - Scuttle all services"
|
|
175
|
+
echo ""
|
|
176
|
+
if get_harbor_config | jq -e '.services[] | select(.log == true)' >/dev/null 2>&1; then
|
|
177
|
+
echo " Logs:"
|
|
178
|
+
echo " tail -f .harbor/${session_name}-<service>.log"
|
|
179
|
+
echo ""
|
|
180
|
+
fi
|
|
181
|
+
else
|
|
182
|
+
tmux attach-session -t "$session_name"
|
|
183
|
+
fi
|