@humanu/orchestra 0.5.46 → 0.5.52

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 (33) 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 +7 -2
  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 +55 -8
  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/checkout_worktree.sh +3 -3
  19. package/resources/scripts/shell/git/create_worktree.sh +2 -2
  20. package/resources/scripts/shell/gw_env_copy.sh +65 -0
  21. package/resources/scripts/shell/gw_load.sh +1 -0
  22. package/resources/scripts/shell/gwr/check-updates.sh +24 -7
  23. package/resources/scripts/shell/tmux/new_session_command.sh +54 -0
  24. package/resources/scripts/copy_env.sh +0 -244
  25. package/resources/scripts/shell/bridge/copy_env.sh +0 -84
  26. package/resources/scripts/shell/env/copy_env_command.sh +0 -27
  27. package/resources/scripts/shell/env/copy_env_constants.sh +0 -3
  28. package/resources/scripts/shell/env/copy_env_core.sh +0 -171
  29. package/resources/scripts/shell/env/copy_env_debug_parse.sh +0 -14
  30. package/resources/scripts/shell/env/copy_env_load.sh +0 -9
  31. package/resources/scripts/shell/env/copy_env_locations.sh +0 -34
  32. package/resources/scripts/shell/env/copy_env_logging.sh +0 -17
  33. 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.46",
3
+ "version": "0.5.52",
4
4
  "description": "AI-powered Git worktree and tmux session manager with modern TUI",
5
5
  "keywords": [
6
6
  "git",
@@ -737,8 +737,13 @@ tmux_session_preview() {
737
737
  else
738
738
  echo "<<<IDLE:false>>>"
739
739
  fi
740
-
741
- echo "$txt"
740
+
741
+ # If no content after processing, show placeholder
742
+ if [[ -z "$txt" ]]; then
743
+ echo "(session active, no visible output)"
744
+ else
745
+ echo "$txt"
746
+ fi
742
747
  }
743
748
 
744
749
  # --------------------------- Advanced Operations ----------------------------
@@ -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,14 @@ 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" \
57
+ "tmux/new_session_command.sh"
60
58
  do
61
59
  if [[ -f "$COMMANDS_DIR/$command_file" ]]; then
62
60
  # shellcheck source=/dev/null
@@ -136,15 +134,21 @@ usage() {
136
134
  echo " ch, checkout <branch> Switch to the worktree of <branch>"
137
135
  echo " copy-env <worktreename> Copy env files from current worktree to target"
138
136
  echo " status [git status args] Show current worktree + git status"
139
- echo " update, --update Check for available updates"
137
+ echo " upgrade, update, --update Check for available updates"
138
+ echo ""
139
+ echo "Tmux flags:"
140
+ echo " --branch <branch> Branch name for tmux session worktree"
141
+ echo " --new-tmux Create a new tmux session for the branch"
142
+ echo " --cmd <command> Command to run inside the new session"
140
143
  echo ""
141
144
  echo "Examples:"
142
145
  echo " $(basename "$0") # interactive picker (tmux session management)"
143
146
  echo " $(basename "$0") ls # list worktrees only"
144
147
  echo " $(basename "$0") ch -b feat/x # create and switch to worktree"
145
- echo " $(basename "$0") update # check for available updates"
148
+ echo " $(basename "$0") --branch main --new-tmux --cmd \"git status\""
149
+ echo " $(basename "$0") upgrade # check for available updates"
146
150
  echo ""
147
- echo "Note: tmux session management is only available via the interactive menu (no args)."
151
+ echo "Note: tmux session management is available via flags or the interactive menu."
148
152
  }
149
153
 
150
154
 
@@ -184,6 +188,49 @@ if [ $# -eq 0 ]; then
184
188
  exit 0
185
189
  fi
186
190
 
191
+ new_tmux=false
192
+ tmux_branch=""
193
+ tmux_cmd=""
194
+ while [[ $# -gt 0 ]]; do
195
+ case "$1" in
196
+ --branch|-b)
197
+ [[ $# -lt 2 ]] && { err "--branch requires a value"; exit 1; }
198
+ tmux_branch="$2"
199
+ shift 2
200
+ ;;
201
+ --cmd|-c)
202
+ [[ $# -lt 2 ]] && { err "--cmd requires a value"; exit 1; }
203
+ tmux_cmd="$2"
204
+ shift 2
205
+ ;;
206
+ --new-tmux)
207
+ new_tmux=true
208
+ shift
209
+ ;;
210
+ --)
211
+ shift
212
+ break
213
+ ;;
214
+ *)
215
+ break
216
+ ;;
217
+ esac
218
+ done
219
+
220
+ if $new_tmux; then
221
+ if [[ -z "$tmux_branch" || -z "$tmux_cmd" ]]; then
222
+ err "Usage: $(basename "$0") --branch <branch> --new-tmux --cmd \"<command>\""
223
+ exit 1
224
+ fi
225
+ cmd_new_tmux_session_with_command "$tmux_branch" "$tmux_cmd"
226
+ exit $?
227
+ fi
228
+
229
+ if [[ -n "$tmux_branch" || -n "$tmux_cmd" ]]; then
230
+ err "--branch/--cmd require --new-tmux"
231
+ exit 1
232
+ fi
233
+
187
234
  COMMAND="$1"; shift || true
188
235
  case "$COMMAND" in
189
236
  b|create|add) cmd_create_worktree "$@" ;;
@@ -192,7 +239,7 @@ case "$COMMAND" in
192
239
  ch|checkout) cmd_checkout_worktree "$@" ;;
193
240
  status) cmd_status "$@" ;;
194
241
  copy-env) cmd_copy_env "$@" ;;
195
- update|--update) "$SCRIPT_DIR/shell/gwr/check-updates.sh" ;;
242
+ upgrade|update|--update) "$SCRIPT_DIR/shell/gwr/check-updates.sh" ;;
196
243
  help|-h|--help) usage ;;
197
244
  *) err "Unknown command '$COMMAND'"; usage ;;
198
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
+ }
@@ -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 "$@"
@@ -0,0 +1,54 @@
1
+ # shellcheck shell=bash
2
+
3
+ cmd_new_tmux_session_with_command() {
4
+ local branch_name="$1"
5
+ local command_line="$2"
6
+
7
+ local root; root="$(git_require_repo_root)" || return 1
8
+ local shared_root; shared_root="$(git_shared_root 2>/dev/null)"
9
+ [[ -z "$shared_root" ]] && shared_root="$root"
10
+ local source_root="$root"
11
+
12
+ if ! tmux_available; then
13
+ err "tmux not installed"
14
+ return 1
15
+ fi
16
+
17
+ if [[ -z "$branch_name" ]]; then
18
+ err "Branch name required"
19
+ return 1
20
+ fi
21
+
22
+ if [[ -z "${command_line// }" ]]; then
23
+ err "Command required"
24
+ return 1
25
+ fi
26
+
27
+ local wt=""
28
+ if git_branch_exists "$branch_name"; then
29
+ wt="$(git_branch_to_worktree_path "$branch_name")"
30
+ if [[ -z "$wt" ]]; then
31
+ info "🌳 Creating worktree for existing branch '$branch_name'..."
32
+ wt="$(git_ensure_worktree_for_branch "$branch_name")"
33
+ gw_env_copy_files "$source_root" "$wt" "$shared_root"
34
+ fi
35
+ else
36
+ err "Branch '$branch_name' does not exist"
37
+ return 1
38
+ fi
39
+
40
+ if [[ -z "$wt" ]]; then
41
+ err "Worktree not found for branch '$branch_name'"
42
+ return 1
43
+ fi
44
+
45
+ local session=""
46
+ session="$(tmux_ensure_session "$branch_name" "" "$wt")" || return 1
47
+ if [[ -z "$session" ]]; then
48
+ err "Failed to create tmux session"
49
+ return 1
50
+ fi
51
+
52
+ tmux_send_keys "$session" "$command_line" || return 1
53
+ tmux_attach_session "$session"
54
+ }
@@ -1,244 +0,0 @@
1
- #!/bin/bash
2
-
3
- ###############################################################################
4
- # copy_env.sh – Shared helpers for copying .env/config files into worktrees
5
- # ---------------------------------------------------------------------------
6
- # Provides reusable functions consumed by commands.sh, gw-bridge.sh, and any
7
- # other shell entry point. Adds structured logging so we can troubleshoot which
8
- # files were selected and whether individual copy operations succeeded.
9
- ###############################################################################
10
-
11
- # Global variables populated by copy_env_files
12
- COPY_ENV_LAST_COPIED=0
13
- COPY_ENV_LAST_TARGET=""
14
- COPY_ENV_LOG_FILE=""
15
-
16
- # Internal: write a log line (timestamped). Respects COPY_ENV_VERBOSE: if set to
17
- # 1, the message is also echoed to stderr for real-time debugging.
18
- copy_env_log() {
19
- local level="$1"
20
- shift
21
- local message="$*"
22
- local timestamp
23
- timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
24
- if [[ -n "$COPY_ENV_LOG_FILE" ]]; then
25
- if ! printf '%s [%s] %s\n' "$timestamp" "$level" "$message" >>"$COPY_ENV_LOG_FILE" 2>/dev/null; then
26
- # If we cannot write to the configured log file, drop logging to disk
27
- COPY_ENV_LOG_FILE=""
28
- fi
29
- fi
30
- if [[ ${COPY_ENV_VERBOSE:-0} -eq 1 ]]; then
31
- printf '[copy-env][%s] %s\n' "$level" "$message" >&2
32
- fi
33
- }
34
-
35
- # Determine which locations to copy by reading <repo>/.orchestra/options.json.
36
- copy_env_load_locations() {
37
- local repo_root="$1"
38
- local config_file="$repo_root/.orchestra/options.json"
39
-
40
- if [[ -f "$config_file" ]]; then
41
- if command -v jq >/dev/null 2>&1; then
42
- jq -r '(.copy_locations // ["/"])[]' "$config_file" 2>/dev/null || printf '/\n'
43
- return
44
- elif command -v python3 >/dev/null 2>&1; then
45
- python3 - "$config_file" <<'PY'
46
- import json
47
- import sys
48
- from pathlib import Path
49
-
50
- config_path = Path(sys.argv[1])
51
- try:
52
- data = json.loads(config_path.read_text())
53
- except Exception:
54
- data = {}
55
- locations = data.get("copy_locations")
56
- if isinstance(locations, list) and locations:
57
- for entry in locations:
58
- if isinstance(entry, str):
59
- print(entry)
60
- else:
61
- print("/")
62
- PY
63
- return
64
- fi
65
- fi
66
- # Fallback: default to repo root
67
- printf '/\n'
68
- }
69
-
70
- # Copy .env files from the source worktree into the target worktree.
71
- # Arguments:
72
- # $1 - source root (worktree we are copying from)
73
- # $2 - target root (new or existing worktree)
74
- # $3 - repository shared root (defaults to source root)
75
- # $4 - quiet flag (1 suppresses user-facing echo output)
76
- copy_env_files() {
77
- local source_root="$1"
78
- local target_root="$2"
79
- local repo_root="${3:-$source_root}"
80
- local quiet="${4:-0}"
81
-
82
- COPY_ENV_LAST_COPIED=0
83
- COPY_ENV_LAST_TARGET="$target_root"
84
-
85
- local enable_logs=${COPY_ENV_ENABLE_LOG:-0}
86
- local log_file="${COPY_ENV_LOG_FILE:-}"
87
- local log_dir="${COPY_ENV_LOG_DIR:-}"
88
-
89
- if [[ -n "$log_file" ]]; then
90
- mkdir -p "$(dirname "$log_file")" 2>/dev/null || true
91
- COPY_ENV_LOG_FILE="$log_file"
92
- elif [[ $enable_logs -eq 1 || -n "$log_dir" ]]; then
93
- log_dir="${log_dir:-$repo_root/.orchestra/logs}"
94
- mkdir -p "$log_dir" 2>/dev/null || true
95
- COPY_ENV_LOG_FILE="$log_dir/copy-env.log"
96
- else
97
- COPY_ENV_LOG_FILE=""
98
- fi
99
-
100
- copy_env_log "INFO" "Starting copy (source=$source_root, target=$target_root, repo=$repo_root)"
101
-
102
- local override_locations="${COPY_ENV_OVERRIDE_LOCATIONS:-}"
103
- unset COPY_ENV_OVERRIDE_LOCATIONS
104
- local locations=()
105
- if [[ -n "$override_locations" ]]; then
106
- while IFS= read -r line; do
107
- [[ -z "$line" ]] && continue
108
- locations+=("$line")
109
- done <<<"$override_locations"
110
- else
111
- while IFS= read -r line; do
112
- [[ -z "$line" ]] && continue
113
- locations+=("$line")
114
- done < <(copy_env_load_locations "$repo_root")
115
- fi
116
-
117
- if [[ ${#locations[@]} -eq 0 ]]; then
118
- locations=("/")
119
- fi
120
-
121
- local bases=("$source_root")
122
- if [[ -n "$repo_root" && "$repo_root" != "$source_root" ]]; then
123
- bases+=("$repo_root")
124
- fi
125
-
126
- local overall_copied=0
127
- local overall_failed=0
128
-
129
- (( quiet != 1 )) && echo "🔧 Copying .env files to worktree..."
130
-
131
- for raw_location in "${locations[@]}"; do
132
- local trimmed
133
- trimmed="$(printf '%s' "$raw_location" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
134
- [[ -z "$trimmed" ]] && continue
135
-
136
- local rel="${trimmed#/}"
137
- rel="${rel#./}"
138
-
139
- local location_copied=0
140
- local location_failed=0
141
-
142
- for base in "${bases[@]}"; do
143
- [[ -z "$base" ]] && continue
144
-
145
- copy_env_log "DEBUG" "Checking location '$trimmed' in base '$base'"
146
-
147
- if [[ "$trimmed" == "/" ]]; then
148
- local found_root=0
149
- for env_file in "$base"/.env*; do
150
- [[ -f "$env_file" ]] || continue
151
- mkdir -p "$target_root"
152
- local err_msg
153
- err_msg="$(cp "$env_file" "$target_root/$(basename "$env_file")" 2>&1)"
154
- if [[ $? -eq 0 ]]; then
155
- (( overall_copied++ ))
156
- location_copied=1
157
- found_root=1
158
- (( quiet != 1 )) && echo " ✅ Copied $(basename "$env_file") from $base/"
159
- copy_env_log "INFO" "Copied $(basename "$env_file") from $base/"
160
- else
161
- (( overall_failed++ ))
162
- (( location_failed++ ))
163
- (( quiet != 1 )) && echo " ❌ Failed to copy $(basename "$env_file") from $base/"
164
- copy_env_log "ERROR" "Failed to copy $(basename "$env_file") from $base/: ${err_msg:-unknown error}"
165
- fi
166
- done
167
- if [[ $found_root -eq 1 ]]; then
168
- break
169
- fi
170
- else
171
- local source_path="$base/$rel"
172
- if [[ -d "$source_path" ]]; then
173
- local dest_dir="$target_root/$rel"
174
- mkdir -p "$dest_dir"
175
- local found_any=0
176
- for env_file in "$source_path"/.env*; do
177
- [[ -f "$env_file" ]] || continue
178
- local err_msg
179
- err_msg="$(cp "$env_file" "$dest_dir/$(basename "$env_file")" 2>&1)"
180
- if [[ $? -eq 0 ]]; then
181
- (( overall_copied++ ))
182
- location_copied=1
183
- found_any=1
184
- (( quiet != 1 )) && echo " ✅ Copied $(basename "$env_file") from $rel/"
185
- copy_env_log "INFO" "Copied $(basename "$env_file") from $rel/"
186
- else
187
- (( overall_failed++ ))
188
- (( location_failed++ ))
189
- (( quiet != 1 )) && echo " ❌ Failed to copy $(basename "$env_file") from $rel/"
190
- copy_env_log "ERROR" "Failed to copy $(basename "$env_file") from $rel/: ${err_msg:-unknown error}"
191
- fi
192
- done
193
- if [[ $found_any -eq 1 ]]; then
194
- break
195
- fi
196
- elif [[ -f "$source_path" ]]; then
197
- local dest_dir
198
- dest_dir="$(dirname "$rel")"
199
- if [[ "$dest_dir" != "." ]]; then
200
- mkdir -p "$target_root/$dest_dir"
201
- else
202
- mkdir -p "$target_root"
203
- fi
204
- local err_msg
205
- err_msg="$(cp "$source_path" "$target_root/$rel" 2>&1)"
206
- if [[ $? -eq 0 ]]; then
207
- (( overall_copied++ ))
208
- location_copied=1
209
- (( quiet != 1 )) && echo " ✅ Copied $rel"
210
- copy_env_log "INFO" "Copied file $rel"
211
- else
212
- (( overall_failed++ ))
213
- (( location_failed++ ))
214
- (( quiet != 1 )) && echo " ❌ Failed to copy $rel"
215
- copy_env_log "ERROR" "Failed to copy file $rel: ${err_msg:-unknown error}"
216
- fi
217
- break
218
- fi
219
- fi
220
- done
221
-
222
- if [[ $location_copied -eq 0 ]]; then
223
- if [[ "$trimmed" == "/" ]]; then
224
- (( quiet != 1 )) && echo " ⚠️ No .env files found in any root"
225
- copy_env_log "WARN" "No .env files discovered for '/'"
226
- else
227
- (( quiet != 1 )) && echo " ⚠️ No .env files found for $trimmed"
228
- copy_env_log "WARN" "No .env files found for '$trimmed'"
229
- fi
230
- fi
231
- done
232
-
233
- COPY_ENV_LAST_COPIED=$overall_copied
234
-
235
- if [[ $overall_copied -eq 0 ]]; then
236
- copy_env_log "WARN" "Completed with no files copied"
237
- (( quiet != 1 )) && echo " ⚠️ No environment files copied."
238
- else
239
- copy_env_log "INFO" "Completed: $overall_copied file(s) copied, failures=$overall_failed"
240
- (( quiet != 1 )) && echo " ✅ Environment files copied ($overall_copied)."
241
- fi
242
-
243
- return 0
244
- }
@@ -1,84 +0,0 @@
1
- #!/bin/bash
2
-
3
- # shellcheck shell=bash
4
-
5
- # Copy environment files
6
- bridge_copy_env_files() {
7
- if [[ -z "${1:-}" ]] || [[ -z "${2:-}" ]]; then
8
- json_error "Source and target paths required"
9
- return 1
10
- fi
11
- source_path="$1"
12
- target_path="$2"
13
- raw_locations="${3-}"
14
-
15
- repo_root="$(cd "$source_path" 2>/dev/null && git rev-parse --show-toplevel 2>/dev/null || printf '%s' "$source_path")"
16
- shared_root="$(cd "$source_path" 2>/dev/null && git rev-parse --git-common-dir 2>/dev/null || echo "")"
17
- if [[ -n "$shared_root" ]]; then
18
- if [[ "$shared_root" != /* ]]; then
19
- shared_root="$(cd "$source_path" 2>/dev/null && pwd -P)/$shared_root"
20
- fi
21
- shared_root="$(dirname "$shared_root")"
22
- else
23
- shared_root="$repo_root"
24
- fi
25
-
26
- if [[ -n "$raw_locations" ]]; then
27
- if have_cmd jq; then
28
- override_list="$(printf '%s' "$raw_locations" | jq -r '.[]' 2>/dev/null || printf '/\n')"
29
- else
30
- bridge_init_json_backend
31
- case "$BRIDGE_JSON_BACKEND" in
32
- python)
33
- override_list="$(python3 - <<'PY' "$raw_locations"
34
- import json
35
- import sys
36
- try:
37
- data = json.loads(sys.argv[1])
38
- if isinstance(data, list) and data:
39
- for item in data:
40
- if isinstance(item, str):
41
- print(item)
42
- else:
43
- print('/')
44
- except Exception:
45
- print('/')
46
- PY
47
- )"
48
- ;;
49
- node)
50
- override_list="$(node - <<'NODE' "$raw_locations"
51
- let data;
52
- try {
53
- data = JSON.parse(process.argv[2]);
54
- if (Array.isArray(data) && data.length) {
55
- for (const item of data) {
56
- if (typeof item === 'string') {
57
- process.stdout.write(item + '\n');
58
- }
59
- }
60
- } else {
61
- process.stdout.write('/\n');
62
- }
63
- } catch (err) {
64
- process.stdout.write('/\n');
65
- }
66
- NODE
67
- )"
68
- ;;
69
- *)
70
- override_list="/\n"
71
- ;;
72
- esac
73
- fi
74
- else
75
- override_list=""
76
- fi
77
-
78
- COPY_ENV_OVERRIDE_LOCATIONS="$override_list" copy_env_files "$source_path" "$target_path" "$shared_root" 1 >/dev/null
79
- if [[ ${COPY_ENV_LAST_COPIED:-0} -gt 0 ]]; then
80
- echo "true"
81
- else
82
- echo "false"
83
- fi
84
- }
@@ -1,27 +0,0 @@
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
- # Find absolute path of target worktree by branch name
14
- local target_path; 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; root="$(git_repo_root)"
21
- local shared_root; shared_root="$(git_shared_root 2>/dev/null)"
22
- [[ -z "$shared_root" ]] && shared_root="$root"
23
- local source_root="$root"
24
- info "🔧 Copying env files from '$source_root' -> '$target_path'"
25
- copy_env_files "$source_root" "$target_path" "$shared_root"
26
- info "✅ Env files synced to '$target_branch'"
27
- }
@@ -1,3 +0,0 @@
1
- # shellcheck shell=bash
2
-
3
- COPY_ENV_DEFAULT_LOCATION="/"
@@ -1,171 +0,0 @@
1
- # shellcheck shell=bash
2
-
3
- copy_env_files() {
4
- local source_root="$1"
5
- local target_root="$2"
6
- local repo_root="${3:-$source_root}"
7
- local quiet="${4:-0}"
8
-
9
- COPY_ENV_LAST_COPIED=0
10
- COPY_ENV_LAST_TARGET="$target_root"
11
-
12
- local enable_logs=${COPY_ENV_ENABLE_LOG:-0}
13
- local log_file="${COPY_ENV_LOG_FILE:-}"
14
- local log_dir="${COPY_ENV_LOG_DIR:-}"
15
-
16
- if [[ -n "$log_file" ]]; then
17
- mkdir -p "$(dirname "$log_file")" 2>/dev/null || true
18
- COPY_ENV_LOG_FILE="$log_file"
19
- elif [[ $enable_logs -eq 1 || -n "$log_dir" ]]; then
20
- log_dir="${log_dir:-$repo_root/.orchestra/logs}"
21
- mkdir -p "$log_dir" 2>/dev/null || true
22
- COPY_ENV_LOG_FILE="$log_dir/copy-env.log"
23
- else
24
- COPY_ENV_LOG_FILE=""
25
- fi
26
-
27
- copy_env_log "INFO" "Starting copy (source=$source_root, target=$target_root, repo=$repo_root)"
28
-
29
- local override_locations="${COPY_ENV_OVERRIDE_LOCATIONS:-}"
30
- unset COPY_ENV_OVERRIDE_LOCATIONS
31
- local locations=()
32
- if [[ -n "$override_locations" ]]; then
33
- while IFS= read -r line; do
34
- [[ -z "$line" ]] && continue
35
- locations+=("$line")
36
- done <<<"$override_locations"
37
- else
38
- while IFS= read -r line; do
39
- [[ -z "$line" ]] && continue
40
- locations+=("$line")
41
- done < <(copy_env_load_locations "$repo_root")
42
- fi
43
-
44
- if [[ ${#locations[@]} -eq 0 ]]; then
45
- locations=("$COPY_ENV_DEFAULT_LOCATION")
46
- fi
47
-
48
- local bases=("$source_root")
49
- if [[ -n "$repo_root" && "$repo_root" != "$source_root" ]]; then
50
- bases+=("$repo_root")
51
- fi
52
-
53
- local overall_copied=0
54
- local overall_failed=0
55
-
56
- (( quiet != 1 )) && echo "🔧 Copying .env files to worktree..."
57
-
58
- for raw_location in "${locations[@]}"; do
59
- local trimmed
60
- trimmed="$(printf '%s' "$raw_location" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
61
- [[ -z "$trimmed" ]] && continue
62
-
63
- local rel="${trimmed#/}"
64
- rel="${rel#./}"
65
-
66
- local location_copied=0
67
- local location_failed=0
68
-
69
- for base in "${bases[@]}"; do
70
- [[ -z "$base" ]] && continue
71
-
72
- copy_env_log "DEBUG" "Checking location '$trimmed' in base '$base'"
73
-
74
- if [[ "$trimmed" == "/" ]]; then
75
- local found_root=0
76
- for env_file in "$base"/.env*; do
77
- [[ -f "$env_file" ]] || continue
78
- mkdir -p "$target_root"
79
- local err_msg
80
- err_msg="$(cp "$env_file" "$target_root/$(basename "$env_file")" 2>&1)"
81
- if [[ $? -eq 0 ]]; then
82
- (( overall_copied++ ))
83
- location_copied=1
84
- found_root=1
85
- (( quiet != 1 )) && echo " ✅ Copied $(basename "$env_file") from $base/"
86
- copy_env_log "INFO" "Copied $(basename "$env_file") from $base/"
87
- else
88
- (( overall_failed++ ))
89
- (( location_failed++ ))
90
- (( quiet != 1 )) && echo " ❌ Failed to copy $(basename "$env_file") from $base/"
91
- copy_env_log "ERROR" "Failed to copy $(basename "$env_file") from $base/: ${err_msg:-unknown error}"
92
- fi
93
- done
94
- if [[ $found_root -eq 1 ]]; then
95
- break
96
- fi
97
- else
98
- local source_path="$base/$rel"
99
- if [[ -d "$source_path" ]]; then
100
- local dest_dir="$target_root/$rel"
101
- mkdir -p "$dest_dir"
102
- local found_any=0
103
- for env_file in "$source_path"/.env*; do
104
- [[ -f "$env_file" ]] || continue
105
- local err_msg
106
- err_msg="$(cp "$env_file" "$dest_dir/$(basename "$env_file")" 2>&1)"
107
- if [[ $? -eq 0 ]]; then
108
- (( overall_copied++ ))
109
- location_copied=1
110
- found_any=1
111
- (( quiet != 1 )) && echo " ✅ Copied $(basename "$env_file") from $rel/"
112
- copy_env_log "INFO" "Copied $(basename "$env_file") from $rel/"
113
- else
114
- (( overall_failed++ ))
115
- (( location_failed++ ))
116
- (( quiet != 1 )) && echo " ❌ Failed to copy $(basename "$env_file") from $rel/"
117
- copy_env_log "ERROR" "Failed to copy $(basename "$env_file") from $rel/: ${err_msg:-unknown error}"
118
- fi
119
- done
120
- if [[ $found_any -eq 1 ]]; then
121
- break
122
- fi
123
- elif [[ -f "$source_path" ]]; then
124
- local dest_dir
125
- dest_dir="$(dirname "$rel")"
126
- if [[ "$dest_dir" != "." ]]; then
127
- mkdir -p "$target_root/$dest_dir"
128
- else
129
- mkdir -p "$target_root"
130
- fi
131
- local err_msg
132
- err_msg="$(cp "$source_path" "$target_root/$rel" 2>&1)"
133
- if [[ $? -eq 0 ]]; then
134
- (( overall_copied++ ))
135
- location_copied=1
136
- (( quiet != 1 )) && echo " ✅ Copied $rel"
137
- copy_env_log "INFO" "Copied file $rel"
138
- else
139
- (( overall_failed++ ))
140
- (( location_failed++ ))
141
- (( quiet != 1 )) && echo " ❌ Failed to copy $rel"
142
- copy_env_log "ERROR" "Failed to copy file $rel: ${err_msg:-unknown error}"
143
- fi
144
- break
145
- fi
146
- fi
147
- done
148
-
149
- if [[ $location_copied -eq 0 ]]; then
150
- if [[ "$trimmed" == "/" ]]; then
151
- (( quiet != 1 )) && echo " ⚠️ No .env files found in any root"
152
- copy_env_log "WARN" "No .env files discovered for '/'"
153
- else
154
- (( quiet != 1 )) && echo " ⚠️ No .env files found for $trimmed"
155
- copy_env_log "WARN" "No .env files found for '$trimmed'"
156
- fi
157
- fi
158
- done
159
-
160
- COPY_ENV_LAST_COPIED=$overall_copied
161
-
162
- if [[ $overall_copied -eq 0 ]]; then
163
- copy_env_log "WARN" "Completed with no files copied"
164
- (( quiet != 1 )) && echo " ⚠️ No environment files copied."
165
- else
166
- copy_env_log "INFO" "Completed: $overall_copied file(s) copied, failures=$overall_failed"
167
- (( quiet != 1 )) && echo " ✅ Environment files copied ($overall_copied)."
168
- fi
169
-
170
- return 0
171
- }
@@ -1,14 +0,0 @@
1
- # shellcheck shell=bash
2
-
3
- copy_env_init
4
- source "$SCRIPT_DIR/api/git.sh"
5
- source "$SCRIPT_DIR/api/tmux.sh"
6
-
7
- SESSION="orchestra__performance-analysis__cdedeb95__20250827__155000__auto_test_session"
8
-
9
- export GW_DEBUG_RENAME=1
10
-
11
- echo "Testing parsing of: $SESSION"
12
- echo ""
13
-
14
- tmux_rename_session "$SESSION" "test_name" 2>&1
@@ -1,9 +0,0 @@
1
- # shellcheck shell=bash
2
-
3
- copy_env_init() {
4
- source "$SCRIPT_DIR/shell/env/copy_env_state.sh"
5
- source "$SCRIPT_DIR/shell/env/copy_env_constants.sh"
6
- source "$SCRIPT_DIR/shell/env/copy_env_logging.sh"
7
- source "$SCRIPT_DIR/shell/env/copy_env_locations.sh"
8
- source "$SCRIPT_DIR/shell/env/copy_env_core.sh"
9
- }
@@ -1,34 +0,0 @@
1
- # shellcheck shell=bash
2
-
3
- copy_env_load_locations() {
4
- local repo_root="$1"
5
- local config_file="$repo_root/.orchestra/options.json"
6
-
7
- if [[ -f "$config_file" ]]; then
8
- if command -v jq >/dev/null 2>&1; then
9
- jq -r '(.copy_locations // ["/"])[]' "$config_file" 2>/dev/null || printf '/\n'
10
- return
11
- elif command -v python3 >/dev/null 2>&1; then
12
- python3 - "$config_file" <<'PY'
13
- import json
14
- import sys
15
- from pathlib import Path
16
-
17
- config_path = Path(sys.argv[1])
18
- try:
19
- data = json.loads(config_path.read_text())
20
- except Exception:
21
- data = {}
22
- locations = data.get("copy_locations")
23
- if isinstance(locations, list) and locations:
24
- for entry in locations:
25
- if isinstance(entry, str):
26
- print(entry)
27
- else:
28
- print("/")
29
- PY
30
- return
31
- fi
32
- fi
33
- printf '/\n'
34
- }
@@ -1,17 +0,0 @@
1
- # shellcheck shell=bash
2
-
3
- copy_env_log() {
4
- local level="$1"
5
- shift
6
- local message="$*"
7
- local timestamp
8
- timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
9
- if [[ -n "$COPY_ENV_LOG_FILE" ]]; then
10
- if ! printf '%s [%s] %s\n' "$timestamp" "$level" "$message" >>"$COPY_ENV_LOG_FILE" 2>/dev/null; then
11
- COPY_ENV_LOG_FILE=""
12
- fi
13
- fi
14
- if [[ ${COPY_ENV_VERBOSE:-0} -eq 1 ]]; then
15
- printf '[copy-env][%s] %s\n' "$level" "$message" >&2
16
- fi
17
- }
@@ -1,5 +0,0 @@
1
- # shellcheck shell=bash
2
-
3
- COPY_ENV_LAST_COPIED=0
4
- COPY_ENV_LAST_TARGET=""
5
- COPY_ENV_LOG_FILE=""