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.
- package/index.js +89 -7
- 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.
|
|
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) {
|