@humanu/orchestra 0.5.49 → 0.5.53

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.
Files changed (35) hide show
  1. package/README.md +1 -0
  2. package/bin/orchestra.js +4 -1
  3. package/install.js +25 -2
  4. package/package.json +1 -1
  5. package/resources/api/tmux.sh +20 -5
  6. package/resources/prebuilt/linux-x64/gw-env-copy +0 -0
  7. package/resources/prebuilt/linux-x64/orchestra +0 -0
  8. package/resources/prebuilt/macos-arm64/gw-env-copy +0 -0
  9. package/resources/prebuilt/macos-arm64/orchestra +0 -0
  10. package/resources/prebuilt/macos-intel/gw-env-copy +0 -0
  11. package/resources/prebuilt/macos-intel/orchestra +0 -0
  12. package/resources/scripts/gw-bridge.sh +43 -3
  13. package/resources/scripts/gw.sh +4 -7
  14. package/resources/scripts/shell/AGENTS.md +13 -29
  15. package/resources/scripts/shell/bridge/ai.sh +3 -3
  16. package/resources/scripts/shell/env_copy_command.sh +29 -0
  17. package/resources/scripts/shell/git/bridge_create_worktree.sh +8 -3
  18. package/resources/scripts/shell/git/bridge_delete_branch_only.sh +6 -8
  19. package/resources/scripts/shell/git/bridge_delete_worktree_only.sh +6 -10
  20. package/resources/scripts/shell/git/checkout_worktree.sh +3 -3
  21. package/resources/scripts/shell/git/create_worktree.sh +2 -2
  22. package/resources/scripts/shell/gw_env_copy.sh +65 -0
  23. package/resources/scripts/shell/gw_load.sh +1 -0
  24. package/resources/scripts/shell/gwr/check-updates.sh +24 -7
  25. package/resources/scripts/shell/tmux/new_session_command.sh +2 -2
  26. package/resources/scripts/copy_env.sh +0 -251
  27. package/resources/scripts/shell/bridge/copy_env.sh +0 -84
  28. package/resources/scripts/shell/env/copy_env_command.sh +0 -27
  29. package/resources/scripts/shell/env/copy_env_constants.sh +0 -3
  30. package/resources/scripts/shell/env/copy_env_core.sh +0 -178
  31. package/resources/scripts/shell/env/copy_env_debug_parse.sh +0 -14
  32. package/resources/scripts/shell/env/copy_env_load.sh +0 -9
  33. package/resources/scripts/shell/env/copy_env_locations.sh +0 -34
  34. package/resources/scripts/shell/env/copy_env_logging.sh +0 -17
  35. package/resources/scripts/shell/env/copy_env_state.sh +0 -5
package/README.md CHANGED
@@ -16,6 +16,7 @@ npm install -g @humanu/orchestra
16
16
 
17
17
  ```bash
18
18
  orchestra # Launch the Rust TUI
19
+ orchestra upgrade # Check for available updates
19
20
  ```
20
21
 
21
22
  For full documentation and release notes, visit https://github.com/humanunsupervised/orchestra
package/bin/orchestra.js CHANGED
@@ -4,7 +4,10 @@ const path = require('path');
4
4
  const { spawnSync } = require('child_process');
5
5
 
6
6
  const installScript = path.join(__dirname, '..', 'install.js');
7
- const result = spawnSync(process.execPath, [installScript, '--invoke=orchestra', ...process.argv.slice(2)], {
7
+ const args = process.argv.slice(2);
8
+ const upgradeCommands = new Set(['upgrade', 'update', '--update', '--check-updates']);
9
+ const invoke = upgradeCommands.has(args[0]) ? 'gw' : 'orchestra';
10
+ const result = spawnSync(process.execPath, [installScript, `--invoke=${invoke}`, ...args], {
8
11
  stdio: 'inherit',
9
12
  });
10
13
  process.exit(result.status ?? 0);
package/install.js CHANGED
@@ -5,7 +5,8 @@ const fs = require('fs');
5
5
  const path = require('path');
6
6
 
7
7
  const BINARY_NAME = 'orchestra';
8
- const SHELL_SCRIPTS = ['gwr.sh', 'gw.sh', 'gw-bridge.sh', 'copy_env.sh', 'orchestra-local.sh'];
8
+ const ENV_COPY_NAME = 'gw-env-copy';
9
+ const SHELL_SCRIPTS = ['gwr.sh', 'gw.sh', 'gw-bridge.sh', 'orchestra-local.sh'];
9
10
  const SHELL_DIRS = ['shell'];
10
11
  const SUPPORTED_PLATFORMS = {
11
12
  'darwin-x64': 'macos-intel',
@@ -135,6 +136,7 @@ function linkCompatibilityBinary(binaryPath) {
135
136
  function installPrebuiltBinary() {
136
137
  const platform = getPlatformKey();
137
138
  const source = path.join(prebuiltDir, platform, BINARY_NAME);
139
+ const envCopySource = path.join(prebuiltDir, platform, ENV_COPY_NAME);
138
140
 
139
141
  if (!fs.existsSync(source)) {
140
142
  console.log(`⚠️ No prebuilt binary found for ${platform}`);
@@ -159,6 +161,13 @@ function installPrebuiltBinary() {
159
161
  fs.copyFileSync(source, destination);
160
162
  fs.chmodSync(destination, 0o755);
161
163
  linkCompatibilityBinary(destination);
164
+ if (fs.existsSync(envCopySource)) {
165
+ const envCopyDestination = path.join(distDir, ENV_COPY_NAME);
166
+ fs.copyFileSync(envCopySource, envCopyDestination);
167
+ fs.chmodSync(envCopyDestination, 0o755);
168
+ } else {
169
+ console.warn(`⚠️ Missing ${ENV_COPY_NAME} for ${platform}`);
170
+ }
162
171
  return true;
163
172
  }
164
173
 
@@ -226,12 +235,26 @@ function buildFromSource() {
226
235
  fs.copyFileSync(builtBinary, destination);
227
236
  fs.chmodSync(destination, 0o755);
228
237
  linkCompatibilityBinary(destination);
238
+
239
+ const envCopyBinary = path.join(projectRoot, 'gw-tui', 'target', 'release', ENV_COPY_NAME);
240
+ if (!fs.existsSync(envCopyBinary)) {
241
+ console.error(`❌ ${ENV_COPY_NAME} not found after build.`);
242
+ console.error(' Expected location:', envCopyBinary);
243
+ process.exit(1);
244
+ }
245
+ const envCopyDestination = path.join(distDir, ENV_COPY_NAME);
246
+ fs.copyFileSync(envCopyBinary, envCopyDestination);
247
+ fs.chmodSync(envCopyDestination, 0o755);
229
248
 
230
249
  console.log('✅ Successfully built from source!');
231
250
  }
232
251
 
233
252
  function assetsReady() {
234
- return fs.existsSync(path.join(distDir, BINARY_NAME)) && fs.existsSync(path.join(distDir, 'gwr.sh'));
253
+ return (
254
+ fs.existsSync(path.join(distDir, BINARY_NAME)) &&
255
+ fs.existsSync(path.join(distDir, ENV_COPY_NAME)) &&
256
+ fs.existsSync(path.join(distDir, 'gwr.sh'))
257
+ );
235
258
  }
236
259
 
237
260
  function printShellWrapperInstructions(binaryPath) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanu/orchestra",
3
- "version": "0.5.49",
3
+ "version": "0.5.53",
4
4
  "description": "AI-powered Git worktree and tmux session manager with modern TUI",
5
5
  "keywords": [
6
6
  "git",
@@ -435,20 +435,35 @@ tmux_list_sessions_for_slug() {
435
435
  d="$(_tmux_delim)"
436
436
  ORCH_PREFIX="$(_tmux_orch_prefix)"
437
437
 
438
- # If worktree_path provided, match both old (absolute-path hash) and new (slug hash)
438
+ # If worktree_path provided, match hash-based and no-hash prefixes
439
439
  if [[ -n "$worktree_path" ]]; then
440
- local hash_slug hash_path
440
+ local hash_slug hash_path branch_name
441
441
  hash_slug="$(tmux_path_hash "$slug")"
442
442
  hash_path="$(tmux_path_hash "$worktree_path")"
443
+ branch_name="$(git_worktree_path_to_branch "$worktree_path" 2>/dev/null || true)"
444
+
443
445
  local p1_new="${ORCH_PREFIX}${slug}${d}${hash_slug}${d}"
444
446
  local p2_new="${slug}${d}${hash_slug}${d}"
445
447
  local p1_old="${ORCH_PREFIX}${slug}${d}${hash_path}${d}"
446
448
  local p2_old="${slug}${d}${hash_path}${d}"
447
-
448
- # List sessions with either hash variant
449
+ local p1_plain="${ORCH_PREFIX}${slug}${d}"
450
+ local p2_plain="${slug}${d}"
451
+
452
+ local p1_branch_hash=""
453
+ local p2_branch_hash=""
454
+ local p1_branch_plain=""
455
+ local p2_branch_plain=""
456
+ if [[ -n "$branch_name" ]]; then
457
+ p1_branch_hash="${ORCH_PREFIX}${branch_name}${d}${hash_path}${d}"
458
+ p2_branch_hash="${branch_name}${d}${hash_path}${d}"
459
+ p1_branch_plain="${ORCH_PREFIX}${branch_name}${d}"
460
+ p2_branch_plain="${branch_name}${d}"
461
+ fi
462
+
463
+ # List sessions with any known prefix variant
449
464
  tmux list-sessions -F '#{session_name}|||#{session_last_attached}|||#{session_activity}' 2>/dev/null \
450
465
  | sed 's/|||/\t/g' \
451
- | awk -v a="$p1_new" -v b="$p2_new" -v c="$p1_old" -v d="$p2_old" 'BEGIN{FS="\t"} index($1, a)==1 || index($1, b)==1 || index($1, c)==1 || index($1, d)==1 {print $1"\t"$2"\t"$3}' \
466
+ | awk -v a="$p1_new" -v b="$p2_new" -v c="$p1_old" -v d="$p2_old" -v e="$p1_plain" -v f="$p2_plain" -v g="$p1_branch_hash" -v h="$p2_branch_hash" -v i="$p1_branch_plain" -v j="$p2_branch_plain" 'BEGIN{FS="\t"} (length(a)>0 && index($1, a)==1) || (length(b)>0 && index($1, b)==1) || (length(c)>0 && index($1, c)==1) || (length(d)>0 && index($1, d)==1) || (length(e)>0 && index($1, e)==1) || (length(f)>0 && index($1, f)==1) || (length(g)>0 && index($1, g)==1) || (length(h)>0 && index($1, h)==1) || (length(i)>0 && index($1, i)==1) || (length(j)>0 && index($1, j)==1) {print $1"\t"$2"\t"$3}' \
452
467
  | sort -t $'\t' -k2,2nr -k3,3nr \
453
468
  | awk -F '\t' '{print $1}' | awk '!seen[$0]++' || true
454
469
  else
@@ -16,14 +16,12 @@ set -euo pipefail
16
16
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
17
  source "$SCRIPT_DIR/api/git.sh"
18
18
  source "$SCRIPT_DIR/api/tmux.sh"
19
- source "$SCRIPT_DIR/shell/env/copy_env_load.sh"
20
- copy_env_init
19
+ source "$SCRIPT_DIR/shell/gw_env_copy.sh"
21
20
 
22
21
  # Source bridge modules
23
22
  source "$SCRIPT_DIR/shell/bridge/utils.sh"
24
23
  source "$SCRIPT_DIR/shell/bridge/tmux.sh"
25
24
  source "$SCRIPT_DIR/shell/bridge/ai.sh"
26
- source "$SCRIPT_DIR/shell/bridge/copy_env.sh"
27
25
 
28
26
  # Source git bridge modules (now modular)
29
27
  source "$SCRIPT_DIR/shell/git/bridge_worktree.sh"
@@ -37,6 +35,48 @@ err() { printf '❌ %s\n' "$*" >&2; }
37
35
  info() { printf '%s\n' "$*"; }
38
36
  have_cmd() { command -v "$1" >/dev/null 2>&1; }
39
37
 
38
+ bridge_copy_env_files() {
39
+ if [[ -z "${1:-}" ]] || [[ -z "${2:-}" ]]; then
40
+ json_error "Source and target paths required"
41
+ return 1
42
+ fi
43
+ local source_path="$1"
44
+ local target_path="$2"
45
+ local raw_locations="${3-}"
46
+
47
+ local repo_root
48
+ repo_root="$(cd "$source_path" 2>/dev/null && git rev-parse --show-toplevel 2>/dev/null || printf '%s' "$source_path")"
49
+ local shared_root
50
+ shared_root="$(cd "$source_path" 2>/dev/null && git rev-parse --git-common-dir 2>/dev/null || echo "")"
51
+ if [[ -n "$shared_root" ]]; then
52
+ if [[ "$shared_root" != /* ]]; then
53
+ shared_root="$(cd "$source_path" 2>/dev/null && pwd -P)/$shared_root"
54
+ fi
55
+ shared_root="$(dirname "$shared_root")"
56
+ else
57
+ shared_root="$repo_root"
58
+ fi
59
+
60
+ local bin
61
+ if ! bin="$(gw_env_copy_bin)"; then
62
+ json_error "gw-env-copy binary not found"
63
+ return 1
64
+ fi
65
+
66
+ local args=(
67
+ --source "$source_path"
68
+ --target "$target_path"
69
+ --repo "$shared_root"
70
+ --format boolean
71
+ --quiet
72
+ )
73
+ if [[ -n "$raw_locations" ]]; then
74
+ args+=(--locations-json "$raw_locations")
75
+ fi
76
+
77
+ "$bin" "${args[@]}"
78
+ }
79
+
40
80
  # Ensure JSON backend is available (jq preferred, python/node fallback)
41
81
  bridge_init_json_backend
42
82
 
@@ -47,16 +47,13 @@ fi
47
47
 
48
48
  # Source command-line operations
49
49
  COMMANDS_DIR="$SCRIPT_DIR/shell"
50
- source "$SCRIPT_DIR/shell/env/copy_env_load.sh"
51
- copy_env_init
52
-
53
50
  for command_file in \
54
51
  "git/create_worktree.sh" \
55
52
  "git/delete_worktree.sh" \
56
53
  "git/list_worktrees.sh" \
57
54
  "git/checkout_worktree.sh" \
58
55
  "git/status.sh" \
59
- "env/copy_env_command.sh" \
56
+ "env_copy_command.sh" \
60
57
  "tmux/new_session_command.sh"
61
58
  do
62
59
  if [[ -f "$COMMANDS_DIR/$command_file" ]]; then
@@ -137,7 +134,7 @@ usage() {
137
134
  echo " ch, checkout <branch> Switch to the worktree of <branch>"
138
135
  echo " copy-env <worktreename> Copy env files from current worktree to target"
139
136
  echo " status [git status args] Show current worktree + git status"
140
- echo " update, --update Check for available updates"
137
+ echo " upgrade, update, --update Check for available updates"
141
138
  echo ""
142
139
  echo "Tmux flags:"
143
140
  echo " --branch <branch> Branch name for tmux session worktree"
@@ -149,7 +146,7 @@ usage() {
149
146
  echo " $(basename "$0") ls # list worktrees only"
150
147
  echo " $(basename "$0") ch -b feat/x # create and switch to worktree"
151
148
  echo " $(basename "$0") --branch main --new-tmux --cmd \"git status\""
152
- echo " $(basename "$0") update # check for available updates"
149
+ echo " $(basename "$0") upgrade # check for available updates"
153
150
  echo ""
154
151
  echo "Note: tmux session management is available via flags or the interactive menu."
155
152
  }
@@ -242,7 +239,7 @@ case "$COMMAND" in
242
239
  ch|checkout) cmd_checkout_worktree "$@" ;;
243
240
  status) cmd_status "$@" ;;
244
241
  copy-env) cmd_copy_env "$@" ;;
245
- update|--update) "$SCRIPT_DIR/shell/gwr/check-updates.sh" ;;
242
+ upgrade|update|--update) "$SCRIPT_DIR/shell/gwr/check-updates.sh" ;;
246
243
  help|-h|--help) usage ;;
247
244
  *) err "Unknown command '$COMMAND'"; usage ;;
248
245
  esac
@@ -20,7 +20,7 @@ shell/
20
20
  ├── gw_git.sh # Git operations for gw CLI
21
21
  ├── gw_worktree.sh # Worktree management for gw CLI
22
22
  ├── gw_usage.sh # Usage text for gw command
23
- ├── copy_env.sh # Environment variable copying system
23
+ ├── gw_env_copy.sh # Rust env copy helper/wrapper
24
24
  ├── commands.sh # Shared command utilities
25
25
  ├── build/ # Build system components
26
26
  │ ├── build_bridge.sh # Bridge building logic
@@ -30,15 +30,6 @@ shell/
30
30
  │ ├── build_logging.sh # Build-specific logging
31
31
  │ ├── build_rust.sh # Rust compilation logic
32
32
  │ └── build_usage.sh # Build command usage
33
- ├── env/ # Environment management
34
- │ ├── copy_env_command.sh # Command processing for env copying
35
- │ ├── copy_env_constants.sh # Constants and paths
36
- │ ├── copy_env_core.sh # Core environment copying logic
37
- │ ├── copy_env_debug_parse.sh # Debug parsing for env copying
38
- │ ├── copy_env_load.sh # Environment loader
39
- │ ├── copy_env_locations.sh # Path and location management
40
- │ ├── copy_env_logging.sh # Environment copying logging
41
- │ └── copy_env_state.sh # State management for env copying
42
33
  ├── git/ # Git-specific utilities
43
34
  │ └── gwr_worktree_title.sh # Worktree title generation
44
35
  └── gwr/ # gwr-specific components
@@ -122,13 +113,12 @@ shell/
122
113
 
123
114
  ## Shared System Files
124
115
 
125
- ### copy_env.sh
126
- **Purpose**: Environment variable copying system
127
- **Usage**: Copies environment between shell contexts
116
+ ### gw_env_copy.sh
117
+ **Purpose**: Rust env copy helper for CLI and bridge flows
118
+ **Usage**: Locates and invokes the `gw-env-copy` binary
128
119
  **Key Functions**:
129
- - `copy_env()` - Main function for environment copying
130
- - Critical for worktree directory changes and session management
131
- - Used by both gwr and gw systems
120
+ - `gw_env_copy_bin()` - Resolve the env copy binary path
121
+ - `gw_env_copy_files()` - Execute env copy with repo config
132
122
 
133
123
  ### commands.sh
134
124
  **Purpose**: Shared command utilities
@@ -157,17 +147,11 @@ shell/
157
147
  **Usage**: Handle specific aspects of the build process
158
148
  **Key Functions**: Each focuses on a specific build operation
159
149
 
160
- ## Environment System (env/)
150
+ ## Rust Env Copy
161
151
 
162
- ### copy_env_core.sh
163
- **Purpose**: Core environment copying logic
164
- **Usage**: Main implementation of environment variable copying
165
- **Key Functions**: `copy_env()` - Core function for environment management
166
-
167
- ### copy_env_command.sh, copy_env_constants.sh, copy_env_debug_parse.sh, copy_env_load.sh, copy_env_locations.sh, copy_env_logging.sh, copy_env_state.sh
168
- **Purpose**: Specialized environment copying components
169
- **Usage**: Handle specific aspects of environment management
170
- **Key Functions**: Each module handles a specific aspect of the complex environment copying system
152
+ Environment file copying is implemented in Rust via the `gw-env-copy` binary.
153
+ Shell wrappers invoke the binary and read `.orchestra/options.json` for
154
+ `copy_locations` when needed.
171
155
 
172
156
  ## Git Utilities (git/)
173
157
 
@@ -196,7 +180,7 @@ shell/
196
180
 
197
181
  ### gw.sh Integration
198
182
  - Sources `gw_load.sh` to load all gw components
199
- - Uses shared `copy_env.sh` for environment management
183
+ - Uses `gw_env_copy.sh` to invoke the Rust env copy binary
200
184
  - Provides CLI interface to same underlying functionality
201
185
 
202
186
  ### Bridge System Integration
@@ -209,7 +193,7 @@ shell/
209
193
  1. **Modular Loading**: Each system (gwr/gw) has its own loader that sources components in dependency order
210
194
  2. **Color Consistency**: Separate color files ensure consistent theming across components
211
195
  3. **Logging Standardization**: Standardized logging functions provide consistent output
212
- 4. **Environment Isolation**: Complex environment copying system handles shell context changes
196
+ 4. **Environment Isolation**: Rust env copy binary handles .env file replication across worktrees
213
197
  5. **Binary Discovery**: Sophisticated binary finding supports development, build, and installed scenarios
214
198
  6. **Update Integration**: Update checking integrated into main launcher with installation method detection
215
199
 
@@ -233,4 +217,4 @@ shell/
233
217
  3. Follow existing build system patterns
234
218
  4. Update `build_usage.sh` for new build options
235
219
 
236
- This documentation provides the foundation for AI agents to understand and work effectively with the Orchestrator shell system.
220
+ This documentation provides the foundation for AI agents to understand and work effectively with the Orchestrator shell system.
@@ -160,8 +160,8 @@ bridge_manual_rename_session() {
160
160
  return 0
161
161
  fi
162
162
 
163
- # Validate new display name (alphanumeric and underscore only)
164
- if [[ ! "$new_display_name" =~ ^[a-zA-Z0-9_]+$ ]]; then
163
+ # Validate new display name (alphanumeric, underscore, hyphen only)
164
+ if [[ ! "$new_display_name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
165
165
  echo "\"invalid_name\""
166
166
  return 0
167
167
  fi
@@ -172,4 +172,4 @@ bridge_manual_rename_session() {
172
172
  else
173
173
  echo "\"rename_failed\""
174
174
  fi
175
- }
175
+ }
@@ -0,0 +1,29 @@
1
+ # shellcheck shell=bash
2
+
3
+ cmd_copy_env() {
4
+ local target_branch="${1-}"
5
+ if [[ -z "$target_branch" ]]; then
6
+ err "Target worktree name required"
7
+ echo "Usage: gw copy-env <worktreename>"
8
+ return 1
9
+ fi
10
+
11
+ git_require_repo || return 1
12
+
13
+ local target_path
14
+ target_path="$(git_branch_to_worktree_path "$target_branch")"
15
+ if [[ -z "$target_path" ]]; then
16
+ err "Worktree for branch '$target_branch' not found"
17
+ return 1
18
+ fi
19
+
20
+ local root
21
+ root="$(git_repo_root)"
22
+ local shared_root
23
+ shared_root="$(git_shared_root 2>/dev/null)"
24
+ [[ -z "$shared_root" ]] && shared_root="$root"
25
+
26
+ info "Copying env files from '$root' -> '$target_path'"
27
+ gw_env_copy_files "$root" "$target_path" "$shared_root"
28
+ info "Env files synced to '$target_branch'"
29
+ }
@@ -25,11 +25,16 @@ bridge_create_worktree() {
25
25
  shared_root="$(cd "$source_path" 2>/dev/null && git rev-parse --show-toplevel 2>/dev/null || printf '%s' "$source_path")"
26
26
  fi
27
27
 
28
- copy_env_files "$source_path" "$target_path" "$shared_root" 1 >/dev/null
29
- shopt -u nullglob
28
+ local env_copy_bin
29
+ if ! env_copy_bin="$(gw_env_copy_bin)"; then
30
+ json_error "gw-env-copy binary not found"
31
+ return 1
32
+ fi
33
+
34
+ "$env_copy_bin" --source "$source_path" --target "$target_path" --repo "$shared_root" --format boolean --quiet >/dev/null
30
35
 
31
36
  echo "\"$worktree_path\""
32
37
  else
33
38
  json_error "Not a git repository"
34
39
  fi
35
- }
40
+ }
@@ -2,8 +2,8 @@
2
2
 
3
3
  # shellcheck shell=bash
4
4
 
5
- # Delete worktree only
6
- bridge_delete_worktree_only() {
5
+ # Delete branch only
6
+ bridge_delete_branch_only() {
7
7
  if [[ -z "${1:-}" ]]; then
8
8
  json_error "Branch name required"
9
9
  return 1
@@ -11,15 +11,13 @@ bridge_delete_worktree_only() {
11
11
  branch_name="$1"
12
12
 
13
13
  if git_require_repo_root >/dev/null 2>&1; then
14
- worktree_path="$(git_branch_to_worktree_path "$branch_name")"
15
-
16
- if [[ -n "$worktree_path" ]]; then
17
- git_remove_worktree "$worktree_path"
14
+ if git_branch_exists "$branch_name"; then
15
+ git_delete_branch "$branch_name"
18
16
  echo "true"
19
17
  else
20
- json_error "No worktree found for branch: $branch_name"
18
+ json_error "Branch not found: $branch_name"
21
19
  fi
22
20
  else
23
21
  json_error "Not a git repository"
24
22
  fi
25
- }
23
+ }
@@ -2,8 +2,8 @@
2
2
 
3
3
  # shellcheck shell=bash
4
4
 
5
- # Delete worktree
6
- bridge_delete_worktree() {
5
+ # Delete worktree only
6
+ bridge_delete_worktree_only() {
7
7
  if [[ -z "${1:-}" ]]; then
8
8
  json_error "Branch name required"
9
9
  return 1
@@ -12,17 +12,13 @@ bridge_delete_worktree() {
12
12
 
13
13
  if git_require_repo_root >/dev/null 2>&1; then
14
14
  worktree_path="$(git_branch_to_worktree_path "$branch_name")"
15
-
16
15
  if [[ -n "$worktree_path" ]]; then
17
16
  git_remove_worktree "$worktree_path"
17
+ echo "true"
18
+ else
19
+ json_error "No worktree found for branch: $branch_name"
18
20
  fi
19
-
20
- if git_branch_exists "$branch_name"; then
21
- git_delete_branch "$branch_name"
22
- fi
23
-
24
- echo "true"
25
21
  else
26
22
  json_error "Not a git repository"
27
23
  fi
28
- }
24
+ }
@@ -6,7 +6,7 @@ cmd_checkout_worktree() {
6
6
  local root; root="$(git_require_repo_root)" || return 1
7
7
  local shared_root; shared_root="$(git_shared_root 2>/dev/null)"
8
8
  [[ -z "$shared_root" ]] && shared_root="$root"
9
- local source_root="$(git_current_path)"
9
+ local source_root="$root"
10
10
 
11
11
  if [[ "${1-}" == "-b" ]]; then
12
12
  [[ -z "${2-}" ]] && { err "Branch name required for -b"; echo "Usage: gw checkout -b <branch>"; return 1; }
@@ -30,7 +30,7 @@ cmd_checkout_worktree() {
30
30
  # Create worktree for existing branch
31
31
  info "🌳 Creating worktree for existing branch '$branch_name'..."
32
32
  wt="$(git_ensure_worktree_for_branch "$branch_name")"
33
- copy_env_files "$source_root" "$wt" "$shared_root"
33
+ gw_env_copy_files "$source_root" "$wt" "$shared_root"
34
34
  echo "cd \"$wt\""
35
35
  fi
36
36
  return 0
@@ -57,7 +57,7 @@ cmd_checkout_worktree() {
57
57
  if git_branch_exists "$branch_name"; then
58
58
  info "🌳 Creating worktree for existing branch '$branch_name'..."
59
59
  wt="$(git_ensure_worktree_for_branch "$branch_name")"
60
- copy_env_files "$source_root" "$wt" "$shared_root"
60
+ gw_env_copy_files "$source_root" "$wt" "$shared_root"
61
61
  echo "cd \"$wt\""
62
62
  return 0
63
63
  else
@@ -10,13 +10,13 @@ cmd_create_worktree() {
10
10
  local root; root="$(git_require_repo_root)" || return 1
11
11
  local shared_root; shared_root="$(git_shared_root 2>/dev/null)"
12
12
  [[ -z "$shared_root" ]] && shared_root="$root"
13
- local source_root="$(git_current_path)"
13
+ local source_root="$root"
14
14
 
15
15
  local dir; dir="$(git_build_worktree_path "$branch_name")"
16
16
 
17
17
  info "🌳 Creating branch '$branch_name' and worktree at '$dir'..."
18
18
  dir="$(git_create_branch_and_worktree "$branch_name")"
19
- copy_env_files "$source_root" "$dir" "$shared_root"
19
+ gw_env_copy_files "$source_root" "$dir" "$shared_root"
20
20
  info "✅ Success! Worktree created at '$dir'."
21
21
  echo "cd \"$dir\""
22
22
  (cd "$dir" && git_status_short)
@@ -0,0 +1,65 @@
1
+ # shellcheck shell=bash
2
+
3
+ gw_env_copy_bin() {
4
+ local candidate=""
5
+
6
+ if [[ -n "${GW_ENV_COPY_BIN:-}" && -x "$GW_ENV_COPY_BIN" ]]; then
7
+ printf '%s\n' "$GW_ENV_COPY_BIN"
8
+ return 0
9
+ fi
10
+
11
+ if [[ -n "${SCRIPT_DIR:-}" ]]; then
12
+ candidate="$SCRIPT_DIR/gw-env-copy"
13
+ if [[ -x "$candidate" ]]; then
14
+ printf '%s\n' "$candidate"
15
+ return 0
16
+ fi
17
+ candidate="$SCRIPT_DIR/gw-tui/target/release/gw-env-copy"
18
+ if [[ -x "$candidate" ]]; then
19
+ printf '%s\n' "$candidate"
20
+ return 0
21
+ fi
22
+ candidate="$SCRIPT_DIR/gw-tui/target/debug/gw-env-copy"
23
+ if [[ -x "$candidate" ]]; then
24
+ printf '%s\n' "$candidate"
25
+ return 0
26
+ fi
27
+ fi
28
+
29
+ if [[ -n "${GW_ORCHESTRATOR_ROOT:-}" ]]; then
30
+ candidate="$GW_ORCHESTRATOR_ROOT/gw-env-copy"
31
+ if [[ -x "$candidate" ]]; then
32
+ printf '%s\n' "$candidate"
33
+ return 0
34
+ fi
35
+ fi
36
+
37
+ if command -v gw-env-copy >/dev/null 2>&1; then
38
+ command -v gw-env-copy
39
+ return 0
40
+ fi
41
+
42
+ return 1
43
+ }
44
+
45
+ gw_env_copy_files() {
46
+ local source_root="$1"
47
+ local target_root="$2"
48
+ local repo_root="${3:-$source_root}"
49
+
50
+ local bin
51
+ if ! bin="$(gw_env_copy_bin)"; then
52
+ if declare -f err >/dev/null 2>&1; then
53
+ err "gw-env-copy binary not found"
54
+ else
55
+ printf 'Error: %s\n' "gw-env-copy binary not found" >&2
56
+ fi
57
+ return 1
58
+ fi
59
+
60
+ "$bin" \
61
+ --source "$source_root" \
62
+ --target "$target_root" \
63
+ --repo "$repo_root" \
64
+ --format text
65
+ }
@@ -5,5 +5,6 @@ gw_load_core() {
5
5
  source "$SCRIPT_DIR/shell/gw_info.sh"
6
6
  source "$SCRIPT_DIR/shell/gw_debug.sh"
7
7
  source "$SCRIPT_DIR/shell/gw_have_cmd.sh"
8
+ source "$SCRIPT_DIR/shell/gw_env_copy.sh"
8
9
  source "$SCRIPT_DIR/shell/gw_legacy_wrappers.sh"
9
10
  }
@@ -37,9 +37,17 @@ get_npm_latest_version() {
37
37
  # Function to get latest homebrew version
38
38
  get_brew_latest_version() {
39
39
  if command -v brew >/dev/null 2>&1; then
40
- brew info --json "$BREW_FORMULA" 2>/dev/null | \
41
- python3 -c "import sys, json; data = json.load(sys.stdin); print(data[0]['versions']['stable'])" 2>/dev/null || \
42
- echo "unknown"
40
+ if command -v jq >/dev/null 2>&1; then
41
+ brew info --json=v2 "$BREW_FORMULA" 2>/dev/null | \
42
+ jq -r '.[0].versions.stable // "unknown"' 2>/dev/null || \
43
+ echo "unknown"
44
+ elif command -v node >/dev/null 2>&1; then
45
+ brew info --json=v2 "$BREW_FORMULA" 2>/dev/null | \
46
+ node -e "const fs=require('fs');const d=JSON.parse(fs.readFileSync(0,'utf8'));console.log(d[0]?.versions?.stable||'unknown');" 2>/dev/null || \
47
+ echo "unknown"
48
+ else
49
+ echo "unknown"
50
+ fi
43
51
  else
44
52
  echo "brew_not_found"
45
53
  fi
@@ -106,9 +114,18 @@ check_updates() {
106
114
  latest_version=$(get_npm_latest_version)
107
115
  ;;
108
116
  "brew")
109
- current_version=$(brew info --json "$BREW_FORMULA" 2>/dev/null | \
110
- python3 -c "import sys, json; data = json.load(sys.stdin); print(data[0]['installed'][0]['version'])" 2>/dev/null || \
111
- echo "$CURRENT_VERSION")
117
+ if command -v jq >/dev/null 2>&1; then
118
+ current_version=$(brew info --json=v2 "$BREW_FORMULA" 2>/dev/null | \
119
+ jq -r '.[0].installed[0].version // ""' 2>/dev/null)
120
+ elif command -v node >/dev/null 2>&1; then
121
+ current_version=$(brew info --json=v2 "$BREW_FORMULA" 2>/dev/null | \
122
+ node -e "const fs=require('fs');const d=JSON.parse(fs.readFileSync(0,'utf8'));console.log(d[0]?.installed?.[0]?.version||'');" 2>/dev/null)
123
+ else
124
+ current_version=""
125
+ fi
126
+ if [[ -z "$current_version" ]]; then
127
+ current_version="$CURRENT_VERSION"
128
+ fi
112
129
  latest_version=$(get_brew_latest_version)
113
130
  ;;
114
131
  "local")
@@ -171,4 +188,4 @@ check_updates() {
171
188
  }
172
189
 
173
190
  # Run the update check
174
- check_updates "$@"
191
+ check_updates "$@"