greptile 2.2.10 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-app.sh +27 -2
- package/com.greptile.health.plist +2 -2
- package/greptile-fix +25 -4
- package/greptile-fix.applescript +26 -9
- package/health-server.js +110 -8
- package/package.json +4 -1
- package/postinstall.sh +29 -5
package/build-app.sh
CHANGED
|
@@ -24,12 +24,37 @@ APP_PATH="$OUTPUT_DIR/$APP_NAME.app"
|
|
|
24
24
|
|
|
25
25
|
echo "Building $APP_NAME.app..."
|
|
26
26
|
|
|
27
|
-
#
|
|
27
|
+
# Resolve the absolute path to greptile-fix at install time so the .app
|
|
28
|
+
# works even when launched by macOS with a minimal PATH (e.g. nvm/fnm/volta users).
|
|
29
|
+
GREPTILE_FIX_BIN="$(command -v greptile-fix 2>/dev/null || echo "")"
|
|
30
|
+
if [ -z "$GREPTILE_FIX_BIN" ]; then
|
|
31
|
+
# Fallback: it should be next to this script in bin/
|
|
32
|
+
GREPTILE_FIX_BIN="$SCRIPT_DIR/bin/greptile-fix.js"
|
|
33
|
+
fi
|
|
34
|
+
GREPTILE_FIX_DIR="$(dirname "$GREPTILE_FIX_BIN")"
|
|
35
|
+
echo "Resolved greptile-fix: $GREPTILE_FIX_BIN"
|
|
36
|
+
|
|
37
|
+
# 1. Compile AppleScript into .app bundle — inject the resolved bin directory
|
|
38
|
+
# into the PATH so it works regardless of node version manager.
|
|
28
39
|
if [ -d "$APP_PATH" ]; then
|
|
29
40
|
rm -rf "$APP_PATH"
|
|
30
41
|
fi
|
|
31
42
|
|
|
32
|
-
|
|
43
|
+
TEMP_SCRIPT="$(mktemp /tmp/greptile-fix-XXXXXX.applescript)"
|
|
44
|
+
# Clean up temp file on exit (success or failure)
|
|
45
|
+
trap 'rm -f "$TEMP_SCRIPT"' EXIT
|
|
46
|
+
|
|
47
|
+
# Escape sed special characters in the directory path (& \ /)
|
|
48
|
+
ESCAPED_DIR="$(printf '%s' "$GREPTILE_FIX_DIR" | sed 's/[&\\/]/\\&/g')"
|
|
49
|
+
sed "s|PATH=/opt/homebrew/bin:/usr/local/bin:|PATH=$ESCAPED_DIR:/opt/homebrew/bin:/usr/local/bin:|" \
|
|
50
|
+
"$SCRIPT_DIR/greptile-fix.applescript" > "$TEMP_SCRIPT"
|
|
51
|
+
|
|
52
|
+
osacompile -o "$APP_PATH" "$TEMP_SCRIPT"
|
|
53
|
+
|
|
54
|
+
if [ ! -d "$APP_PATH" ]; then
|
|
55
|
+
echo "Error: osacompile failed to create $APP_PATH"
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
33
58
|
|
|
34
59
|
# 2. Add URL scheme to Info.plist
|
|
35
60
|
PLIST="$APP_PATH/Contents/Info.plist"
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
<key>KeepAlive</key>
|
|
17
17
|
<true/>
|
|
18
18
|
<key>StandardOutPath</key>
|
|
19
|
-
<string
|
|
19
|
+
<string>__HOME__/.cache/greptile/greptile-health.log</string>
|
|
20
20
|
<key>StandardErrorPath</key>
|
|
21
|
-
<string
|
|
21
|
+
<string>__HOME__/.cache/greptile/greptile-health.log</string>
|
|
22
22
|
</dict>
|
|
23
23
|
</plist>
|
package/greptile-fix
CHANGED
|
@@ -15,11 +15,23 @@
|
|
|
15
15
|
|
|
16
16
|
set -euo pipefail
|
|
17
17
|
|
|
18
|
+
# Verify python3 is available (used for URL decoding and JSON operations)
|
|
19
|
+
if ! command -v python3 &>/dev/null; then
|
|
20
|
+
echo "Error: python3 is required but not found in PATH." >&2
|
|
21
|
+
osascript -e 'display dialog "Greptile Fix requires Python 3, which was not found on this system.\n\nPlease install it via: xcode-select --install" buttons {"OK"} default button "OK" with icon caution' 2>/dev/null || true
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
18
25
|
CONFIG_DIR="$HOME/.greptile"
|
|
19
26
|
REPOS_FILE="$CONFIG_DIR/repos.json"
|
|
20
|
-
|
|
27
|
+
CACHE_DIR="$HOME/.cache/greptile"
|
|
28
|
+
LOG_FILE="$CACHE_DIR/greptile-fix.log"
|
|
21
29
|
|
|
22
30
|
log() {
|
|
31
|
+
# Rotate log if it exceeds 1MB
|
|
32
|
+
if [ -f "$LOG_FILE" ] && [ "$(wc -c < "$LOG_FILE" 2>/dev/null || echo 0)" -gt 1048576 ]; then
|
|
33
|
+
mv -f "$LOG_FILE" "$LOG_FILE.old" 2>/dev/null || true
|
|
34
|
+
fi
|
|
23
35
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
|
|
24
36
|
}
|
|
25
37
|
|
|
@@ -54,11 +66,15 @@ parse_url_param() {
|
|
|
54
66
|
done
|
|
55
67
|
}
|
|
56
68
|
|
|
57
|
-
# Ensure config
|
|
69
|
+
# Ensure config and cache directories exist
|
|
58
70
|
ensure_config_dir() {
|
|
59
71
|
if [ ! -d "$CONFIG_DIR" ]; then
|
|
60
72
|
mkdir -p "$CONFIG_DIR"
|
|
61
73
|
fi
|
|
74
|
+
if [ ! -d "$CACHE_DIR" ]; then
|
|
75
|
+
mkdir -p "$CACHE_DIR"
|
|
76
|
+
chmod 700 "$CACHE_DIR"
|
|
77
|
+
fi
|
|
62
78
|
if [ ! -f "$REPOS_FILE" ]; then
|
|
63
79
|
echo '{}' > "$REPOS_FILE"
|
|
64
80
|
fi
|
|
@@ -120,13 +136,13 @@ create_runner_script() {
|
|
|
120
136
|
# Write args to a temp file — avoids all shell quoting issues.
|
|
121
137
|
# Line 1: repo path, Line 2+: prompt (may be multiline)
|
|
122
138
|
local argsfile
|
|
123
|
-
argsfile=$(mktemp /
|
|
139
|
+
argsfile=$(mktemp "$CACHE_DIR/greptile-fix-args-XXXXXX")
|
|
124
140
|
printf '%s\n' "$repo_path" > "$argsfile"
|
|
125
141
|
printf '%s' "$prompt" >> "$argsfile"
|
|
126
142
|
|
|
127
143
|
# Write a runner script that reads args from the file, then cleans up
|
|
128
144
|
local runner
|
|
129
|
-
runner=$(mktemp /
|
|
145
|
+
runner=$(mktemp "$CACHE_DIR/greptile-fix-run-XXXXXX")
|
|
130
146
|
chmod +x "$runner"
|
|
131
147
|
|
|
132
148
|
# Note: $argsfile and $cli_cmd are safe to interpolate (mktemp path and validated CLI name)
|
|
@@ -171,6 +187,7 @@ esac
|
|
|
171
187
|
# Parse URL parameters
|
|
172
188
|
PROMPT=$(parse_url_param "$URL" "prompt")
|
|
173
189
|
REPO=$(parse_url_param "$URL" "repo")
|
|
190
|
+
ACK_ID=$(parse_url_param "$URL" "ack")
|
|
174
191
|
|
|
175
192
|
if [ -z "$PROMPT" ]; then
|
|
176
193
|
log "ERROR: No prompt parameter in URL"
|
|
@@ -218,5 +235,9 @@ log "Runner script: $RUNNER"
|
|
|
218
235
|
# Print the runner path to stdout — the AppleScript app reads this
|
|
219
236
|
# and uses it to open Terminal (which requires Automation permission
|
|
220
237
|
# that only the .app bundle has).
|
|
238
|
+
# Line 1: runner path, Line 2 (optional): ACK ID for auto-redirect
|
|
221
239
|
echo "$RUNNER"
|
|
240
|
+
if [ -n "$ACK_ID" ]; then
|
|
241
|
+
echo "$ACK_ID"
|
|
242
|
+
fi
|
|
222
243
|
log "Done"
|
package/greptile-fix.applescript
CHANGED
|
@@ -10,8 +10,18 @@
|
|
|
10
10
|
|
|
11
11
|
on open location theURL
|
|
12
12
|
try
|
|
13
|
-
--
|
|
14
|
-
|
|
13
|
+
-- Ensure log directory exists
|
|
14
|
+
do shell script "mkdir -p $HOME/.cache/greptile"
|
|
15
|
+
-- Call greptile-fix to parse URL and create runner script
|
|
16
|
+
-- Output: line 1 = runner path, line 2 (optional) = ACK ID for auto-redirect
|
|
17
|
+
set fixOutput to do shell script "PATH=/opt/homebrew/bin:/usr/local/bin:$PATH greptile-fix " & quoted form of theURL & " 2>> $HOME/.cache/greptile/greptile-fix.log"
|
|
18
|
+
|
|
19
|
+
set outputLines to paragraphs of fixOutput
|
|
20
|
+
set runnerPath to item 1 of outputLines
|
|
21
|
+
set ackId to ""
|
|
22
|
+
if (count of outputLines) > 1 then
|
|
23
|
+
set ackId to item 2 of outputLines
|
|
24
|
+
end if
|
|
15
25
|
|
|
16
26
|
if runnerPath is not "" then
|
|
17
27
|
-- Detect terminal by checking running processes via shell (no accessibility permissions needed).
|
|
@@ -34,28 +44,35 @@ on open location theURL
|
|
|
34
44
|
end if
|
|
35
45
|
end try
|
|
36
46
|
|
|
37
|
-
do shell script "echo 'Detected terminal: " & termApp & "' >> /
|
|
47
|
+
do shell script "echo 'Detected terminal: " & termApp & "' >> $HOME/.cache/greptile/greptile-fix.log"
|
|
38
48
|
|
|
39
49
|
-- Terminal-specific launch commands for macOS.
|
|
40
50
|
-- Ghostty/kitty/WezTerm/Alacritty close the window when the -e command exits,
|
|
41
51
|
-- so we launch bash and source the runner script to keep the shell session alive
|
|
42
52
|
-- after the IDE command (claude/codex/cursor) finishes.
|
|
43
53
|
-- Unset CLAUDECODE so the new session doesn't think it's nested inside an existing one.
|
|
44
|
-
set cleanEnv to "unset CLAUDECODE; source " & runnerPath & " ; exec zsh"
|
|
54
|
+
set cleanEnv to "unset CLAUDECODE; source " & quoted form of runnerPath & " ; exec zsh"
|
|
45
55
|
if termApp is "Ghostty" then
|
|
46
|
-
do shell script "open -na Ghostty --args -e zsh -li -c
|
|
56
|
+
do shell script "open -na Ghostty --args -e zsh -li -c " & quoted form of cleanEnv & " &>/dev/null &"
|
|
47
57
|
else if termApp is "kitty" then
|
|
48
|
-
do shell script "open -na kitty --args zsh -li -c
|
|
58
|
+
do shell script "open -na kitty --args zsh -li -c " & quoted form of cleanEnv & " &>/dev/null &"
|
|
49
59
|
else if termApp is "WezTerm" then
|
|
50
|
-
do shell script "open -na WezTerm --args start -- zsh -li -c
|
|
60
|
+
do shell script "open -na WezTerm --args start -- zsh -li -c " & quoted form of cleanEnv & " &>/dev/null &"
|
|
51
61
|
else if termApp is "Alacritty" then
|
|
52
|
-
do shell script "open -na Alacritty --args -e zsh -li -c
|
|
62
|
+
do shell script "open -na Alacritty --args -e zsh -li -c " & quoted form of cleanEnv & " &>/dev/null &"
|
|
53
63
|
else
|
|
54
64
|
-- Terminal.app, iTerm, and Warp natively execute scripts via "open -a"
|
|
55
65
|
do shell script "open -a " & quoted form of termApp & " " & quoted form of runnerPath
|
|
56
66
|
end if
|
|
67
|
+
|
|
68
|
+
-- Send ACK to health server so the web page can auto-redirect back to the PR
|
|
69
|
+
if ackId is not "" then
|
|
70
|
+
try
|
|
71
|
+
do shell script "curl -s -X POST http://127.0.0.1:4747/ack/" & quoted form of ackId & " --max-time 2 &>/dev/null &"
|
|
72
|
+
end try
|
|
73
|
+
end if
|
|
57
74
|
end if
|
|
58
75
|
on error errMsg
|
|
59
|
-
do shell script "echo '[ERROR] " & quoted form of errMsg & "' >> /
|
|
76
|
+
do shell script "echo '[ERROR] " & quoted form of errMsg & "' >> $HOME/.cache/greptile/greptile-fix.log"
|
|
60
77
|
end try
|
|
61
78
|
end open location
|
package/health-server.js
CHANGED
|
@@ -7,6 +7,21 @@ const { spawn } = require('child_process')
|
|
|
7
7
|
|
|
8
8
|
const PORT = 4747
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Compare two semver strings. Returns true if b is newer than a.
|
|
12
|
+
* Handles x.y.z format; ignores pre-release suffixes.
|
|
13
|
+
*/
|
|
14
|
+
function isNewerVersion(current, latest) {
|
|
15
|
+
const parse = (v) => (v || '').replace(/^v/, '').split('-')[0].split('.').map(Number)
|
|
16
|
+
const a = parse(current)
|
|
17
|
+
const b = parse(latest)
|
|
18
|
+
for (let i = 0; i < 3; i++) {
|
|
19
|
+
if ((b[i] || 0) > (a[i] || 0)) return true
|
|
20
|
+
if ((b[i] || 0) < (a[i] || 0)) return false
|
|
21
|
+
}
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
|
|
10
25
|
// Self-destruct: if the package has been uninstalled, clean up and exit.
|
|
11
26
|
// Checks that package.json next to this script still exists.
|
|
12
27
|
// Note: we intentionally don't check `command -v greptile-fix` because
|
|
@@ -72,10 +87,52 @@ function checkForUpdate() {
|
|
|
72
87
|
res.on('end', () => {
|
|
73
88
|
try {
|
|
74
89
|
const latest = JSON.parse(data)
|
|
75
|
-
if (latest.version && latest.version
|
|
90
|
+
if (latest.version && isNewerVersion(localVersion, latest.version)) {
|
|
76
91
|
console.log(`Update available: ${localVersion} → ${latest.version}. Installing...`)
|
|
77
|
-
// Resolve npm path — launchd has minimal PATH
|
|
78
|
-
|
|
92
|
+
// Resolve npm path — launchd has minimal PATH.
|
|
93
|
+
// Check well-known locations including node version managers.
|
|
94
|
+
const home = process.env.HOME || ''
|
|
95
|
+
const npmPaths = [
|
|
96
|
+
'/opt/homebrew/bin/npm',
|
|
97
|
+
'/usr/local/bin/npm',
|
|
98
|
+
...(home
|
|
99
|
+
? [
|
|
100
|
+
path.join(home, '.volta/bin/npm'),
|
|
101
|
+
path.join(home, '.asdf/shims/npm'),
|
|
102
|
+
path.join(home, '.local/bin/npm'),
|
|
103
|
+
]
|
|
104
|
+
: []),
|
|
105
|
+
]
|
|
106
|
+
// Also check nvm: find the current default node version's npm
|
|
107
|
+
if (home) {
|
|
108
|
+
try {
|
|
109
|
+
const nvmDir = path.join(home, '.nvm/versions/node')
|
|
110
|
+
if (fs.existsSync(nvmDir)) {
|
|
111
|
+
const versions = fs.readdirSync(nvmDir).sort().reverse()
|
|
112
|
+
for (const v of versions) {
|
|
113
|
+
const candidate = path.join(nvmDir, v, 'bin/npm')
|
|
114
|
+
if (fs.existsSync(candidate)) {
|
|
115
|
+
npmPaths.push(candidate)
|
|
116
|
+
break
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch {}
|
|
121
|
+
// Also check fnm
|
|
122
|
+
try {
|
|
123
|
+
const fnmDir = path.join(home, '.local/share/fnm/node-versions')
|
|
124
|
+
if (fs.existsSync(fnmDir)) {
|
|
125
|
+
const versions = fs.readdirSync(fnmDir).sort().reverse()
|
|
126
|
+
for (const v of versions) {
|
|
127
|
+
const candidate = path.join(fnmDir, v, 'installation/bin/npm')
|
|
128
|
+
if (fs.existsSync(candidate)) {
|
|
129
|
+
npmPaths.push(candidate)
|
|
130
|
+
break
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch {}
|
|
135
|
+
}
|
|
79
136
|
let npmBin = 'npm'
|
|
80
137
|
for (const p of npmPaths) {
|
|
81
138
|
if (fs.existsSync(p)) {
|
|
@@ -83,16 +140,17 @@ function checkForUpdate() {
|
|
|
83
140
|
break
|
|
84
141
|
}
|
|
85
142
|
}
|
|
143
|
+
isUpdating = true
|
|
86
144
|
let child
|
|
87
145
|
try {
|
|
88
146
|
child = spawn(npmBin, ['install', '-g', 'greptile@latest'], {
|
|
89
147
|
stdio: 'inherit',
|
|
90
148
|
})
|
|
91
149
|
} catch (err) {
|
|
150
|
+
isUpdating = false
|
|
92
151
|
console.error('Failed to spawn npm:', err.message)
|
|
93
152
|
return
|
|
94
153
|
}
|
|
95
|
-
isUpdating = true
|
|
96
154
|
child.on('close', (code) => {
|
|
97
155
|
isUpdating = false
|
|
98
156
|
if (code === 0) {
|
|
@@ -117,18 +175,42 @@ function checkForUpdate() {
|
|
|
117
175
|
})
|
|
118
176
|
}
|
|
119
177
|
|
|
178
|
+
// --- ACK store for "Fix in X" auto-redirect ---
|
|
179
|
+
const ackStore = new Map() // id → timestamp
|
|
180
|
+
const ACK_TTL_MS = 60_000
|
|
181
|
+
|
|
182
|
+
function pruneAcks() {
|
|
183
|
+
const now = Date.now()
|
|
184
|
+
for (const [id, ts] of ackStore) {
|
|
185
|
+
if (now - ts > ACK_TTL_MS) ackStore.delete(id)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
setInterval(pruneAcks, 30_000)
|
|
189
|
+
|
|
190
|
+
const ACK_PATH_RE = /^\/ack\/([a-zA-Z0-9_-]+)$/
|
|
191
|
+
|
|
120
192
|
const ALLOWED_ORIGINS = ['https://app.greptile.com', 'https://app.staging.greptile.com', 'http://localhost:3000']
|
|
121
193
|
|
|
194
|
+
function isAllowedOrigin(origin) {
|
|
195
|
+
if (ALLOWED_ORIGINS.includes(origin)) return true
|
|
196
|
+
// Allow Vercel preview deployments (e.g. https://greptilia-abc123.vercel.app)
|
|
197
|
+
if (/^https:\/\/greptilia-[a-z0-9-]+\.vercel\.app$/.test(origin)) return true
|
|
198
|
+
return false
|
|
199
|
+
}
|
|
200
|
+
|
|
122
201
|
const server = http.createServer((req, res) => {
|
|
123
202
|
const origin = req.headers.origin || ''
|
|
124
|
-
|
|
203
|
+
const allowed = isAllowedOrigin(origin)
|
|
204
|
+
|
|
205
|
+
if (allowed) {
|
|
125
206
|
res.setHeader('Access-Control-Allow-Origin', origin)
|
|
207
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
|
208
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
|
|
209
|
+
res.setHeader('Access-Control-Allow-Private-Network', 'true')
|
|
126
210
|
}
|
|
127
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS')
|
|
128
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
|
|
129
211
|
|
|
130
212
|
if (req.method === 'OPTIONS') {
|
|
131
|
-
res.writeHead(204)
|
|
213
|
+
res.writeHead(allowed ? 204 : 403)
|
|
132
214
|
res.end()
|
|
133
215
|
return
|
|
134
216
|
}
|
|
@@ -139,6 +221,26 @@ const server = http.createServer((req, res) => {
|
|
|
139
221
|
return
|
|
140
222
|
}
|
|
141
223
|
|
|
224
|
+
// ACK endpoints for "Fix in X" auto-redirect
|
|
225
|
+
const urlPath = (req.url || '').split('?')[0]
|
|
226
|
+
const ackMatch = urlPath.match(ACK_PATH_RE)
|
|
227
|
+
if (ackMatch) {
|
|
228
|
+
const id = ackMatch[1]
|
|
229
|
+
if (req.method === 'POST') {
|
|
230
|
+
ackStore.set(id, Date.now())
|
|
231
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
232
|
+
res.end(JSON.stringify({ ok: true }))
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
if (req.method === 'GET') {
|
|
236
|
+
const acked = ackStore.has(id)
|
|
237
|
+
if (acked) ackStore.delete(id)
|
|
238
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
239
|
+
res.end(JSON.stringify({ acked }))
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
142
244
|
res.writeHead(404)
|
|
143
245
|
res.end()
|
|
144
246
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "greptile",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Bridge for Greptile code review 'Fix in Claude Code' and 'Fix in Codex' links",
|
|
5
5
|
"bin": {
|
|
6
6
|
"greptile-fix": "bin/greptile-fix.js"
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
"health-server.js",
|
|
20
20
|
"com.greptile.health.plist"
|
|
21
21
|
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18.0.0"
|
|
24
|
+
},
|
|
22
25
|
"os": [
|
|
23
26
|
"darwin"
|
|
24
27
|
],
|
package/postinstall.sh
CHANGED
|
@@ -20,7 +20,10 @@ APP_DIR="$HOME/Applications"
|
|
|
20
20
|
mkdir -p "$APP_DIR"
|
|
21
21
|
|
|
22
22
|
# Build the .app bundle into ~/Applications
|
|
23
|
-
bash "$SCRIPT_DIR/build-app.sh" "$APP_DIR"
|
|
23
|
+
if ! bash "$SCRIPT_DIR/build-app.sh" "$APP_DIR"; then
|
|
24
|
+
echo "greptile: Warning — failed to build .app bundle. The greptile:// URL scheme may not work."
|
|
25
|
+
echo "greptile: You can retry by running: bash $(printf '%q' "$SCRIPT_DIR/build-app.sh") $(printf '%q' "$APP_DIR")"
|
|
26
|
+
fi
|
|
24
27
|
|
|
25
28
|
# --- Health server LaunchAgent ---
|
|
26
29
|
PLIST_NAME="com.greptile.health.plist"
|
|
@@ -31,18 +34,39 @@ if [ -f "$PLIST_SRC" ]; then
|
|
|
31
34
|
# Ensure LaunchAgents directory exists
|
|
32
35
|
mkdir -p "$HOME/Library/LaunchAgents"
|
|
33
36
|
|
|
34
|
-
# Resolve the node binary path
|
|
35
|
-
NODE_BIN="$(command -v node 2>/dev/null || echo "
|
|
37
|
+
# Resolve the node binary path (check common locations for Apple Silicon and Intel Macs)
|
|
38
|
+
NODE_BIN="$(command -v node 2>/dev/null || echo "")"
|
|
39
|
+
if [ -z "$NODE_BIN" ]; then
|
|
40
|
+
for candidate in /opt/homebrew/bin/node /usr/local/bin/node; do
|
|
41
|
+
if [ -x "$candidate" ]; then
|
|
42
|
+
NODE_BIN="$candidate"
|
|
43
|
+
break
|
|
44
|
+
fi
|
|
45
|
+
done
|
|
46
|
+
fi
|
|
47
|
+
if [ -z "$NODE_BIN" ]; then
|
|
48
|
+
echo "greptile: Warning — could not find node binary. Health server may not start."
|
|
49
|
+
NODE_BIN="/usr/local/bin/node"
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Ensure log directory exists
|
|
53
|
+
mkdir -p "$HOME/.cache/greptile"
|
|
54
|
+
chmod 700 "$HOME/.cache/greptile"
|
|
36
55
|
|
|
37
56
|
# Create a configured copy with the correct paths
|
|
38
57
|
sed -e "s|__PACKAGE_DIR__|$SCRIPT_DIR|g" \
|
|
58
|
+
-e "s|__HOME__|$HOME|g" \
|
|
39
59
|
-e "s|/usr/local/bin/node|$NODE_BIN|g" \
|
|
40
60
|
"$PLIST_SRC" > "$PLIST_DST"
|
|
41
61
|
|
|
42
62
|
# Load the agent (unload first in case it's already loaded)
|
|
43
63
|
launchctl unload "$PLIST_DST" 2>/dev/null || true
|
|
44
|
-
launchctl load "$PLIST_DST"
|
|
45
|
-
|
|
64
|
+
if launchctl load "$PLIST_DST" 2>/dev/null; then
|
|
65
|
+
echo "Greptile health server installed and started."
|
|
66
|
+
else
|
|
67
|
+
echo "greptile: Warning — failed to load health server LaunchAgent."
|
|
68
|
+
echo "greptile: You can retry by running: launchctl load $(printf '%q' "$PLIST_DST")"
|
|
69
|
+
fi
|
|
46
70
|
fi
|
|
47
71
|
|
|
48
72
|
echo ""
|