autopilot-code 0.0.17 → 0.0.19

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/dist/cli.js CHANGED
@@ -158,6 +158,8 @@ function generateSystemdUnit(logPath, intervalSeconds, useSystem) {
158
158
  ];
159
159
  const user = process.env.USER || "root";
160
160
  const home = process.env.HOME || "/root";
161
+ // Include current PATH so systemd can find nvm-installed binaries like opencode
162
+ const currentPath = process.env.PATH || "/usr/bin:/bin";
161
163
  return `[Unit]
162
164
  Description=Autopilot automated issue runner
163
165
  After=network.target
@@ -173,6 +175,8 @@ StandardError=journal
173
175
  SyslogIdentifier=autopilot
174
176
  ${useSystem ? `User=${user}` : ""}
175
177
  Environment="NODE_ENV=production"
178
+ Environment="PATH=${currentPath}"
179
+ Environment="HOME=${home}"
176
180
 
177
181
  [Install]
178
182
  WantedBy=${useSystem ? "multi-user.target" : "default.target"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autopilot-code",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "private": false,
5
5
  "description": "Repo-issue–driven autopilot runner",
6
6
  "license": "MIT",
@@ -24,6 +24,7 @@ import time
24
24
  from dataclasses import dataclass
25
25
  from pathlib import Path
26
26
  from typing import Any
27
+ import shutil
27
28
 
28
29
  STATE_DIR = ".autopilot"
29
30
  STATE_FILE = "state.json"
@@ -341,17 +342,51 @@ def check_pr_for_issue(cfg: RepoConfig, issue_number: int) -> dict[str, Any] | N
341
342
  return None
342
343
 
343
344
 
345
+ def find_opencode() -> str | None:
346
+ """Find opencode binary in PATH or common locations."""
347
+ # 1. Check PATH first
348
+ opencode = shutil.which("opencode")
349
+ if opencode:
350
+ return opencode
351
+
352
+ # 2. Check common nvm locations
353
+ home = Path.home()
354
+ nvm_dir = home / ".nvm" / "versions" / "node"
355
+ if nvm_dir.exists():
356
+ for node_version in sorted(nvm_dir.iterdir(), reverse=True):
357
+ candidate = node_version / "bin" / "opencode"
358
+ if candidate.exists() and candidate.is_file():
359
+ return str(candidate)
360
+
361
+ # 3. Other common locations
362
+ for path in [
363
+ home / ".local" / "bin" / "opencode",
364
+ Path("/usr/local/bin/opencode"),
365
+ home / ".npm-global" / "bin" / "opencode",
366
+ ]:
367
+ if path.exists() and path.is_file():
368
+ return str(path)
369
+
370
+ return None
371
+
372
+
344
373
  def resolve_merge_conflicts(cfg: RepoConfig, issue_number: int, pr: dict[str, Any], attempt: int) -> bool:
345
374
  """Attempt to resolve merge conflicts using the coding agent. Returns True if resolved."""
346
375
  if cfg.agent != "opencode":
347
376
  print(f"[{cfg.repo}] Cannot resolve conflicts: agent '{cfg.agent}' not supported (only 'opencode')", flush=True)
348
377
  return False
349
378
 
379
+ opencode_bin = find_opencode()
380
+ if not opencode_bin:
381
+ print(f"[{cfg.repo}] Cannot resolve conflicts: opencode not found", flush=True)
382
+ return False
383
+
350
384
  pr_number = pr["number"]
351
385
  branch = pr.get("headRefName", f"autopilot/issue-{issue_number}")
352
386
  worktree = Path(f"/tmp/autopilot-issue-{issue_number}")
353
387
 
354
388
  print(f"[{cfg.repo}] Attempting to resolve merge conflicts for PR #{pr_number} (attempt {attempt}/{cfg.conflict_resolution_max_attempts})", flush=True)
389
+ print(f"[{cfg.repo}] Using opencode: {opencode_bin}", flush=True)
355
390
 
356
391
  # Ensure worktree exists and is up to date
357
392
  if not worktree.exists():
@@ -414,7 +449,7 @@ Work rules:
414
449
 
415
450
  # Run opencode in the worktree
416
451
  agent_result = subprocess.run(
417
- ["opencode", "run", conflict_prompt],
452
+ [opencode_bin, "run", conflict_prompt],
418
453
  cwd=str(worktree),
419
454
  capture_output=True,
420
455
  text=True,
@@ -21,6 +21,7 @@ if command -v jq >/dev/null 2>&1; then
21
21
  AUTO_FIX_CHECKS=$(jq -r '.autoFixChecks // true' < .autopilot/autopilot.json)
22
22
  AUTO_FIX_CHECKS_MAX_ATTEMPTS=$(jq -r '.autoFixChecksMaxAttempts // 3' < .autopilot/autopilot.json)
23
23
  ALLOWED_USERS=$(jq -r '.allowedMergeUsers[]' < .autopilot/autopilot.json 2>/dev/null || true)
24
+ AGENT_PATH=$(jq -r '.agentPath // ""' < .autopilot/autopilot.json)
24
25
  else
25
26
  REPO=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json"))["repo"])')
26
27
  AUTO_MERGE=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json")).get("autoMerge", True))')
@@ -30,8 +31,49 @@ else
30
31
  AUTO_FIX_CHECKS=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json")).get("autoFixChecks", True))')
31
32
  AUTO_FIX_CHECKS_MAX_ATTEMPTS=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json")).get("autoFixChecksMaxAttempts", 3))')
32
33
  ALLOWED_USERS=$(python3 -c 'import json,sys; users=json.load(open(".autopilot/autopilot.json")).get("allowedMergeUsers", []); print("\n".join(users))' 2>/dev/null || true)
34
+ AGENT_PATH=$(python3 -c 'import json; print(json.load(open(".autopilot/autopilot.json")).get("agentPath", ""))' 2>/dev/null || true)
33
35
  fi
34
36
 
37
+ # Find opencode binary - check config, PATH, then common locations
38
+ find_opencode() {
39
+ # 1. Config-specified path
40
+ if [[ -n "$AGENT_PATH" ]] && [[ -x "$AGENT_PATH" ]]; then
41
+ echo "$AGENT_PATH"
42
+ return
43
+ fi
44
+
45
+ # 2. Already in PATH
46
+ if command -v opencode >/dev/null 2>&1; then
47
+ command -v opencode
48
+ return
49
+ fi
50
+
51
+ # 3. Common nvm locations
52
+ for node_dir in "$HOME/.nvm/versions/node"/*/bin; do
53
+ if [[ -x "$node_dir/opencode" ]]; then
54
+ echo "$node_dir/opencode"
55
+ return
56
+ fi
57
+ done
58
+
59
+ # 4. Other common locations
60
+ for path in "$HOME/.local/bin/opencode" "/usr/local/bin/opencode" "$HOME/.npm-global/bin/opencode"; do
61
+ if [[ -x "$path" ]]; then
62
+ echo "$path"
63
+ return
64
+ fi
65
+ done
66
+
67
+ # Not found
68
+ return 1
69
+ }
70
+
71
+ OPENCODE_BIN=$(find_opencode) || {
72
+ echo "[run_opencode_issue.sh] ERROR: opencode not found. Set 'agentPath' in autopilot.json or ensure opencode is installed." >&2
73
+ exit 1
74
+ }
75
+ echo "[run_opencode_issue.sh] Using opencode: $OPENCODE_BIN"
76
+
35
77
  WORKTREE="/tmp/autopilot-issue-$ISSUE_NUMBER"
36
78
  BRANCH="autopilot/issue-$ISSUE_NUMBER"
37
79
 
@@ -73,7 +115,7 @@ Work rules:
73
115
  - If the issue is a simple file-addition, just do it directly (no extra refactors)."
74
116
  # 4. Run opencode inside worktree
75
117
  cd "$WORKTREE"
76
- opencode run "$PROMPT"
118
+ "$OPENCODE_BIN" run "$PROMPT"
77
119
 
78
120
  # 5. Commit any changes OpenCode made
79
121
  if [[ -n "$(git status --porcelain)" ]]; then
@@ -190,7 +232,7 @@ if [[ "$AUTO_RESOLVE_CONFLICTS" == "true" ]] && [[ -n "$PR_URL" ]]; then
190
232
 
191
233
  After resolving all conflicts, report the files that were resolved."
192
234
 
193
- if opencode run "$CONFLICT_PROMPT"; then
235
+ if "$OPENCODE_BIN" run "$CONFLICT_PROMPT"; then
194
236
  # Check if there are still conflicts
195
237
  if [[ -z "$(git diff --name-only --diff-filter=U)" ]]; then
196
238
  echo "[run_opencode_issue.sh] Conflicts resolved by agent."
@@ -316,7 +358,7 @@ if [[ "$AUTO_MERGE" == "true" ]] && [[ -n "$PR_URL" ]]; then
316
358
 
317
359
  # Run opencode to fix the issue
318
360
  cd "$WORKTREE"
319
- if opencode run "$REPAIR_PROMPT"; then
361
+ if "$OPENCODE_BIN" run "$REPAIR_PROMPT"; then
320
362
  # Commit any changes
321
363
  if [[ -n "$(git status --porcelain)" ]]; then
322
364
  git add -A