@humanu/orchestra 0.5.58 → 0.5.61
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/package.json +1 -1
- package/resources/api/tmux.sh +597 -131
- package/resources/prebuilt/linux-x64/orchestra +0 -0
- package/resources/prebuilt/macos-arm64/orchestra +0 -0
- package/resources/prebuilt/macos-intel/orchestra +0 -0
- package/resources/scripts/shell/bridge/ai.sh +184 -59
- package/resources/scripts/shell/bridge/utils.sh +166 -0
- package/resources/scripts/shell/gwr_usage.sh +1 -1
- package/resources/scripts/shell/orchestra-command-hook.sh +139 -15
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# shellcheck shell=bash
|
|
4
4
|
|
|
5
|
+
ai_choose_provider() {
|
|
6
|
+
local primary="${AI_PRIMARY_PROVIDER:-anthropic}"
|
|
7
|
+
primary="$(printf '%s' "$primary" | tr '[:upper:]' '[:lower:]')"
|
|
8
|
+
if [[ "$primary" == "openai" ]]; then
|
|
9
|
+
if [[ -n "${OPENAI_API_KEY-}" ]]; then
|
|
10
|
+
echo "openai"
|
|
11
|
+
return 0
|
|
12
|
+
fi
|
|
13
|
+
if [[ -n "${ANTHROPIC_API_KEY-}" ]]; then
|
|
14
|
+
echo "anthropic"
|
|
15
|
+
return 0
|
|
16
|
+
fi
|
|
17
|
+
else
|
|
18
|
+
if [[ -n "${ANTHROPIC_API_KEY-}" ]]; then
|
|
19
|
+
echo "anthropic"
|
|
20
|
+
return 0
|
|
21
|
+
fi
|
|
22
|
+
if [[ -n "${OPENAI_API_KEY-}" ]]; then
|
|
23
|
+
echo "openai"
|
|
24
|
+
return 0
|
|
25
|
+
fi
|
|
26
|
+
fi
|
|
27
|
+
return 1
|
|
28
|
+
}
|
|
29
|
+
|
|
5
30
|
# AI generate name from base64 content
|
|
6
31
|
bridge_ai_generate_name_from_base64() {
|
|
7
32
|
if [[ -z "${1:-}" ]]; then
|
|
@@ -9,19 +34,66 @@ bridge_ai_generate_name_from_base64() {
|
|
|
9
34
|
return 1
|
|
10
35
|
fi
|
|
11
36
|
content_b64="$1"
|
|
37
|
+
load_ai_primary_provider
|
|
38
|
+
load_openai_model
|
|
39
|
+
load_openai_api_key
|
|
12
40
|
load_anthropic_api_key
|
|
13
|
-
|
|
41
|
+
local provider
|
|
42
|
+
if ! provider="$(ai_choose_provider)"; then
|
|
14
43
|
echo "\"missing_api_key\""
|
|
15
44
|
return 0
|
|
16
45
|
fi
|
|
17
46
|
# Build request via Python to ensure proper JSON encoding
|
|
18
|
-
|
|
19
|
-
import sys, json, base64
|
|
47
|
+
request_payload=$(python3 - "$content_b64" "$provider" "${OPENAI_MODEL:-gpt-4o-mini}" <<'PY'
|
|
48
|
+
import sys, json, base64, re
|
|
49
|
+
|
|
20
50
|
b64 = sys.argv[1]
|
|
51
|
+
provider = sys.argv[2] if len(sys.argv) > 2 else "anthropic"
|
|
52
|
+
openai_model = sys.argv[3] if len(sys.argv) > 3 and sys.argv[3] else "gpt-4o-mini"
|
|
21
53
|
try:
|
|
22
|
-
content = base64.b64decode(b64.encode('utf-8')).decode('utf-8','ignore')
|
|
54
|
+
content = base64.b64decode(b64.encode('utf-8')).decode('utf-8', 'ignore')
|
|
23
55
|
except Exception:
|
|
24
56
|
content = ''
|
|
57
|
+
|
|
58
|
+
def extract_command(line: str) -> str:
|
|
59
|
+
stripped = line.strip()
|
|
60
|
+
for lead in ("$ ", "> "):
|
|
61
|
+
if stripped.startswith(lead):
|
|
62
|
+
return stripped[len(lead):].strip()
|
|
63
|
+
for marker in ("$ ", "# "):
|
|
64
|
+
if marker in line:
|
|
65
|
+
return line.split(marker)[-1].strip()
|
|
66
|
+
return ""
|
|
67
|
+
|
|
68
|
+
def normalize_app(cmd: str) -> str:
|
|
69
|
+
cmd = cmd.strip()
|
|
70
|
+
if not cmd:
|
|
71
|
+
return ""
|
|
72
|
+
for lead in ("$ ", "> "):
|
|
73
|
+
if cmd.startswith(lead):
|
|
74
|
+
cmd = cmd[len(lead):].strip()
|
|
75
|
+
parts = cmd.split()
|
|
76
|
+
if not parts:
|
|
77
|
+
return ""
|
|
78
|
+
if parts[0] in ("sudo", "env", "command"):
|
|
79
|
+
parts = parts[1:]
|
|
80
|
+
if not parts:
|
|
81
|
+
return ""
|
|
82
|
+
base = parts[0].split("/")[-1].lower()
|
|
83
|
+
base = re.sub(r"[^a-z0-9]+", "_", base).strip("_")
|
|
84
|
+
if base in {"bash", "zsh", "sh", "fish", "tmux", "login", "sudo", "man", "less", "more", "cat", "tail", "watch", "source", "export", "set", "alias", "unalias", "history", "bindkey"}:
|
|
85
|
+
return ""
|
|
86
|
+
return base
|
|
87
|
+
|
|
88
|
+
lines = content.splitlines()
|
|
89
|
+
last_cmd = ""
|
|
90
|
+
for line in reversed(lines):
|
|
91
|
+
candidate = extract_command(line)
|
|
92
|
+
if candidate:
|
|
93
|
+
last_cmd = candidate
|
|
94
|
+
break
|
|
95
|
+
|
|
96
|
+
app_prefix = normalize_app(last_cmd) or ""
|
|
25
97
|
prompt = f"""Analyze this terminal session and create a descriptive name based ONLY on the console output and commands executed.
|
|
26
98
|
|
|
27
99
|
Terminal output:
|
|
@@ -36,26 +108,15 @@ IMPORTANT: Look carefully at the terminal output for:
|
|
|
36
108
|
|
|
37
109
|
CRITICAL GIT DETECTION: Check for ANY of these Git indicators in the last outputs:
|
|
38
110
|
- Git commands: git status, git diff, git add, git commit, git push, git pull, git checkout, git branch, git merge, git rebase, git log, git stash, etc.
|
|
39
|
-
- Git output patterns:
|
|
111
|
+
- Git output patterns: "On branch", "Your branch is", "Changes not staged", "Changes to be committed", "Untracked files", "modified:", "deleted:", "new file:", "renamed:"
|
|
40
112
|
- Git diff output: Lines starting with +, -, @@, diff --git
|
|
41
|
-
- Git merge/rebase messages:
|
|
42
|
-
- Git status indicators:
|
|
43
|
-
- Git error messages:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
- running_ : Dev servers (npm run dev, yarn dev, python manage.py runserver, rails server, etc.)
|
|
49
|
-
- build_ : Build processes (npm run build, cargo build, make, webpack, etc.)
|
|
50
|
-
- test_ : Testing (npm test, pytest, jest, cargo test, etc.)
|
|
51
|
-
- docker_ : Docker commands, container operations
|
|
52
|
-
- ssh_ : SSH connections, remote operations
|
|
53
|
-
- db_ : Database operations (psql, mysql, mongo, migrations)
|
|
54
|
-
- debug_ : Debugging sessions, error investigation
|
|
55
|
-
- deploy_ : Deployment operations
|
|
56
|
-
- Otherwise, no prefix for general development
|
|
57
|
-
|
|
58
|
-
If Git patterns are detected, ALWAYS use git_ prefix, even if other activities are present.
|
|
113
|
+
- Git merge/rebase messages: "CONFLICT", "Merge branch", "Rebase", "Cherry-pick"
|
|
114
|
+
- Git status indicators: "nothing to commit", "working tree clean", "ahead of", "behind"
|
|
115
|
+
- Git error messages: "fatal:", "error:", "warning:" (when preceded by git commands)
|
|
116
|
+
|
|
117
|
+
DESCRIPTION ONLY: Return a short description of the activity.
|
|
118
|
+
- Do NOT include the app/tool name or any prefix in the output.
|
|
119
|
+
- If Git patterns are detected, the description should still reflect the git activity, but do not add "git_".
|
|
59
120
|
|
|
60
121
|
Describe what the user was actually doing based on the console output, not just the current state.
|
|
61
122
|
|
|
@@ -63,29 +124,63 @@ The name should be max 100 chars total, use underscores only (no colons or speci
|
|
|
63
124
|
|
|
64
125
|
Respond with ONLY the session name, nothing else."""
|
|
65
126
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
127
|
+
if provider == "openai":
|
|
128
|
+
req = {
|
|
129
|
+
"model": openai_model,
|
|
130
|
+
"max_tokens": 100,
|
|
131
|
+
"temperature": 0.2,
|
|
132
|
+
"messages": [{"role": "user", "content": prompt}],
|
|
133
|
+
}
|
|
134
|
+
else:
|
|
135
|
+
req = {
|
|
136
|
+
"model": "claude-3-5-haiku-latest",
|
|
137
|
+
"max_tokens": 100,
|
|
138
|
+
"messages": [{"role": "user", "content": prompt}],
|
|
139
|
+
}
|
|
140
|
+
print(app_prefix)
|
|
71
141
|
print(json.dumps(req))
|
|
72
142
|
PY
|
|
73
|
-
)
|
|
143
|
+
)
|
|
144
|
+
app_prefix="${request_payload%%$'\n'*}"
|
|
145
|
+
request_body="${request_payload#*$'\n'}"
|
|
146
|
+
if [[ "$request_body" == "$request_payload" ]]; then
|
|
147
|
+
request_body="$request_payload"
|
|
148
|
+
app_prefix=""
|
|
149
|
+
fi
|
|
150
|
+
if [[ "$provider" == "openai" ]]; then
|
|
151
|
+
response=$(curl -s -X POST https://api.openai.com/v1/chat/completions \
|
|
152
|
+
--max-time 20 \
|
|
153
|
+
-H "Content-Type: application/json" \
|
|
154
|
+
-H "Authorization: Bearer $OPENAI_API_KEY" \
|
|
155
|
+
-d "$request_body" 2>/dev/null)
|
|
156
|
+
new_name=$(echo "$response" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('choices',[{}])[0].get('message',{}).get('content',''))" 2>/dev/null)
|
|
157
|
+
else
|
|
74
158
|
response=$(curl -s -X POST https://api.anthropic.com/v1/messages \
|
|
75
159
|
--max-time 20 \
|
|
76
160
|
-H "Content-Type: application/json" \
|
|
77
161
|
-H "x-api-key: $ANTHROPIC_API_KEY" \
|
|
78
162
|
-H "anthropic-version: 2023-06-01" \
|
|
79
163
|
-d "$request_body" 2>/dev/null)
|
|
80
|
-
# Extract text
|
|
81
164
|
new_name=$(echo "$response" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('content',[{}])[0].get('text',''))" 2>/dev/null)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
165
|
+
fi
|
|
166
|
+
# Clean and truncate
|
|
167
|
+
new_name=$(echo "$new_name" | tr ' ' '_' | sed 's/[^a-zA-Z0-9_-]//g' | cut -c1-100)
|
|
168
|
+
if [[ -z "$new_name" ]]; then
|
|
169
|
+
echo "\"ai_generation_failed\""
|
|
170
|
+
else
|
|
171
|
+
app_prefix="$(printf '%s' "$app_prefix" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '_')"
|
|
172
|
+
app_prefix="${app_prefix//__/_}"
|
|
173
|
+
app_prefix="${app_prefix##_}"
|
|
174
|
+
app_prefix="${app_prefix%%_}"
|
|
175
|
+
if [[ -n "$app_prefix" ]]; then
|
|
176
|
+
if [[ "$new_name" == "${app_prefix}_"* ]]; then
|
|
177
|
+
new_name="${new_name#${app_prefix}_}"
|
|
178
|
+
fi
|
|
179
|
+
new_name="${app_prefix}_${new_name}"
|
|
88
180
|
fi
|
|
181
|
+
new_name="${new_name:0:100}"
|
|
182
|
+
printf '"%s"\n' "$new_name"
|
|
183
|
+
fi
|
|
89
184
|
}
|
|
90
185
|
|
|
91
186
|
# Rename session with AI
|
|
@@ -95,48 +190,78 @@ bridge_rename_session() {
|
|
|
95
190
|
return 1
|
|
96
191
|
fi
|
|
97
192
|
original_session_name="$1"
|
|
98
|
-
|
|
193
|
+
|
|
99
194
|
# Load API key from config file or fallback to .env
|
|
195
|
+
load_openai_api_key
|
|
100
196
|
load_anthropic_api_key
|
|
101
|
-
|
|
102
|
-
if [[ -z "${ANTHROPIC_API_KEY-}" ]]; then
|
|
197
|
+
|
|
198
|
+
if [[ -z "${OPENAI_API_KEY-}" && -z "${ANTHROPIC_API_KEY-}" ]]; then
|
|
103
199
|
echo "\"missing_api_key\""
|
|
104
200
|
return 0
|
|
105
201
|
fi
|
|
106
|
-
|
|
202
|
+
|
|
107
203
|
# Check if tmux is available and session exists
|
|
108
204
|
if ! tmux_available; then
|
|
109
205
|
echo "\"tmux_not_available\""
|
|
110
206
|
return 0
|
|
111
207
|
fi
|
|
112
|
-
|
|
208
|
+
|
|
113
209
|
if ! tmux_session_exists "$original_session_name"; then
|
|
114
210
|
echo "\"session_not_found\""
|
|
115
211
|
return 0
|
|
116
212
|
fi
|
|
117
|
-
|
|
118
|
-
# Generate AI
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if [[
|
|
122
|
-
|
|
213
|
+
|
|
214
|
+
# Generate AI description using the CURRENT session (no renaming to temp name)
|
|
215
|
+
TMUX_AI_APP_SOURCE=""
|
|
216
|
+
local err_file
|
|
217
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
218
|
+
err_file=$(mktemp -t gw_ai_err)
|
|
219
|
+
else
|
|
220
|
+
err_file=$(mktemp)
|
|
221
|
+
fi
|
|
222
|
+
local ai_status=0
|
|
223
|
+
if ! ai_name="$(tmux_generate_ai_session_name "$original_session_name" 2>"$err_file")"; then
|
|
224
|
+
ai_status=$?
|
|
225
|
+
fi
|
|
226
|
+
local ai_err=""
|
|
227
|
+
if [[ -s "$err_file" ]]; then
|
|
228
|
+
ai_err=$(tail -n 1 "$err_file")
|
|
229
|
+
fi
|
|
230
|
+
rm -f "$err_file"
|
|
231
|
+
|
|
232
|
+
if [[ -n "$ai_name" ]]; then
|
|
233
|
+
# Clean the AI description for session naming
|
|
123
234
|
ai_name="$(echo "$ai_name" | tr ' ' '_' | sed 's/[^a-zA-Z0-9_-]//g' | cut -c1-100)"
|
|
124
|
-
|
|
125
|
-
# Remove any prefix from AI name to avoid duplication
|
|
126
|
-
ai_name="$(echo "$ai_name" | sed -E 's/^(opencode|running|ssh|docker|k8s|git|db)_//')"
|
|
127
|
-
|
|
128
|
-
# Remove any leading underscores
|
|
129
235
|
ai_name="$(echo "$ai_name" | sed 's/^_*//')"
|
|
130
|
-
|
|
236
|
+
if [[ -z "$ai_name" ]]; then
|
|
237
|
+
echo "\"ai_generation_failed\""
|
|
238
|
+
return 0
|
|
239
|
+
fi
|
|
240
|
+
|
|
131
241
|
# Perform the rename preserving canonical prefix and delimiter
|
|
132
242
|
if tmux_rename_session "$original_session_name" "$ai_name" >/dev/null; then
|
|
133
|
-
|
|
243
|
+
local app_source="${TMUX_AI_APP_SOURCE-}"
|
|
244
|
+
app_source="$(printf '%s' "$app_source" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '_')"
|
|
245
|
+
app_source="${app_source//__/_}"
|
|
246
|
+
app_source="${app_source##_}"
|
|
247
|
+
app_source="${app_source%%_}"
|
|
248
|
+
if [[ -n "$app_source" ]]; then
|
|
249
|
+
echo "\"success:app_source:${app_source}\""
|
|
250
|
+
else
|
|
251
|
+
echo "\"success\""
|
|
252
|
+
fi
|
|
134
253
|
else
|
|
135
254
|
echo "\"rename_failed\""
|
|
136
255
|
fi
|
|
137
256
|
else
|
|
138
257
|
# AI generation failed, keep original name
|
|
139
|
-
|
|
258
|
+
if [[ -n "$ai_err" ]]; then
|
|
259
|
+
echo "\"ai_generation_failed: ${ai_err}\""
|
|
260
|
+
elif [[ $ai_status -ne 0 ]]; then
|
|
261
|
+
echo "\"ai_generation_failed: ai helper exited ${ai_status}\""
|
|
262
|
+
else
|
|
263
|
+
echo "\"ai_generation_failed\""
|
|
264
|
+
fi
|
|
140
265
|
fi
|
|
141
266
|
}
|
|
142
267
|
|
|
@@ -148,24 +273,24 @@ bridge_manual_rename_session() {
|
|
|
148
273
|
fi
|
|
149
274
|
old_name="$1"
|
|
150
275
|
new_display_name="$2"
|
|
151
|
-
|
|
276
|
+
|
|
152
277
|
if ! tmux_available; then
|
|
153
278
|
echo "\"tmux_not_available\""
|
|
154
279
|
return 0
|
|
155
280
|
fi
|
|
156
|
-
|
|
281
|
+
|
|
157
282
|
# Check if session exists
|
|
158
283
|
if ! tmux has-session -t "$old_name" 2>/dev/null; then
|
|
159
284
|
echo "\"session_not_found\""
|
|
160
285
|
return 0
|
|
161
286
|
fi
|
|
162
|
-
|
|
287
|
+
|
|
163
288
|
# Validate new display name (alphanumeric, underscore, hyphen only)
|
|
164
289
|
if [[ ! "$new_display_name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
165
290
|
echo "\"invalid_name\""
|
|
166
291
|
return 0
|
|
167
292
|
fi
|
|
168
|
-
|
|
293
|
+
|
|
169
294
|
# Perform rename via tmux API to preserve delimiter and canonical prefix
|
|
170
295
|
if tmux_rename_session "$old_name" "$new_display_name" >/dev/null; then
|
|
171
296
|
echo "\"success\""
|
|
@@ -448,3 +448,169 @@ NODE
|
|
|
448
448
|
fi
|
|
449
449
|
fi
|
|
450
450
|
}
|
|
451
|
+
|
|
452
|
+
# Helper function to load OpenAI API key from config or .env file
|
|
453
|
+
load_openai_api_key() {
|
|
454
|
+
local config_file="$HOME/.orchestra/config.json"
|
|
455
|
+
if [[ -f "$config_file" ]]; then
|
|
456
|
+
if have_cmd jq; then
|
|
457
|
+
local api_key
|
|
458
|
+
api_key="$(jq -r '.openai_api_key // empty' "$config_file" 2>/dev/null)"
|
|
459
|
+
if [[ -n "$api_key" ]] && [[ "$api_key" != "null" ]]; then
|
|
460
|
+
export OPENAI_API_KEY="$api_key"
|
|
461
|
+
return 0
|
|
462
|
+
fi
|
|
463
|
+
else
|
|
464
|
+
bridge_init_json_backend
|
|
465
|
+
local api_key
|
|
466
|
+
case "$BRIDGE_JSON_BACKEND" in
|
|
467
|
+
python)
|
|
468
|
+
api_key="$(python3 - "$config_file" <<'PY'
|
|
469
|
+
import json
|
|
470
|
+
import sys
|
|
471
|
+
try:
|
|
472
|
+
data = json.load(open(sys.argv[1], 'r'))
|
|
473
|
+
except Exception:
|
|
474
|
+
data = {}
|
|
475
|
+
value = data.get('openai_api_key') or ''
|
|
476
|
+
print(value)
|
|
477
|
+
PY
|
|
478
|
+
)"
|
|
479
|
+
;;
|
|
480
|
+
node)
|
|
481
|
+
api_key="$(node - "$config_file" <<'NODE'
|
|
482
|
+
const fs = require('fs');
|
|
483
|
+
let value = '';
|
|
484
|
+
try {
|
|
485
|
+
const data = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
486
|
+
value = data.openai_api_key || '';
|
|
487
|
+
} catch (err) {
|
|
488
|
+
value = '';
|
|
489
|
+
}
|
|
490
|
+
process.stdout.write(value);
|
|
491
|
+
NODE
|
|
492
|
+
)"
|
|
493
|
+
;;
|
|
494
|
+
esac
|
|
495
|
+
if [[ -n "${api_key:-}" ]]; then
|
|
496
|
+
export OPENAI_API_KEY="$api_key"
|
|
497
|
+
return 0
|
|
498
|
+
fi
|
|
499
|
+
fi
|
|
500
|
+
fi
|
|
501
|
+
|
|
502
|
+
if [[ -f ".env" ]]; then
|
|
503
|
+
source .env
|
|
504
|
+
fi
|
|
505
|
+
|
|
506
|
+
if [[ -z "${OPENAI_API_KEY-}" ]]; then
|
|
507
|
+
local repo_root
|
|
508
|
+
repo_root="$(git_repo_root 2>/dev/null)"
|
|
509
|
+
if [[ -n "$repo_root" ]] && [[ -f "$repo_root/.env" ]]; then
|
|
510
|
+
source "$repo_root/.env"
|
|
511
|
+
fi
|
|
512
|
+
fi
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
# Helper function to load primary AI provider
|
|
516
|
+
load_ai_primary_provider() {
|
|
517
|
+
if [[ -n "${AI_PRIMARY_PROVIDER:-}" ]]; then
|
|
518
|
+
return 0
|
|
519
|
+
fi
|
|
520
|
+
|
|
521
|
+
local config_file="$HOME/.orchestra/config.json"
|
|
522
|
+
local provider=""
|
|
523
|
+
if [[ -f "$config_file" ]]; then
|
|
524
|
+
if have_cmd jq; then
|
|
525
|
+
provider="$(jq -r '.ai_primary_provider // empty' "$config_file" 2>/dev/null)"
|
|
526
|
+
else
|
|
527
|
+
bridge_init_json_backend
|
|
528
|
+
case "$BRIDGE_JSON_BACKEND" in
|
|
529
|
+
python)
|
|
530
|
+
provider="$(python3 - "$config_file" <<'PY'
|
|
531
|
+
import json
|
|
532
|
+
import sys
|
|
533
|
+
try:
|
|
534
|
+
data = json.load(open(sys.argv[1], 'r'))
|
|
535
|
+
except Exception:
|
|
536
|
+
data = {}
|
|
537
|
+
value = data.get('ai_primary_provider') or ''
|
|
538
|
+
print(value)
|
|
539
|
+
PY
|
|
540
|
+
)"
|
|
541
|
+
;;
|
|
542
|
+
node)
|
|
543
|
+
provider="$(node - "$config_file" <<'NODE'
|
|
544
|
+
const fs = require('fs');
|
|
545
|
+
let value = '';
|
|
546
|
+
try {
|
|
547
|
+
const data = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
548
|
+
value = data.ai_primary_provider || '';
|
|
549
|
+
} catch (err) {
|
|
550
|
+
value = '';
|
|
551
|
+
}
|
|
552
|
+
process.stdout.write(value);
|
|
553
|
+
NODE
|
|
554
|
+
)"
|
|
555
|
+
;;
|
|
556
|
+
esac
|
|
557
|
+
fi
|
|
558
|
+
fi
|
|
559
|
+
|
|
560
|
+
provider="$(printf '%s' "$provider" | tr '[:upper:]' '[:lower:]')"
|
|
561
|
+
if [[ "$provider" != "openai" && "$provider" != "anthropic" ]]; then
|
|
562
|
+
provider="anthropic"
|
|
563
|
+
fi
|
|
564
|
+
export AI_PRIMARY_PROVIDER="$provider"
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
# Helper function to load OpenAI model
|
|
568
|
+
load_openai_model() {
|
|
569
|
+
if [[ -n "${OPENAI_MODEL:-}" ]]; then
|
|
570
|
+
return 0
|
|
571
|
+
fi
|
|
572
|
+
|
|
573
|
+
local config_file="$HOME/.orchestra/config.json"
|
|
574
|
+
local model=""
|
|
575
|
+
if [[ -f "$config_file" ]]; then
|
|
576
|
+
if have_cmd jq; then
|
|
577
|
+
model="$(jq -r '.openai_model // empty' "$config_file" 2>/dev/null)"
|
|
578
|
+
else
|
|
579
|
+
bridge_init_json_backend
|
|
580
|
+
case "$BRIDGE_JSON_BACKEND" in
|
|
581
|
+
python)
|
|
582
|
+
model="$(python3 - "$config_file" <<'PY'
|
|
583
|
+
import json
|
|
584
|
+
import sys
|
|
585
|
+
try:
|
|
586
|
+
data = json.load(open(sys.argv[1], 'r'))
|
|
587
|
+
except Exception:
|
|
588
|
+
data = {}
|
|
589
|
+
value = data.get('openai_model') or ''
|
|
590
|
+
print(value)
|
|
591
|
+
PY
|
|
592
|
+
)"
|
|
593
|
+
;;
|
|
594
|
+
node)
|
|
595
|
+
model="$(node - "$config_file" <<'NODE'
|
|
596
|
+
const fs = require('fs');
|
|
597
|
+
let value = '';
|
|
598
|
+
try {
|
|
599
|
+
const data = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
600
|
+
value = data.openai_model || '';
|
|
601
|
+
} catch (err) {
|
|
602
|
+
value = '';
|
|
603
|
+
}
|
|
604
|
+
process.stdout.write(value);
|
|
605
|
+
NODE
|
|
606
|
+
)"
|
|
607
|
+
;;
|
|
608
|
+
esac
|
|
609
|
+
fi
|
|
610
|
+
fi
|
|
611
|
+
|
|
612
|
+
if [[ -z "$model" || "$model" == "null" ]]; then
|
|
613
|
+
model="gpt-4o-mini"
|
|
614
|
+
fi
|
|
615
|
+
export OPENAI_MODEL="$model"
|
|
616
|
+
}
|