gitdone-agent 0.1.3 → 0.2.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/index.js +243 -159
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,65 +1,89 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// gitdone-agent —
|
|
3
|
-
//
|
|
2
|
+
// gitdone-agent — scans root folders for git repos and pushes snapshots of the
|
|
3
|
+
// ones the server marks as "tracked" to gitdone.eu.
|
|
4
|
+
//
|
|
5
|
+
// Setup (once):
|
|
6
|
+
// npx gitdone-agent --key=gdo_xxx --root=C:\path\to\projects --install
|
|
7
|
+
//
|
|
8
|
+
// Then pick which discovered repos to track from gitdone.eu/github. Add more
|
|
9
|
+
// roots anytime with --root (repeatable). The running agent reads its config
|
|
10
|
+
// from ~/.gitdone-agent/config.json, so the autostart entry needs no args.
|
|
4
11
|
|
|
5
12
|
import { execSync, spawn } from 'node:child_process'
|
|
6
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
existsSync, writeFileSync, readFileSync, unlinkSync,
|
|
15
|
+
mkdirSync, copyFileSync, appendFileSync, readdirSync,
|
|
16
|
+
} from 'node:fs'
|
|
7
17
|
import { resolve, join } from 'node:path'
|
|
8
|
-
import { homedir } from 'node:os'
|
|
18
|
+
import { homedir, hostname } from 'node:os'
|
|
19
|
+
import { randomUUID } from 'node:crypto'
|
|
9
20
|
|
|
10
|
-
// ─── Stable agent dir + logging
|
|
21
|
+
// ─── Stable agent dir, config + logging ────────────────────────────────────────
|
|
11
22
|
// Everything persistent lives here: a stable copy of the agent script (so
|
|
12
|
-
// autostart never points at a purged npx temp dir)
|
|
13
|
-
//
|
|
23
|
+
// autostart never points at a purged npx temp dir), the config file (source of
|
|
24
|
+
// truth for the running agent), and a log file (so failures are visible even
|
|
25
|
+
// when the agent runs in a hidden window).
|
|
14
26
|
|
|
15
27
|
const AGENT_DIR = join(homedir(), '.gitdone-agent')
|
|
28
|
+
const CONFIG_PATH = join(AGENT_DIR, 'config.json')
|
|
29
|
+
const STABLE_AGENT = join(AGENT_DIR, 'agent.mjs')
|
|
30
|
+
const LOG_PATH = join(AGENT_DIR, 'agent.log')
|
|
16
31
|
|
|
17
32
|
function ensureAgentDir() {
|
|
18
33
|
if (!existsSync(AGENT_DIR)) mkdirSync(AGENT_DIR, { recursive: true })
|
|
19
34
|
}
|
|
20
35
|
|
|
21
|
-
let LOG_PATH = null
|
|
22
|
-
|
|
23
36
|
function log(msg) {
|
|
24
37
|
const line = `[${new Date().toISOString()}] ${msg}`
|
|
25
38
|
console.log(line)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
try { ensureAgentDir(); appendFileSync(LOG_PATH, line + '\n') } catch { /* best-effort */ }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readConfig() {
|
|
43
|
+
try { return JSON.parse(readFileSync(CONFIG_PATH, 'utf8')) } catch { return null }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function writeConfig(cfg) {
|
|
47
|
+
ensureAgentDir()
|
|
48
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2), 'utf8')
|
|
29
49
|
}
|
|
30
50
|
|
|
31
51
|
// ─── Arg parsing ─────────────────────────────────────────────────────────────
|
|
32
52
|
|
|
33
53
|
function parseArgs() {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const key = args.key
|
|
44
|
-
const repoPath = args.path ? resolve(args.path) : process.cwd()
|
|
45
|
-
const name = args.name ?? repoPath.split(/[\\/]/).pop() ?? 'repo'
|
|
46
|
-
const projectId = args.project ?? null
|
|
47
|
-
const interval = Number(args.interval ?? 30)
|
|
48
|
-
const url = (args.url ?? 'https://gitdone.eu').replace(/\/$/, '')
|
|
49
|
-
const install = 'install' in args
|
|
50
|
-
const uninstall = 'uninstall' in args
|
|
51
|
-
const doctor = 'doctor' in args
|
|
52
|
-
|
|
53
|
-
if (!key && !uninstall && !doctor) {
|
|
54
|
-
console.error('Error: --key is required (e.g. --key=gdo_xxx)')
|
|
55
|
-
process.exit(1)
|
|
54
|
+
const argv = process.argv.slice(2)
|
|
55
|
+
const flags = {}
|
|
56
|
+
const roots = []
|
|
57
|
+
for (const a of argv) {
|
|
58
|
+
if (!a.startsWith('--')) continue
|
|
59
|
+
const [k, ...rest] = a.slice(2).split('=')
|
|
60
|
+
const v = rest.join('=')
|
|
61
|
+
if (k === 'root') { if (v) roots.push(resolve(v)) }
|
|
62
|
+
else flags[k] = v
|
|
56
63
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
return {
|
|
65
|
+
key: flags.key,
|
|
66
|
+
roots,
|
|
67
|
+
interval: flags.interval ? Number(flags.interval) : undefined,
|
|
68
|
+
url: flags.url ? flags.url.replace(/\/$/, '') : undefined,
|
|
69
|
+
install: 'install' in flags,
|
|
70
|
+
uninstall: 'uninstall' in flags,
|
|
71
|
+
doctor: 'doctor' in flags,
|
|
60
72
|
}
|
|
73
|
+
}
|
|
61
74
|
|
|
62
|
-
|
|
75
|
+
// Merge CLI args into the persisted config, generating a machineId on first use.
|
|
76
|
+
function buildConfig(args) {
|
|
77
|
+
const existing = readConfig() ?? {}
|
|
78
|
+
const mergedRoots = Array.from(new Set([...(existing.roots ?? []), ...args.roots]))
|
|
79
|
+
return {
|
|
80
|
+
key: args.key ?? existing.key,
|
|
81
|
+
url: args.url ?? existing.url ?? 'https://gitdone.eu',
|
|
82
|
+
interval: args.interval ?? existing.interval ?? 30,
|
|
83
|
+
machineId: existing.machineId ?? randomUUID(),
|
|
84
|
+
hostname: existing.hostname ?? hostname(),
|
|
85
|
+
roots: mergedRoots,
|
|
86
|
+
}
|
|
63
87
|
}
|
|
64
88
|
|
|
65
89
|
// ─── Windows auto-start ───────────────────────────────────────────────────────
|
|
@@ -68,77 +92,73 @@ function getStartupDir() {
|
|
|
68
92
|
return join(homedir(), 'AppData', 'Roaming', 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup')
|
|
69
93
|
}
|
|
70
94
|
|
|
95
|
+
function getVbsPath() {
|
|
96
|
+
return join(getStartupDir(), 'gitdone-agent.vbs')
|
|
97
|
+
}
|
|
98
|
+
|
|
71
99
|
function getNodePath() {
|
|
72
100
|
try { return execSync('where node', { encoding: 'utf8' }).trim().split('\n')[0].trim() } catch {}
|
|
73
101
|
try { return execSync('which node', { encoding: 'utf8' }).trim() } catch {}
|
|
74
102
|
return process.execPath
|
|
75
103
|
}
|
|
76
104
|
|
|
77
|
-
function installStartup(
|
|
105
|
+
function installStartup() {
|
|
78
106
|
ensureAgentDir()
|
|
79
107
|
const nodePath = getNodePath()
|
|
80
108
|
|
|
81
109
|
// Copy this script to a stable location. process.argv[1] may point inside an
|
|
82
110
|
// npx cache dir that gets purged later — referencing it from autostart would
|
|
83
111
|
// silently break on the next login. A stable copy survives.
|
|
84
|
-
const stableAgent = join(AGENT_DIR, 'agent.mjs')
|
|
85
112
|
try {
|
|
86
|
-
copyFileSync(resolve(process.argv[1]),
|
|
113
|
+
copyFileSync(resolve(process.argv[1]), STABLE_AGENT)
|
|
87
114
|
} catch (err) {
|
|
88
|
-
console.error(`Warning: could not copy agent to ${
|
|
115
|
+
console.error(`Warning: could not copy agent to ${STABLE_AGENT}: ${err.message}`)
|
|
89
116
|
}
|
|
90
|
-
const agentPath = existsSync(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
`--key=${key}`,
|
|
95
|
-
`--name=${name}`,
|
|
96
|
-
`--path=${repoPath}`,
|
|
97
|
-
`--url=${url}`,
|
|
98
|
-
`--interval=${interval}`,
|
|
99
|
-
...(projectId ? [`--project=${projectId}`] : []),
|
|
100
|
-
].join(' ')
|
|
101
|
-
|
|
102
|
-
// VBScript runs the agent silently — no console window pops up on login
|
|
117
|
+
const agentPath = existsSync(STABLE_AGENT) ? STABLE_AGENT : resolve(process.argv[1])
|
|
118
|
+
|
|
119
|
+
// The running agent reads everything from config.json — no args needed.
|
|
120
|
+
// VBScript runs it silently so no console window pops up on login.
|
|
103
121
|
const vbs = [
|
|
104
122
|
'Set WshShell = CreateObject("WScript.Shell")',
|
|
105
|
-
`WshShell.Run """${nodePath}"" ""${agentPath}""
|
|
123
|
+
`WshShell.Run """${nodePath}"" ""${agentPath}""", 0, False`,
|
|
106
124
|
].join('\r\n')
|
|
107
125
|
|
|
108
|
-
|
|
109
|
-
writeFileSync(vbsPath, vbs, 'utf8')
|
|
110
|
-
|
|
111
|
-
console.log(`✓ Инсталиран! При следващото влизане в Windows агентът ще стартира автоматично.`)
|
|
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`)}`)
|
|
117
|
-
console.log()
|
|
126
|
+
writeFileSync(getVbsPath(), vbs, 'utf8')
|
|
118
127
|
}
|
|
119
128
|
|
|
120
|
-
function uninstallStartup(
|
|
121
|
-
const vbsPath =
|
|
129
|
+
function uninstallStartup() {
|
|
130
|
+
const vbsPath = getVbsPath()
|
|
122
131
|
if (existsSync(vbsPath)) {
|
|
123
132
|
unlinkSync(vbsPath)
|
|
124
|
-
console.log(`✓ Премахнат автостарт за
|
|
133
|
+
console.log(`✓ Премахнат автостарт за gitdone-agent`)
|
|
125
134
|
} else {
|
|
126
|
-
console.log(`Не е намерен автостарт
|
|
135
|
+
console.log(`Не е намерен автостарт (${vbsPath})`)
|
|
127
136
|
}
|
|
128
137
|
}
|
|
129
138
|
|
|
130
|
-
// ─── Doctor
|
|
139
|
+
// ─── Doctor ────────────────────────────────────────────────────────────────────
|
|
131
140
|
|
|
132
|
-
function runDoctor(
|
|
133
|
-
const
|
|
134
|
-
const stableAgent = join(AGENT_DIR, 'agent.mjs')
|
|
135
|
-
const logPath = join(AGENT_DIR, `${name}.log`)
|
|
141
|
+
function runDoctor() {
|
|
142
|
+
const cfg = readConfig()
|
|
136
143
|
console.log('gitdone-agent doctor')
|
|
137
144
|
console.log(` node : ${getNodePath()}`)
|
|
138
145
|
console.log(` agent dir : ${AGENT_DIR} ${existsSync(AGENT_DIR) ? '(ok)' : '(MISSING)'}`)
|
|
139
|
-
console.log(` stable agent : ${
|
|
140
|
-
console.log(` autostart vbs : ${
|
|
141
|
-
console.log(`
|
|
146
|
+
console.log(` stable agent : ${STABLE_AGENT} ${existsSync(STABLE_AGENT) ? '(ok)' : '(not installed)'}`)
|
|
147
|
+
console.log(` autostart vbs : ${getVbsPath()} ${existsSync(getVbsPath()) ? '(ok)' : '(not installed)'}`)
|
|
148
|
+
console.log(` config : ${CONFIG_PATH} ${existsSync(CONFIG_PATH) ? '(ok)' : '(MISSING)'}`)
|
|
149
|
+
console.log(` log file : ${LOG_PATH} ${existsSync(LOG_PATH) ? '(ok)' : '(none yet)'}`)
|
|
150
|
+
if (cfg) {
|
|
151
|
+
console.log(` machineId : ${cfg.machineId}`)
|
|
152
|
+
console.log(` server : ${cfg.url}`)
|
|
153
|
+
console.log(` interval : ${cfg.interval}s`)
|
|
154
|
+
console.log(` roots :`)
|
|
155
|
+
for (const r of cfg.roots ?? []) console.log(` - ${r} ${existsSync(r) ? '' : '(MISSING)'}`)
|
|
156
|
+
if (cfg.roots?.length) {
|
|
157
|
+
const found = scanRepos(cfg.roots)
|
|
158
|
+
console.log(` found repos : ${found.length}`)
|
|
159
|
+
for (const r of found) console.log(` - ${r.name} (${r.path})`)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
142
162
|
}
|
|
143
163
|
|
|
144
164
|
// ─── Git helpers ─────────────────────────────────────────────────────────────
|
|
@@ -151,10 +171,6 @@ function git(cmd, cwd) {
|
|
|
151
171
|
}
|
|
152
172
|
}
|
|
153
173
|
|
|
154
|
-
function isGitRepo(path) {
|
|
155
|
-
return git('git rev-parse --git-dir', path) !== ''
|
|
156
|
-
}
|
|
157
|
-
|
|
158
174
|
function parseDiffByFile(diffOutput) {
|
|
159
175
|
const files = {}
|
|
160
176
|
if (!diffOutput) return files
|
|
@@ -194,124 +210,192 @@ function getSnapshot(repoPath) {
|
|
|
194
210
|
|
|
195
211
|
const diffs = parseDiffByFile(git('git diff HEAD', repoPath))
|
|
196
212
|
|
|
197
|
-
|
|
213
|
+
// Origin remote → lets the server auto-detect the GitHub repo for the History tab.
|
|
214
|
+
const remoteUrl = git('git config --get remote.origin.url', repoPath) || null
|
|
215
|
+
|
|
216
|
+
return { branch, modified, staged, aheadBy, behindBy, lastCommit, statuses, diffs, remoteUrl }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ─── Repo discovery ─────────────────────────────────────────────────────────────
|
|
220
|
+
// Walk each root looking for directories that contain a `.git` entry. Stop
|
|
221
|
+
// descending once a repo is found (don't recurse into submodules/nested repos),
|
|
222
|
+
// skip noisy dirs, and cap depth so a huge tree can't hang a tick.
|
|
223
|
+
|
|
224
|
+
const SKIP_DIRS = new Set(['node_modules', '.next', 'dist', 'build', '.cache', 'vendor', '.venv', '__pycache__'])
|
|
225
|
+
|
|
226
|
+
function scanRepos(roots, maxDepth = 5) {
|
|
227
|
+
const found = []
|
|
228
|
+
const seen = new Set()
|
|
229
|
+
|
|
230
|
+
function walk(dir, depth) {
|
|
231
|
+
if (depth > maxDepth) return
|
|
232
|
+
let entries
|
|
233
|
+
try { entries = readdirSync(dir, { withFileTypes: true }) } catch { return }
|
|
234
|
+
|
|
235
|
+
if (entries.some((e) => e.name === '.git')) {
|
|
236
|
+
const path = resolve(dir)
|
|
237
|
+
if (!seen.has(path)) {
|
|
238
|
+
seen.add(path)
|
|
239
|
+
found.push({ name: path.split(/[\\/]/).pop() ?? path, path })
|
|
240
|
+
}
|
|
241
|
+
return // a repo — don't descend further
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
for (const e of entries) {
|
|
245
|
+
if (!e.isDirectory()) continue
|
|
246
|
+
if (e.name.startsWith('.') || SKIP_DIRS.has(e.name)) continue
|
|
247
|
+
walk(join(dir, e.name), depth + 1)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
for (const root of roots) walk(resolve(root), 0)
|
|
252
|
+
return found
|
|
198
253
|
}
|
|
199
254
|
|
|
200
|
-
// ───
|
|
255
|
+
// ─── Server sync + commands ─────────────────────────────────────────────────────
|
|
201
256
|
|
|
202
|
-
async function
|
|
203
|
-
await fetch(`${url}
|
|
257
|
+
async function api(cfg, path, body) {
|
|
258
|
+
const res = await fetch(`${cfg.url}${path}`, {
|
|
204
259
|
method: 'POST',
|
|
205
|
-
headers: {
|
|
206
|
-
body: JSON.stringify(
|
|
207
|
-
})
|
|
260
|
+
headers: { Authorization: `Bearer ${cfg.key}`, 'Content-Type': 'application/json' },
|
|
261
|
+
body: JSON.stringify(body),
|
|
262
|
+
})
|
|
263
|
+
if (!res.ok) {
|
|
264
|
+
const text = await res.text().catch(() => '')
|
|
265
|
+
throw new Error(`HTTP ${res.status} ${path}: ${text}`)
|
|
266
|
+
}
|
|
267
|
+
return res.json().catch(() => ({}))
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function reportCommandResult(cfg, id, status, result) {
|
|
271
|
+
await api(cfg, '/api/v1/agent/command-result', { id, status, result }).catch(() => {})
|
|
208
272
|
}
|
|
209
273
|
|
|
210
|
-
async function executeCommand(cmd, repoPath
|
|
274
|
+
async function executeCommand(cfg, cmd, repoPath) {
|
|
211
275
|
let result = 'ok'
|
|
212
276
|
let status = 'done'
|
|
213
277
|
try {
|
|
214
278
|
if (cmd.type === 'commit') {
|
|
215
279
|
const msg = (cmd.payload?.message ?? 'commit').replace(/"/g, '\\"')
|
|
216
280
|
git('git add -A', repoPath)
|
|
217
|
-
|
|
218
|
-
result = out || 'committed'
|
|
281
|
+
result = git(`git commit -m "${msg}"`, repoPath) || 'committed'
|
|
219
282
|
} else if (cmd.type === 'push') {
|
|
220
|
-
|
|
221
|
-
result = out || 'pushed'
|
|
283
|
+
result = git('git push', repoPath) || 'pushed'
|
|
222
284
|
} else if (cmd.type === 'pull') {
|
|
223
|
-
|
|
224
|
-
result = out || 'pulled'
|
|
285
|
+
result = git('git pull', repoPath) || 'pulled'
|
|
225
286
|
}
|
|
226
|
-
log(`✓ ${cmd.type}: ${result}`)
|
|
287
|
+
log(`✓ ${cmd.type} @ ${repoPath}: ${result}`)
|
|
227
288
|
} catch (err) {
|
|
228
289
|
status = 'error'
|
|
229
290
|
result = err.message
|
|
230
|
-
log(`✗ ${cmd.type} failed: ${result}`)
|
|
291
|
+
log(`✗ ${cmd.type} failed @ ${repoPath}: ${result}`)
|
|
231
292
|
}
|
|
232
|
-
await reportCommandResult(
|
|
293
|
+
await reportCommandResult(cfg, cmd.id, status, result)
|
|
233
294
|
}
|
|
234
295
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
296
|
+
// Report discovered repos + machine info, get back which repos to track.
|
|
297
|
+
async function sync(cfg, discovered) {
|
|
298
|
+
const data = await api(cfg, '/api/v1/agent/sync', {
|
|
299
|
+
machineId: cfg.machineId,
|
|
300
|
+
hostname: cfg.hostname,
|
|
301
|
+
roots: cfg.roots,
|
|
302
|
+
repos: discovered,
|
|
303
|
+
})
|
|
304
|
+
return data.tracked ?? []
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Push one tracked repo's snapshot and run any pending commands for it.
|
|
308
|
+
async function pushSnapshot(cfg, repo) {
|
|
309
|
+
const snapshot = getSnapshot(repo.path)
|
|
310
|
+
const data = await api(cfg, '/api/v1/agent/snapshot', {
|
|
311
|
+
machineId: cfg.machineId,
|
|
312
|
+
path: repo.path,
|
|
313
|
+
repoName: repo.name,
|
|
314
|
+
...snapshot,
|
|
240
315
|
})
|
|
241
|
-
if (!res.ok) {
|
|
242
|
-
const text = await res.text().catch(() => '')
|
|
243
|
-
throw new Error(`HTTP ${res.status}: ${text}`)
|
|
244
|
-
}
|
|
245
|
-
const data = await res.json().catch(() => ({}))
|
|
246
316
|
for (const cmd of data.commands ?? []) {
|
|
247
|
-
await executeCommand(
|
|
317
|
+
await executeCommand(cfg, cmd, repo.path)
|
|
248
318
|
}
|
|
319
|
+
return snapshot
|
|
249
320
|
}
|
|
250
321
|
|
|
251
322
|
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
252
323
|
|
|
253
|
-
async function
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
}
|
|
324
|
+
async function runLoop(cfg) {
|
|
325
|
+
log(`gitdone-agent started — machine: ${cfg.hostname}, server: ${cfg.url}, interval: ${cfg.interval}s`)
|
|
326
|
+
log(` roots: ${cfg.roots.join(', ') || '(none — add one with --root)'}`)
|
|
268
327
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
328
|
+
async function tick() {
|
|
329
|
+
try {
|
|
330
|
+
const discovered = scanRepos(cfg.roots)
|
|
331
|
+
const tracked = await sync(cfg, discovered)
|
|
332
|
+
let pushed = 0
|
|
333
|
+
for (const repo of tracked) {
|
|
334
|
+
try { await pushSnapshot(cfg, repo); pushed++ }
|
|
335
|
+
catch (err) { log(`✗ snapshot failed @ ${repo.path}: ${err.message}`) }
|
|
336
|
+
}
|
|
337
|
+
log(`✓ tick — discovered: ${discovered.length}, tracked: ${tracked.length}, pushed: ${pushed}`)
|
|
338
|
+
} catch (err) {
|
|
339
|
+
log(`✗ sync error: ${err.message}`)
|
|
340
|
+
}
|
|
272
341
|
}
|
|
273
342
|
|
|
274
|
-
|
|
275
|
-
|
|
343
|
+
await tick()
|
|
344
|
+
setInterval(tick, cfg.interval * 1000)
|
|
345
|
+
}
|
|
276
346
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const child = spawn('wscript.exe', [vbsPath], { detached: true, stdio: 'ignore' })
|
|
280
|
-
child.unref()
|
|
347
|
+
async function main() {
|
|
348
|
+
const args = parseArgs()
|
|
281
349
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
console.log(' При следващо влизане в Windows ще стартира автоматично.')
|
|
350
|
+
if (args.doctor) {
|
|
351
|
+
runDoctor()
|
|
285
352
|
process.exit(0)
|
|
286
353
|
}
|
|
287
354
|
|
|
288
|
-
if (
|
|
289
|
-
|
|
290
|
-
process.exit(
|
|
355
|
+
if (args.uninstall) {
|
|
356
|
+
uninstallStartup()
|
|
357
|
+
process.exit(0)
|
|
291
358
|
}
|
|
292
359
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
log(
|
|
308
|
-
|
|
309
|
-
log(
|
|
360
|
+
// Setup / install path: merge CLI args into config, optionally (re)install.
|
|
361
|
+
if (args.install || args.key || args.roots.length || args.url || args.interval) {
|
|
362
|
+
const cfg = buildConfig(args)
|
|
363
|
+
if (!cfg.key) {
|
|
364
|
+
console.error('Error: --key is required on first setup (e.g. --key=gdo_xxx)')
|
|
365
|
+
process.exit(1)
|
|
366
|
+
}
|
|
367
|
+
writeConfig(cfg)
|
|
368
|
+
|
|
369
|
+
if (args.install) {
|
|
370
|
+
installStartup()
|
|
371
|
+
console.log(`✓ Инсталиран! При следващото влизане в Windows агентът ще стартира автоматично.`)
|
|
372
|
+
console.log(` Агент : ${STABLE_AGENT}`)
|
|
373
|
+
console.log(` Config : ${CONFIG_PATH}`)
|
|
374
|
+
console.log(` Лог : ${LOG_PATH}`)
|
|
375
|
+
console.log(` Roots :`)
|
|
376
|
+
for (const r of cfg.roots) console.log(` - ${r}`)
|
|
377
|
+
console.log()
|
|
378
|
+
|
|
379
|
+
// Launch silently in the background right now.
|
|
380
|
+
const child = spawn('wscript.exe', [getVbsPath()], { detached: true, stdio: 'ignore' })
|
|
381
|
+
child.unref()
|
|
382
|
+
console.log('✓ Агентът е стартиран в заден план — можеш да затвориш прозореца.')
|
|
383
|
+
console.log(' Сега избери кои репота да следи от gitdone.eu/github.')
|
|
384
|
+
process.exit(0)
|
|
310
385
|
}
|
|
386
|
+
|
|
387
|
+
// --key/--root without --install: just run the loop in the foreground.
|
|
388
|
+
await runLoop(cfg)
|
|
389
|
+
return
|
|
311
390
|
}
|
|
312
391
|
|
|
313
|
-
|
|
314
|
-
|
|
392
|
+
// No args → running mode (this is how autostart launches us): read config.
|
|
393
|
+
const cfg = readConfig()
|
|
394
|
+
if (!cfg || !cfg.key) {
|
|
395
|
+
console.error(`Error: no config found at ${CONFIG_PATH}. Run with --key=... --root=... --install first.`)
|
|
396
|
+
process.exit(1)
|
|
397
|
+
}
|
|
398
|
+
await runLoop(cfg)
|
|
315
399
|
}
|
|
316
400
|
|
|
317
401
|
main()
|