greptile 2.4.0 → 3.0.1

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/health-server.js DELETED
@@ -1,264 +0,0 @@
1
- #!/usr/bin/env node
2
- const http = require('http')
3
- const https = require('https')
4
- const fs = require('fs')
5
- const path = require('path')
6
- const { spawn } = require('child_process')
7
-
8
- const PORT = 4747
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
-
25
- // Self-destruct: if the package has been uninstalled, clean up and exit.
26
- // Checks that package.json next to this script still exists.
27
- // Note: we intentionally don't check `command -v greptile-fix` because
28
- // launchd runs with a minimal PATH that doesn't include npm global bin.
29
- const PACKAGE_MARKER = path.join(__dirname, 'package.json')
30
- const SELF_CHECK_INTERVAL_MS = 30_000
31
- const UPDATE_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000 // 6 hours
32
- let isUpdating = false
33
-
34
- function selfCheck() {
35
- // Don't self-destruct during an update — npm temporarily removes the package dir
36
- if (isUpdating) return
37
-
38
- const markerGone = !fs.existsSync(PACKAGE_MARKER)
39
-
40
- if (markerGone) {
41
- console.log('Package uninstalled — cleaning up and exiting.')
42
- const { execSync } = require('child_process')
43
- const home = process.env.HOME || ''
44
- const plistPath = path.join(home, 'Library/LaunchAgents/com.greptile.health.plist')
45
- // Remove .app bundle and plist file
46
- try {
47
- fs.rmSync(path.join(home, 'Applications/Greptile Fix.app'), { recursive: true, force: true })
48
- } catch {}
49
- try {
50
- fs.unlinkSync(plistPath)
51
- } catch {}
52
- server.close()
53
- // Remove the launchd job by label so KeepAlive won't restart us
54
- try {
55
- execSync('launchctl remove com.greptile.health 2>/dev/null')
56
- } catch {}
57
- process.exit(0)
58
- }
59
- }
60
-
61
- // Auto-update: periodically check npm registry for a newer version and install it.
62
- // After a successful update, the process exits so launchd restarts it with the new code.
63
- function checkForUpdate() {
64
- if (isUpdating) return
65
- let localVersion
66
- try {
67
- const pkg = JSON.parse(fs.readFileSync(PACKAGE_MARKER, 'utf8'))
68
- localVersion = pkg.version
69
- } catch {
70
- return // Can't read local version, skip
71
- }
72
-
73
- const req = https.get('https://registry.npmjs.org/greptile/latest', { timeout: 10_000 }, (res) => {
74
- if (res.statusCode !== 200) {
75
- res.resume()
76
- return
77
- }
78
- let data = ''
79
- const MAX_BODY = 100_000 // 100KB safety limit
80
- res.on('data', (chunk) => {
81
- data += chunk
82
- if (data.length > MAX_BODY) {
83
- req.destroy()
84
- return
85
- }
86
- })
87
- res.on('end', () => {
88
- try {
89
- const latest = JSON.parse(data)
90
- if (latest.version && isNewerVersion(localVersion, latest.version)) {
91
- console.log(`Update available: ${localVersion} → ${latest.version}. Installing...`)
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
- }
136
- let npmBin = 'npm'
137
- for (const p of npmPaths) {
138
- if (fs.existsSync(p)) {
139
- npmBin = p
140
- break
141
- }
142
- }
143
- isUpdating = true
144
- let child
145
- try {
146
- child = spawn(npmBin, ['install', '-g', 'greptile@latest'], {
147
- stdio: 'inherit',
148
- })
149
- } catch (err) {
150
- isUpdating = false
151
- console.error('Failed to spawn npm:', err.message)
152
- return
153
- }
154
- child.on('close', (code) => {
155
- isUpdating = false
156
- if (code === 0) {
157
- console.log(`Updated to greptile@${latest.version}. Restarting...`)
158
- // Exit so launchd restarts the server with the new code
159
- setTimeout(() => process.exit(0), 2000)
160
- } else {
161
- console.error(`Update failed with exit code ${code}`)
162
- }
163
- })
164
- child.on('error', (err) => {
165
- isUpdating = false
166
- console.error('Update spawn error:', err.message)
167
- })
168
- }
169
- } catch {}
170
- })
171
- })
172
- req.on('error', () => {}) // Silently ignore network errors
173
- req.on('timeout', () => {
174
- req.destroy()
175
- })
176
- }
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
-
192
- const ALLOWED_ORIGINS = ['https://app.greptile.com', 'https://app.staging.greptile.com', 'http://localhost:3000']
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
-
201
- const server = http.createServer((req, res) => {
202
- const origin = req.headers.origin || ''
203
- const allowed = isAllowedOrigin(origin)
204
-
205
- if (allowed) {
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')
210
- }
211
-
212
- if (req.method === 'OPTIONS') {
213
- res.writeHead(allowed ? 204 : 403)
214
- res.end()
215
- return
216
- }
217
-
218
- if (req.method === 'GET' && req.url === '/health') {
219
- res.writeHead(200, { 'Content-Type': 'application/json' })
220
- res.end(JSON.stringify({ status: 'ok' }))
221
- return
222
- }
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
-
244
- res.writeHead(404)
245
- res.end()
246
- })
247
-
248
- server.on('error', (err) => {
249
- if (err.code === 'EADDRINUSE') {
250
- console.error(`Port ${PORT} is already in use. Exiting.`)
251
- process.exit(0) // Exit cleanly so launchd KeepAlive doesn't restart in a tight loop
252
- }
253
- console.error('Health server error:', err.message)
254
- process.exit(1)
255
- })
256
-
257
- server.listen(PORT, '127.0.0.1', () => {
258
- console.log(`Greptile health server listening on http://127.0.0.1:${PORT}`)
259
- // Start self-check loop after server is up
260
- setInterval(selfCheck, SELF_CHECK_INTERVAL_MS)
261
- // Check for updates on startup and then every 6 hours
262
- checkForUpdate()
263
- setInterval(checkForUpdate, UPDATE_CHECK_INTERVAL_MS)
264
- })
package/postinstall.sh DELETED
@@ -1,77 +0,0 @@
1
- #!/bin/bash
2
- #
3
- # postinstall.sh — Builds and registers the Greptile Fix .app bundle after npm install.
4
- #
5
- # The .app bundle handles the greptile:// URL scheme on macOS.
6
- # It is installed to ~/Applications so it persists independently of npm.
7
-
8
- set -euo pipefail
9
-
10
- # Only run on macOS
11
- if [ "$(uname)" != "Darwin" ]; then
12
- echo "greptile: Skipping .app bundle setup (macOS only)"
13
- exit 0
14
- fi
15
-
16
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
17
- APP_DIR="$HOME/Applications"
18
-
19
- # Ensure ~/Applications exists
20
- mkdir -p "$APP_DIR"
21
-
22
- # Build the .app bundle into ~/Applications
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
27
-
28
- # --- Health server LaunchAgent ---
29
- PLIST_NAME="com.greptile.health.plist"
30
- PLIST_SRC="$SCRIPT_DIR/$PLIST_NAME"
31
- PLIST_DST="$HOME/Library/LaunchAgents/$PLIST_NAME"
32
-
33
- if [ -f "$PLIST_SRC" ]; then
34
- # Ensure LaunchAgents directory exists
35
- mkdir -p "$HOME/Library/LaunchAgents"
36
-
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"
55
-
56
- # Create a configured copy with the correct paths
57
- sed -e "s|__PACKAGE_DIR__|$SCRIPT_DIR|g" \
58
- -e "s|__HOME__|$HOME|g" \
59
- -e "s|/usr/local/bin/node|$NODE_BIN|g" \
60
- "$PLIST_SRC" > "$PLIST_DST"
61
-
62
- # Load the agent (unload first in case it's already loaded)
63
- launchctl unload "$PLIST_DST" 2>/dev/null || true
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
70
- fi
71
-
72
- echo ""
73
- echo "✓ greptile-cli installed successfully."
74
- echo ""
75
- echo "Repo path mappings are stored in ~/.greptile/repos.json."
76
- echo "The first time you click 'Fix in Claude Code' for a repo,"
77
- echo "you'll be asked to select the local folder for that repo."
package/preuninstall.sh DELETED
@@ -1,23 +0,0 @@
1
- #!/bin/bash
2
- #
3
- # preuninstall.sh — Removes the Greptile Fix .app bundle on npm uninstall.
4
-
5
- # Only run on macOS
6
- if [ "$(uname)" != "Darwin" ]; then
7
- exit 0
8
- fi
9
-
10
- APP_PATH="$HOME/Applications/Greptile Fix.app"
11
-
12
- if [ -d "$APP_PATH" ]; then
13
- rm -rf "$APP_PATH"
14
- fi
15
-
16
- # --- Health server LaunchAgent ---
17
- PLIST_PATH="$HOME/Library/LaunchAgents/com.greptile.health.plist"
18
- if [ -f "$PLIST_PATH" ]; then
19
- launchctl unload "$PLIST_PATH" 2>/dev/null || true
20
- rm -f "$PLIST_PATH"
21
- fi
22
-
23
- echo "✓ greptile-cli uninstalled successfully."