@abyrd9/harbor-cli 1.1.0 → 2.0.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 +46 -2
- package/dist/harbor +0 -0
- package/dist/index.js +154 -8
- package/package.json +11 -7
- 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
|
@@ -6,6 +6,7 @@ import { spawn } from 'node:child_process';
|
|
|
6
6
|
import { chmodSync } from 'node:fs';
|
|
7
7
|
import { readFileSync } from 'node:fs';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import os from 'node:os';
|
|
9
10
|
// Read version from package.json
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = path.dirname(__filename);
|
|
@@ -24,8 +25,107 @@ const requiredDependencies = [
|
|
|
24
25
|
requiredFor: 'JSON processing in service management',
|
|
25
26
|
},
|
|
26
27
|
];
|
|
28
|
+
function detectOS() {
|
|
29
|
+
const platform = os.platform();
|
|
30
|
+
const arch = os.arch();
|
|
31
|
+
const isWindows = platform === 'win32';
|
|
32
|
+
const isMac = platform === 'darwin';
|
|
33
|
+
const isLinux = platform === 'linux';
|
|
34
|
+
// Check if running in WSL
|
|
35
|
+
let isWSL = false;
|
|
36
|
+
if (isLinux) {
|
|
37
|
+
try {
|
|
38
|
+
const release = fs.readFileSync('/proc/version', 'utf-8');
|
|
39
|
+
isWSL = release.toLowerCase().includes('microsoft') || release.toLowerCase().includes('wsl');
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// If we can't read /proc/version, assume not WSL
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
platform,
|
|
47
|
+
arch,
|
|
48
|
+
isWindows,
|
|
49
|
+
isMac,
|
|
50
|
+
isLinux,
|
|
51
|
+
isWSL,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function getInstallInstructions(dependency, osInfo) {
|
|
55
|
+
const instructions = [];
|
|
56
|
+
if (dependency === 'tmux') {
|
|
57
|
+
if (osInfo.isMac) {
|
|
58
|
+
instructions.push('🍎 macOS:');
|
|
59
|
+
instructions.push(' • Using Homebrew: brew install tmux');
|
|
60
|
+
instructions.push(' • Using MacPorts: sudo port install tmux');
|
|
61
|
+
instructions.push(' • Manual: https://github.com/tmux/tmux/wiki/Installing');
|
|
62
|
+
}
|
|
63
|
+
else if (osInfo.isLinux) {
|
|
64
|
+
if (osInfo.isWSL) {
|
|
65
|
+
instructions.push('🐧 WSL/Linux:');
|
|
66
|
+
instructions.push(' • Ubuntu/Debian: sudo apt update && sudo apt install tmux');
|
|
67
|
+
instructions.push(' • CentOS/RHEL: sudo yum install tmux');
|
|
68
|
+
instructions.push(' • Fedora: sudo dnf install tmux');
|
|
69
|
+
instructions.push(' • Arch: sudo pacman -S tmux');
|
|
70
|
+
instructions.push(' • openSUSE: sudo zypper install tmux');
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
instructions.push('🐧 Linux:');
|
|
74
|
+
instructions.push(' • Ubuntu/Debian: sudo apt update && sudo apt install tmux');
|
|
75
|
+
instructions.push(' • CentOS/RHEL: sudo yum install tmux');
|
|
76
|
+
instructions.push(' • Fedora: sudo dnf install tmux');
|
|
77
|
+
instructions.push(' • Arch: sudo pacman -S tmux');
|
|
78
|
+
instructions.push(' • openSUSE: sudo zypper install tmux');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (osInfo.isWindows) {
|
|
82
|
+
instructions.push('🪟 Windows:');
|
|
83
|
+
instructions.push(' • Using Chocolatey: choco install tmux');
|
|
84
|
+
instructions.push(' • Using Scoop: scoop install tmux');
|
|
85
|
+
instructions.push(' • Using WSL: Install in WSL and use from there');
|
|
86
|
+
instructions.push(' • Manual: https://github.com/tmux/tmux/wiki/Installing');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (dependency === 'jq') {
|
|
90
|
+
if (osInfo.isMac) {
|
|
91
|
+
instructions.push('🍎 macOS:');
|
|
92
|
+
instructions.push(' • Using Homebrew: brew install jq');
|
|
93
|
+
instructions.push(' • Using MacPorts: sudo port install jq');
|
|
94
|
+
instructions.push(' • Using Fink: fink install jq');
|
|
95
|
+
instructions.push(' • Manual: https://jqlang.org/download/');
|
|
96
|
+
}
|
|
97
|
+
else if (osInfo.isLinux) {
|
|
98
|
+
if (osInfo.isWSL) {
|
|
99
|
+
instructions.push('🐧 WSL/Linux:');
|
|
100
|
+
instructions.push(' • Ubuntu/Debian: sudo apt update && sudo apt install jq');
|
|
101
|
+
instructions.push(' • CentOS/RHEL: sudo yum install jq');
|
|
102
|
+
instructions.push(' • Fedora: sudo dnf install jq');
|
|
103
|
+
instructions.push(' • Arch: sudo pacman -S jq');
|
|
104
|
+
instructions.push(' • openSUSE: sudo zypper install jq');
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
instructions.push('🐧 Linux:');
|
|
108
|
+
instructions.push(' • Ubuntu/Debian: sudo apt update && sudo apt install jq');
|
|
109
|
+
instructions.push(' • CentOS/RHEL: sudo yum install jq');
|
|
110
|
+
instructions.push(' • Fedora: sudo dnf install jq');
|
|
111
|
+
instructions.push(' • Arch: sudo pacman -S jq');
|
|
112
|
+
instructions.push(' • openSUSE: sudo zypper install jq');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if (osInfo.isWindows) {
|
|
116
|
+
instructions.push('🪟 Windows:');
|
|
117
|
+
instructions.push(' • Using winget: winget install jqlang.jq');
|
|
118
|
+
instructions.push(' • Using Chocolatey: choco install jq');
|
|
119
|
+
instructions.push(' • Using Scoop: scoop install jq');
|
|
120
|
+
instructions.push(' • Using WSL: Install in WSL and use from there');
|
|
121
|
+
instructions.push(' • Manual: https://jqlang.org/download/');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return instructions;
|
|
125
|
+
}
|
|
27
126
|
async function checkDependencies() {
|
|
28
127
|
const missingDeps = [];
|
|
128
|
+
const osInfo = detectOS();
|
|
29
129
|
for (const dep of requiredDependencies) {
|
|
30
130
|
try {
|
|
31
131
|
await new Promise((resolve, reject) => {
|
|
@@ -44,10 +144,19 @@ async function checkDependencies() {
|
|
|
44
144
|
}
|
|
45
145
|
if (missingDeps.length > 0) {
|
|
46
146
|
console.log('❌ Missing required dependencies:');
|
|
147
|
+
console.log(`\n🖥️ Detected OS: ${osInfo.platform} ${osInfo.arch}${osInfo.isWSL ? ' (WSL)' : ''}`);
|
|
47
148
|
for (const dep of missingDeps) {
|
|
48
|
-
console.log(`\n${dep.name} (required for ${dep.requiredFor})`);
|
|
49
|
-
|
|
149
|
+
console.log(`\n📦 ${dep.name} (required for ${dep.requiredFor})`);
|
|
150
|
+
const instructions = getInstallInstructions(dep.name, osInfo);
|
|
151
|
+
if (instructions.length > 0) {
|
|
152
|
+
console.log(' Installation options:');
|
|
153
|
+
instructions.forEach(instruction => console.log(instruction));
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
console.log(` General instructions: ${dep.installMsg}`);
|
|
157
|
+
}
|
|
50
158
|
}
|
|
159
|
+
console.log('\n💡 After installing the dependencies, run Harbor again.');
|
|
51
160
|
throw new Error('Please install missing dependencies before continuing');
|
|
52
161
|
}
|
|
53
162
|
}
|
|
@@ -88,6 +197,7 @@ This is typically the first command you'll run in a new project.`)
|
|
|
88
197
|
.option('-p, --path <path>', 'The path to the root of your project', './')
|
|
89
198
|
.action(async (options) => {
|
|
90
199
|
try {
|
|
200
|
+
await checkDependencies();
|
|
91
201
|
const configExists = checkHasHarborConfig();
|
|
92
202
|
if (configExists) {
|
|
93
203
|
console.log('❌ Error: Harbor project already initialized');
|
|
@@ -107,20 +217,34 @@ program.command('moor')
|
|
|
107
217
|
.description('Add new services to your harbor configuration')
|
|
108
218
|
.option('-p, --path <path>', 'The path to the root of your project', './')
|
|
109
219
|
.action(async (options) => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
220
|
+
try {
|
|
221
|
+
await checkDependencies();
|
|
222
|
+
if (!checkHasHarborConfig()) {
|
|
223
|
+
console.log('❌ No harbor configuration found');
|
|
224
|
+
console.log('\nTo initialize a new Harbor project, please use:');
|
|
225
|
+
console.log(' harbor dock');
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
await generateDevFile(options.path);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
console.log('❌ Error:', err instanceof Error ? err.message : 'Unknown error');
|
|
114
232
|
process.exit(1);
|
|
115
233
|
}
|
|
116
|
-
await generateDevFile(options.path);
|
|
117
234
|
});
|
|
118
235
|
program.command('launch')
|
|
119
236
|
.description(`Launch your services in the harbor terminal multiplexer (Using tmux)
|
|
120
237
|
|
|
121
238
|
Note: This command will stop any active Caddy processes, including those from other Harbor projects.`)
|
|
122
239
|
.action(async () => {
|
|
123
|
-
|
|
240
|
+
try {
|
|
241
|
+
await checkDependencies();
|
|
242
|
+
await runServices();
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
console.log('❌ Error:', err instanceof Error ? err.message : 'Unknown error');
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
124
248
|
});
|
|
125
249
|
program.parse();
|
|
126
250
|
function fileExists(path) {
|
|
@@ -387,6 +511,7 @@ async function runServices() {
|
|
|
387
511
|
console.error('Error reading config:', err);
|
|
388
512
|
process.exit(1);
|
|
389
513
|
}
|
|
514
|
+
ensureLogSetup(config);
|
|
390
515
|
// Execute before scripts
|
|
391
516
|
try {
|
|
392
517
|
await execute(config.before || [], 'before');
|
|
@@ -424,6 +549,27 @@ async function runServices() {
|
|
|
424
549
|
});
|
|
425
550
|
});
|
|
426
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
|
+
}
|
|
427
573
|
// Get the package root directory
|
|
428
574
|
function getPackageRoot() {
|
|
429
575
|
const __filename = fileURLToPath(import.meta.url);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abyrd9/harbor-cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A CLI tool for
|
|
3
|
+
"version": "2.0.0",
|
|
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": {
|
|
7
7
|
"harbor": "dist/index.js"
|
|
@@ -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"
|