gitdone-agent 0.5.3 → 0.5.6

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 (2) hide show
  1. package/index.js +89 -7
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -27,7 +27,7 @@ import { randomUUID } from 'node:crypto'
27
27
  // Reported to the server on every sync so the web UI can flag outdated agents.
28
28
  // Keep in lockstep with packages/agent/package.json "version" AND
29
29
  // src/lib/agentVersion.ts LATEST_AGENT_VERSION.
30
- const AGENT_VERSION = '0.5.3'
30
+ const AGENT_VERSION = '0.5.6'
31
31
 
32
32
  const AGENT_DIR = join(homedir(), '.gitdone-agent')
33
33
  const CONFIG_PATH = join(AGENT_DIR, 'config.json')
@@ -111,7 +111,9 @@ function getNodePath() {
111
111
  // native installer) so spawn works WITHOUT a shell — that lets us pass a
112
112
  // multi-line, non-ASCII prompt as a single argv element with no escaping. An
113
113
  // npm shim (claude.cmd) needs shell:true, where we collapse the prompt to one
114
- // line to survive cmd.exe parsing.
114
+ // line to survive cmd.exe parsing. Returns `found:false` when nothing was
115
+ // located, so callers can show a clear message instead of spawning bare
116
+ // `claude` and letting cmd.exe emit "'claude' is not recognized…".
115
117
  function findClaude() {
116
118
  const tryCmd = (c) => {
117
119
  try {
@@ -120,10 +122,27 @@ function findClaude() {
120
122
  } catch { return [] }
121
123
  }
122
124
  const cands = [...tryCmd('where claude'), ...tryCmd('which claude')]
125
+
126
+ // The agent is auto-started at login, so its PATH is frozen at that moment —
127
+ // a `claude` installed (or a PATH entry added) afterwards is invisible to
128
+ // `where`/`which` above. Probe the well-known install locations directly so a
129
+ // freshly-installed CLI works without a Windows re-login.
130
+ const home = homedir()
131
+ const appData = process.env.APPDATA || join(home, 'AppData', 'Roaming')
132
+ for (const p of [
133
+ join(home, '.local', 'bin', 'claude.exe'), // Windows native installer
134
+ join(appData, 'npm', 'claude.cmd'), // Windows npm global shim
135
+ join(home, '.local', 'bin', 'claude'), // macOS/Linux native installer
136
+ '/usr/local/bin/claude', // Homebrew (Intel) / manual install
137
+ '/opt/homebrew/bin/claude', // Homebrew (Apple silicon)
138
+ ]) {
139
+ if (existsSync(p) && !cands.includes(p)) cands.push(p)
140
+ }
141
+
123
142
  const exe = cands.find((p) => /\.exe$/i.test(p))
124
- if (exe) return { path: exe, shell: false }
125
- if (cands.length) return { path: cands[0], shell: true }
126
- return { path: 'claude', shell: true }
143
+ if (exe) return { path: exe, shell: false, found: true }
144
+ if (cands.length) return { path: cands[0], shell: true, found: true }
145
+ return { path: 'claude', shell: true, found: false }
127
146
  }
128
147
 
129
148
  // Kill any previously-running agent processes so an update applies WITHOUT a
@@ -194,6 +213,8 @@ function runDoctor() {
194
213
  console.log(` autostart vbs : ${getVbsPath()} ${existsSync(getVbsPath()) ? '(ok)' : '(not installed)'}`)
195
214
  console.log(` config : ${CONFIG_PATH} ${existsSync(CONFIG_PATH) ? '(ok)' : '(MISSING)'}`)
196
215
  console.log(` log file : ${LOG_PATH} ${existsSync(LOG_PATH) ? '(ok)' : '(none yet)'}`)
216
+ const claude = findClaude()
217
+ console.log(` claude cli : ${claude.found ? claude.path : 'NOT FOUND — инсталирай от claude.ai/code и рестартирай агента'}`)
197
218
  if (cfg) {
198
219
  console.log(` machineId : ${cfg.machineId}`)
199
220
  console.log(` server : ${cfg.url}`)
@@ -545,7 +566,14 @@ function runAiCommand(cfg, cmd, repoPath) {
545
566
  return
546
567
  }
547
568
 
548
- const { path: claudePath, shell } = findClaude()
569
+ const { path: claudePath, shell, found } = findClaude()
570
+ if (!found) {
571
+ const msg = `Claude Code CLI не е намерен на този компютър (${cfg.hostname}). Инсталирай го от claude.ai/code и се увери, че „claude" е в PATH, после рестартирай агента.`
572
+ log(`✗ ai_run ${runId}: claude not found`)
573
+ postRunEvents(cfg, runId, [{ kind: 'SYSTEM', text: `✗ ${msg}` }], 'error', 'claude not found')
574
+ reportCommandResult(cfg, cmd.id, 'error', 'claude not found')
575
+ return
576
+ }
549
577
 
550
578
  let settingsPath
551
579
  try { settingsPath = ensureAiHookFiles() } catch (e) { log(`✗ ai hook setup failed: ${e.message}`) }
@@ -645,7 +673,14 @@ function runAiChat(cfg, cmd, repoPath) {
645
673
  return
646
674
  }
647
675
 
648
- const { path: claudePath, shell } = findClaude()
676
+ const { path: claudePath, shell, found } = findClaude()
677
+ if (!found) {
678
+ const msg = `Claude Code CLI не е намерен на този компютър (${cfg.hostname}). Инсталирай го от claude.ai/code и се увери, че „claude" е в PATH, после рестартирай агента.`
679
+ log(`✗ ai_chat ${sessionId}: claude not found`)
680
+ postSessionEvents(cfg, sessionId, [{ role: 'SYSTEM', text: `✗ ${msg}` }], 'error')
681
+ reportCommandResult(cfg, cmd.id, 'error', 'claude not found')
682
+ return
683
+ }
649
684
 
650
685
  let settingsPath
651
686
  try { settingsPath = ensureAiSessionHookFiles() } catch (e) { log(`✗ ai session hook setup failed: ${e.message}`) }
@@ -726,6 +761,38 @@ function runAiChat(cfg, cmd, repoPath) {
726
761
  })
727
762
  }
728
763
 
764
+ // deploy — run the project's deploy script (scripts/deploy.sh) in the repo.
765
+ // Long-running (git pull + npm build + service restart), so we spawn it detached
766
+ // from the poll loop and report the result on exit. The agent is its OWN process
767
+ // (not part of any gitdone systemd service), so a `systemctl restart` inside the
768
+ // script doesn't kill the deploy mid-flight — which a server-side spawn would.
769
+ function runDeployCommand(cfg, cmd, repoPath) {
770
+ const script = 'scripts/deploy.sh'
771
+ if (!existsSync(join(repoPath, script))) {
772
+ reportCommandResult(cfg, cmd.id, 'error', `няма ${script} в repo-то — няма какво да деплойна`)
773
+ log(`✗ deploy @ ${repoPath}: липсва ${script}`)
774
+ return
775
+ }
776
+ log(`▸ deploy стартиран @ ${repoPath}`)
777
+ const child = spawn('bash', [script], { cwd: repoPath, stdio: ['ignore', 'pipe', 'pipe'] })
778
+ // Keep only the tail of the output — enough to explain a failure without
779
+ // shipping a whole build log back.
780
+ let tail = ''
781
+ const capture = (d) => { tail = (tail + d.toString()).slice(-2000) }
782
+ child.stdout.on('data', capture)
783
+ child.stderr.on('data', capture)
784
+ child.on('error', (err) => {
785
+ reportCommandResult(cfg, cmd.id, 'error', `процесна грешка: ${err.message}`)
786
+ log(`✗ deploy failed @ ${repoPath}: ${err.message}`)
787
+ })
788
+ child.on('close', (code) => {
789
+ const ok = code === 0
790
+ const detail = tail.trim() ? `\n${tail.trim()}` : ''
791
+ reportCommandResult(cfg, cmd.id, ok ? 'done' : 'error', `exit ${code}${detail}`.slice(0, 4000))
792
+ log(`${ok ? '✓' : '✗'} deploy @ ${repoPath} приключи (code ${code})`)
793
+ })
794
+ }
795
+
729
796
  async function executeCommand(cfg, cmd, repoPath) {
730
797
  // ai_run is long-running + streaming — launch it in the background and return
731
798
  // so the snapshot loop keeps going. It reports its own result on exit.
@@ -738,6 +805,13 @@ async function executeCommand(cfg, cmd, repoPath) {
738
805
  runAiChat(cfg, cmd, repoPath)
739
806
  return
740
807
  }
808
+ // deploy — run the repo's deploy script. Long-running (pull + build + service
809
+ // restart), so it's launched in the background and reports its own result on
810
+ // exit, same as ai_run.
811
+ if (cmd.type === 'deploy') {
812
+ runDeployCommand(cfg, cmd, repoPath)
813
+ return
814
+ }
741
815
 
742
816
  let result = 'ok'
743
817
  let status = 'done'
@@ -762,6 +836,14 @@ async function executeCommand(cfg, cmd, repoPath) {
762
836
  result = pushOrPull(cmd.type, repoPath, auth)
763
837
  } else if (cmd.type === 'discard') {
764
838
  result = discardFile(repoPath, cmd.payload?.file)
839
+ } else {
840
+ // Unknown command type — almost always a newer server feature (e.g. the
841
+ // gd-255 `deploy` command) reaching an agent too old to handle it. Do NOT
842
+ // silently mark it done: an older agent used to fall through here and
843
+ // report status=done/result=ok, so a queued deploy looked "completed" while
844
+ // nothing ran. Surface it as an error so the failure is visible and the
845
+ // user knows to update the agent.
846
+ throw new Error(`непозната команда „${cmd.type}" — обнови gitdone-agent (npm i -g gitdone-agent@latest)`)
765
847
  }
766
848
  log(`✓ ${cmd.type} @ ${repoPath}: ${redact(result, auth?.token)}`)
767
849
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitdone-agent",
3
- "version": "0.5.3",
3
+ "version": "0.5.6",
4
4
  "description": "Local git agent for gitdone — watches a local repo and sends snapshots to gitdone.eu",
5
5
  "type": "module",
6
6
  "bin": {