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