gitdone-agent 0.1.1 → 0.1.3
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 +123 -15
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -2,11 +2,32 @@
|
|
|
2
2
|
// gitdone-agent — watches a local git repo and pushes snapshots to gitdone.eu
|
|
3
3
|
// Usage: bun index.js --key=gdo_xxx --name=my-repo --path=/path/to/repo [--install]
|
|
4
4
|
|
|
5
|
-
import { execSync } from 'node:child_process'
|
|
6
|
-
import { existsSync, writeFileSync, unlinkSync } from 'node:fs'
|
|
5
|
+
import { execSync, spawn } from 'node:child_process'
|
|
6
|
+
import { existsSync, writeFileSync, unlinkSync, mkdirSync, copyFileSync, appendFileSync } from 'node:fs'
|
|
7
7
|
import { resolve, join } from 'node:path'
|
|
8
8
|
import { homedir } from 'node:os'
|
|
9
9
|
|
|
10
|
+
// ─── Stable agent dir + logging ────────────────────────────────────────────────
|
|
11
|
+
// Everything persistent lives here: a stable copy of the agent script (so
|
|
12
|
+
// autostart never points at a purged npx temp dir) and a per-repo log file
|
|
13
|
+
// (so failures are visible even when the agent runs in a hidden window).
|
|
14
|
+
|
|
15
|
+
const AGENT_DIR = join(homedir(), '.gitdone-agent')
|
|
16
|
+
|
|
17
|
+
function ensureAgentDir() {
|
|
18
|
+
if (!existsSync(AGENT_DIR)) mkdirSync(AGENT_DIR, { recursive: true })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let LOG_PATH = null
|
|
22
|
+
|
|
23
|
+
function log(msg) {
|
|
24
|
+
const line = `[${new Date().toISOString()}] ${msg}`
|
|
25
|
+
console.log(line)
|
|
26
|
+
if (LOG_PATH) {
|
|
27
|
+
try { appendFileSync(LOG_PATH, line + '\n') } catch { /* best-effort */ }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
10
31
|
// ─── Arg parsing ─────────────────────────────────────────────────────────────
|
|
11
32
|
|
|
12
33
|
function parseArgs() {
|
|
@@ -27,17 +48,18 @@ function parseArgs() {
|
|
|
27
48
|
const url = (args.url ?? 'https://gitdone.eu').replace(/\/$/, '')
|
|
28
49
|
const install = 'install' in args
|
|
29
50
|
const uninstall = 'uninstall' in args
|
|
51
|
+
const doctor = 'doctor' in args
|
|
30
52
|
|
|
31
|
-
if (!key && !uninstall) {
|
|
53
|
+
if (!key && !uninstall && !doctor) {
|
|
32
54
|
console.error('Error: --key is required (e.g. --key=gdo_xxx)')
|
|
33
55
|
process.exit(1)
|
|
34
56
|
}
|
|
35
|
-
if (!existsSync(repoPath) && !uninstall) {
|
|
57
|
+
if (!existsSync(repoPath) && !uninstall && !doctor) {
|
|
36
58
|
console.error(`Error: path does not exist: ${repoPath}`)
|
|
37
59
|
process.exit(1)
|
|
38
60
|
}
|
|
39
61
|
|
|
40
|
-
return { key, path: repoPath, name, projectId, interval, url, install, uninstall }
|
|
62
|
+
return { key, path: repoPath, name, projectId, interval, url, install, uninstall, doctor }
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
// ─── Windows auto-start ───────────────────────────────────────────────────────
|
|
@@ -53,8 +75,19 @@ function getNodePath() {
|
|
|
53
75
|
}
|
|
54
76
|
|
|
55
77
|
function installStartup({ name, key, path: repoPath, url, interval, projectId }) {
|
|
78
|
+
ensureAgentDir()
|
|
56
79
|
const nodePath = getNodePath()
|
|
57
|
-
|
|
80
|
+
|
|
81
|
+
// Copy this script to a stable location. process.argv[1] may point inside an
|
|
82
|
+
// npx cache dir that gets purged later — referencing it from autostart would
|
|
83
|
+
// silently break on the next login. A stable copy survives.
|
|
84
|
+
const stableAgent = join(AGENT_DIR, 'agent.mjs')
|
|
85
|
+
try {
|
|
86
|
+
copyFileSync(resolve(process.argv[1]), stableAgent)
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error(`Warning: could not copy agent to ${stableAgent}: ${err.message}`)
|
|
89
|
+
}
|
|
90
|
+
const agentPath = existsSync(stableAgent) ? stableAgent : resolve(process.argv[1])
|
|
58
91
|
const startupDir = getStartupDir()
|
|
59
92
|
|
|
60
93
|
const argLine = [
|
|
@@ -76,9 +109,11 @@ function installStartup({ name, key, path: repoPath, url, interval, projectId })
|
|
|
76
109
|
writeFileSync(vbsPath, vbs, 'utf8')
|
|
77
110
|
|
|
78
111
|
console.log(`✓ Инсталиран! При следващото влизане в Windows агентът ще стартира автоматично.`)
|
|
79
|
-
console.log(`
|
|
80
|
-
console.log(`
|
|
81
|
-
console.log(`
|
|
112
|
+
console.log(` Файл : ${vbsPath}`)
|
|
113
|
+
console.log(` Агент : ${agentPath}`)
|
|
114
|
+
console.log(` Repo : ${repoPath}`)
|
|
115
|
+
console.log(` Name : ${name}`)
|
|
116
|
+
console.log(` Лог : ${join(AGENT_DIR, `${name}.log`)}`)
|
|
82
117
|
console.log()
|
|
83
118
|
}
|
|
84
119
|
|
|
@@ -92,6 +127,20 @@ function uninstallStartup(name) {
|
|
|
92
127
|
}
|
|
93
128
|
}
|
|
94
129
|
|
|
130
|
+
// ─── Doctor: print where things are so "offline" is debuggable ─────────────────
|
|
131
|
+
|
|
132
|
+
function runDoctor(name) {
|
|
133
|
+
const vbsPath = join(getStartupDir(), `gitdone-${name}.vbs`)
|
|
134
|
+
const stableAgent = join(AGENT_DIR, 'agent.mjs')
|
|
135
|
+
const logPath = join(AGENT_DIR, `${name}.log`)
|
|
136
|
+
console.log('gitdone-agent doctor')
|
|
137
|
+
console.log(` node : ${getNodePath()}`)
|
|
138
|
+
console.log(` agent dir : ${AGENT_DIR} ${existsSync(AGENT_DIR) ? '(ok)' : '(MISSING)'}`)
|
|
139
|
+
console.log(` stable agent : ${stableAgent} ${existsSync(stableAgent) ? '(ok)' : '(not installed)'}`)
|
|
140
|
+
console.log(` autostart vbs : ${vbsPath} ${existsSync(vbsPath) ? '(ok)' : '(not installed)'}`)
|
|
141
|
+
console.log(` log file : ${logPath} ${existsSync(logPath) ? '(ok)' : '(none yet)'}`)
|
|
142
|
+
}
|
|
143
|
+
|
|
95
144
|
// ─── Git helpers ─────────────────────────────────────────────────────────────
|
|
96
145
|
|
|
97
146
|
function git(cmd, cwd) {
|
|
@@ -148,9 +197,42 @@ function getSnapshot(repoPath) {
|
|
|
148
197
|
return { branch, modified, staged, aheadBy, behindBy, lastCommit, statuses, diffs }
|
|
149
198
|
}
|
|
150
199
|
|
|
151
|
-
// ─── Push to API
|
|
200
|
+
// ─── Push to API & execute commands ─────────────────────────────────────────
|
|
152
201
|
|
|
153
|
-
async function
|
|
202
|
+
async function reportCommandResult(url, key, id, status, result) {
|
|
203
|
+
await fetch(`${url}/api/v1/agent/command-result`, {
|
|
204
|
+
method: 'POST',
|
|
205
|
+
headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
206
|
+
body: JSON.stringify({ id, status, result }),
|
|
207
|
+
}).catch(() => {})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function executeCommand(cmd, repoPath, url, key) {
|
|
211
|
+
let result = 'ok'
|
|
212
|
+
let status = 'done'
|
|
213
|
+
try {
|
|
214
|
+
if (cmd.type === 'commit') {
|
|
215
|
+
const msg = (cmd.payload?.message ?? 'commit').replace(/"/g, '\\"')
|
|
216
|
+
git('git add -A', repoPath)
|
|
217
|
+
const out = git(`git commit -m "${msg}"`, repoPath)
|
|
218
|
+
result = out || 'committed'
|
|
219
|
+
} else if (cmd.type === 'push') {
|
|
220
|
+
const out = git('git push', repoPath)
|
|
221
|
+
result = out || 'pushed'
|
|
222
|
+
} else if (cmd.type === 'pull') {
|
|
223
|
+
const out = git('git pull', repoPath)
|
|
224
|
+
result = out || 'pulled'
|
|
225
|
+
}
|
|
226
|
+
log(`✓ ${cmd.type}: ${result}`)
|
|
227
|
+
} catch (err) {
|
|
228
|
+
status = 'error'
|
|
229
|
+
result = err.message
|
|
230
|
+
log(`✗ ${cmd.type} failed: ${result}`)
|
|
231
|
+
}
|
|
232
|
+
await reportCommandResult(url, key, cmd.id, status, result)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function push({ url, key, name, projectId, repoPath, snapshot }) {
|
|
154
236
|
const res = await fetch(`${url}/api/v1/agent/snapshot`, {
|
|
155
237
|
method: 'POST',
|
|
156
238
|
headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
@@ -160,13 +242,29 @@ async function push({ url, key, name, projectId, snapshot }) {
|
|
|
160
242
|
const text = await res.text().catch(() => '')
|
|
161
243
|
throw new Error(`HTTP ${res.status}: ${text}`)
|
|
162
244
|
}
|
|
245
|
+
const data = await res.json().catch(() => ({}))
|
|
246
|
+
for (const cmd of data.commands ?? []) {
|
|
247
|
+
await executeCommand(cmd, repoPath, url, key)
|
|
248
|
+
}
|
|
163
249
|
}
|
|
164
250
|
|
|
165
251
|
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
166
252
|
|
|
167
253
|
async function main() {
|
|
168
254
|
const config = parseArgs()
|
|
169
|
-
const { key, path: repoPath, name, projectId, interval, url, install, uninstall } = config
|
|
255
|
+
const { key, path: repoPath, name, projectId, interval, url, install, uninstall, doctor } = config
|
|
256
|
+
|
|
257
|
+
if (doctor) {
|
|
258
|
+
runDoctor(name)
|
|
259
|
+
process.exit(0)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Route all runtime logs to a per-repo file too, so a silently-running
|
|
263
|
+
// (hidden-window) agent still leaves a trail to debug "why is it offline".
|
|
264
|
+
if (!uninstall) {
|
|
265
|
+
ensureAgentDir()
|
|
266
|
+
LOG_PATH = join(AGENT_DIR, `${name}.log`)
|
|
267
|
+
}
|
|
170
268
|
|
|
171
269
|
if (uninstall) {
|
|
172
270
|
uninstallStartup(name)
|
|
@@ -175,6 +273,16 @@ async function main() {
|
|
|
175
273
|
|
|
176
274
|
if (install) {
|
|
177
275
|
installStartup(config)
|
|
276
|
+
|
|
277
|
+
// Launch the agent silently in the background right now via wscript.exe
|
|
278
|
+
const vbsPath = join(getStartupDir(), `gitdone-${name}.vbs`)
|
|
279
|
+
const child = spawn('wscript.exe', [vbsPath], { detached: true, stdio: 'ignore' })
|
|
280
|
+
child.unref()
|
|
281
|
+
|
|
282
|
+
console.log()
|
|
283
|
+
console.log('✓ Агентът е стартиран в заден план — можеш да затвориш прозореца.')
|
|
284
|
+
console.log(' При следващо влизане в Windows ще стартира автоматично.')
|
|
285
|
+
process.exit(0)
|
|
178
286
|
}
|
|
179
287
|
|
|
180
288
|
if (!isGitRepo(repoPath)) {
|
|
@@ -194,11 +302,11 @@ async function main() {
|
|
|
194
302
|
async function tick() {
|
|
195
303
|
try {
|
|
196
304
|
const snapshot = getSnapshot(repoPath)
|
|
197
|
-
await push({ url, key, name, projectId, snapshot })
|
|
305
|
+
await push({ url, key, name, projectId, repoPath, snapshot })
|
|
198
306
|
const changed = snapshot.modified.length + snapshot.staged.length
|
|
199
|
-
|
|
307
|
+
log(`✓ pushed — branch: ${snapshot.branch}, changes: ${changed}`)
|
|
200
308
|
} catch (err) {
|
|
201
|
-
|
|
309
|
+
log(`✗ error: ${err.message}`)
|
|
202
310
|
}
|
|
203
311
|
}
|
|
204
312
|
|