gitdone-agent 0.1.3 → 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.
Files changed (2) hide show
  1. package/index.js +239 -158
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1,65 +1,89 @@
1
1
  #!/usr/bin/env node
2
- // gitdone-agent — watches a local git repo and pushes snapshots to gitdone.eu
3
- // Usage: bun index.js --key=gdo_xxx --name=my-repo --path=/path/to/repo [--install]
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 { existsSync, writeFileSync, unlinkSync, mkdirSync, copyFileSync, appendFileSync } from 'node:fs'
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) and a per-repo log file
13
- // (so failures are visible even when the agent runs in a hidden window).
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
- if (LOG_PATH) {
27
- try { appendFileSync(LOG_PATH, line + '\n') } catch { /* best-effort */ }
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 args = Object.fromEntries(
35
- process.argv.slice(2)
36
- .filter((a) => a.startsWith('--'))
37
- .map((a) => {
38
- const [k, ...rest] = a.slice(2).split('=')
39
- return [k, rest.join('=')]
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
- if (!existsSync(repoPath) && !uninstall && !doctor) {
58
- console.error(`Error: path does not exist: ${repoPath}`)
59
- process.exit(1)
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
- return { key, path: repoPath, name, projectId, interval, url, install, uninstall, doctor }
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({ name, key, path: repoPath, url, interval, projectId }) {
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]), stableAgent)
113
+ copyFileSync(resolve(process.argv[1]), STABLE_AGENT)
87
114
  } catch (err) {
88
- console.error(`Warning: could not copy agent to ${stableAgent}: ${err.message}`)
115
+ console.error(`Warning: could not copy agent to ${STABLE_AGENT}: ${err.message}`)
89
116
  }
90
- const agentPath = existsSync(stableAgent) ? stableAgent : resolve(process.argv[1])
91
- const startupDir = getStartupDir()
92
-
93
- const argLine = [
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}"" ${argLine}", 0, False`,
123
+ `WshShell.Run """${nodePath}"" ""${agentPath}""", 0, False`,
106
124
  ].join('\r\n')
107
125
 
108
- const vbsPath = join(startupDir, `gitdone-${name}.vbs`)
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(name) {
121
- const vbsPath = join(getStartupDir(), `gitdone-${name}.vbs`)
129
+ function uninstallStartup() {
130
+ const vbsPath = getVbsPath()
122
131
  if (existsSync(vbsPath)) {
123
132
  unlinkSync(vbsPath)
124
- console.log(`✓ Премахнат автостарт за "${name}"`)
133
+ console.log(`✓ Премахнат автостарт за gitdone-agent`)
125
134
  } else {
126
- console.log(`Не е намерен автостарт за "${name}" (${vbsPath})`)
135
+ console.log(`Не е намерен автостарт (${vbsPath})`)
127
136
  }
128
137
  }
129
138
 
130
- // ─── Doctor: print where things are so "offline" is debuggable ─────────────────
139
+ // ─── Doctor ────────────────────────────────────────────────────────────────────
131
140
 
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`)
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 : ${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)'}`)
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
@@ -197,121 +213,186 @@ function getSnapshot(repoPath) {
197
213
  return { branch, modified, staged, aheadBy, behindBy, lastCommit, statuses, diffs }
198
214
  }
199
215
 
200
- // ─── Push to API & execute commands ─────────────────────────────────────────
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
+ }
247
+
248
+ for (const root of roots) walk(resolve(root), 0)
249
+ return found
250
+ }
251
+
252
+ // ─── Server sync + commands ─────────────────────────────────────────────────────
201
253
 
202
- async function reportCommandResult(url, key, id, status, result) {
203
- await fetch(`${url}/api/v1/agent/command-result`, {
254
+ async function api(cfg, path, body) {
255
+ const res = await fetch(`${cfg.url}${path}`, {
204
256
  method: 'POST',
205
- headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' },
206
- body: JSON.stringify({ id, status, result }),
207
- }).catch(() => {})
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(() => {})
208
269
  }
209
270
 
210
- async function executeCommand(cmd, repoPath, url, key) {
271
+ async function executeCommand(cfg, cmd, repoPath) {
211
272
  let result = 'ok'
212
273
  let status = 'done'
213
274
  try {
214
275
  if (cmd.type === 'commit') {
215
276
  const msg = (cmd.payload?.message ?? 'commit').replace(/"/g, '\\"')
216
277
  git('git add -A', repoPath)
217
- const out = git(`git commit -m "${msg}"`, repoPath)
218
- result = out || 'committed'
278
+ result = git(`git commit -m "${msg}"`, repoPath) || 'committed'
219
279
  } else if (cmd.type === 'push') {
220
- const out = git('git push', repoPath)
221
- result = out || 'pushed'
280
+ result = git('git push', repoPath) || 'pushed'
222
281
  } else if (cmd.type === 'pull') {
223
- const out = git('git pull', repoPath)
224
- result = out || 'pulled'
282
+ result = git('git pull', repoPath) || 'pulled'
225
283
  }
226
- log(`✓ ${cmd.type}: ${result}`)
284
+ log(`✓ ${cmd.type} @ ${repoPath}: ${result}`)
227
285
  } catch (err) {
228
286
  status = 'error'
229
287
  result = err.message
230
- log(`✗ ${cmd.type} failed: ${result}`)
288
+ log(`✗ ${cmd.type} failed @ ${repoPath}: ${result}`)
231
289
  }
232
- await reportCommandResult(url, key, cmd.id, status, result)
290
+ await reportCommandResult(cfg, cmd.id, status, result)
233
291
  }
234
292
 
235
- async function push({ url, key, name, projectId, repoPath, snapshot }) {
236
- const res = await fetch(`${url}/api/v1/agent/snapshot`, {
237
- method: 'POST',
238
- headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' },
239
- body: JSON.stringify({ repoName: name, projectId, ...snapshot }),
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,
240
312
  })
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
313
  for (const cmd of data.commands ?? []) {
247
- await executeCommand(cmd, repoPath, url, key)
314
+ await executeCommand(cfg, cmd, repo.path)
248
315
  }
316
+ return snapshot
249
317
  }
250
318
 
251
319
  // ─── Main ────────────────────────────────────────────────────────────────────
252
320
 
253
- async function main() {
254
- const config = parseArgs()
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
- }
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)'}`)
268
324
 
269
- if (uninstall) {
270
- uninstallStartup(name)
271
- process.exit(0)
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
+ }
272
338
  }
273
339
 
274
- if (install) {
275
- installStartup(config)
340
+ await tick()
341
+ setInterval(tick, cfg.interval * 1000)
342
+ }
276
343
 
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()
344
+ async function main() {
345
+ const args = parseArgs()
281
346
 
282
- console.log()
283
- console.log('✓ Агентът е стартиран в заден план — можеш да затвориш прозореца.')
284
- console.log(' При следващо влизане в Windows ще стартира автоматично.')
347
+ if (args.doctor) {
348
+ runDoctor()
285
349
  process.exit(0)
286
350
  }
287
351
 
288
- if (!isGitRepo(repoPath)) {
289
- console.error(`Error: ${repoPath} is not a git repository`)
290
- process.exit(1)
352
+ if (args.uninstall) {
353
+ uninstallStartup()
354
+ process.exit(0)
291
355
  }
292
356
 
293
- console.log(`gitdone-agent started`)
294
- console.log(` repo : ${repoPath}`)
295
- console.log(` name : ${name}`)
296
- if (projectId) console.log(` project : ${projectId}`)
297
- console.log(` server : ${url}`)
298
- console.log(` interval: ${interval}s`)
299
- if (install) console.log(` startup : registered`)
300
- console.log()
301
-
302
- async function tick() {
303
- try {
304
- const snapshot = getSnapshot(repoPath)
305
- await push({ url, key, name, projectId, repoPath, snapshot })
306
- const changed = snapshot.modified.length + snapshot.staged.length
307
- log(`✓ pushed — branch: ${snapshot.branch}, changes: ${changed}`)
308
- } catch (err) {
309
- log(`✗ error: ${err.message}`)
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)
310
382
  }
383
+
384
+ // --key/--root without --install: just run the loop in the foreground.
385
+ await runLoop(cfg)
386
+ return
311
387
  }
312
388
 
313
- await tick()
314
- setInterval(tick, interval * 1000)
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)
315
396
  }
316
397
 
317
398
  main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitdone-agent",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
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": {