gitdone-agent 0.8.7 → 0.8.8
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 +127 -3
- package/package.json +5 -2
package/index.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Setup (once):
|
|
6
6
|
// npx gitdone-agent --key=gdo_xxx --root=C:\path\to\projects --install
|
|
7
|
+
// Optional WoW progress sync:
|
|
8
|
+
// npx gitdone-agent --wow="C:\Program Files (x86)\World of Warcraft\_retail_" --install
|
|
7
9
|
//
|
|
8
10
|
// Then pick which discovered repos to track from gitdone.eu/github. Add more
|
|
9
11
|
// roots anytime with --root (repeatable). The running agent reads its config
|
|
@@ -29,7 +31,7 @@ import { randomUUID, createHash } from 'node:crypto'
|
|
|
29
31
|
// Reported to the server on every sync so the web UI can flag outdated agents.
|
|
30
32
|
// Keep in lockstep with packages/agent/package.json. The server's offline
|
|
31
33
|
// fallback is bumped only after this release has actually reached npm.
|
|
32
|
-
const AGENT_VERSION = '0.8.
|
|
34
|
+
const AGENT_VERSION = '0.8.8'
|
|
33
35
|
|
|
34
36
|
const AGENT_DIR = join(homedir(), '.gitdone-agent')
|
|
35
37
|
const CONFIG_PATH = join(AGENT_DIR, 'config.json')
|
|
@@ -182,21 +184,25 @@ function parseArgs() {
|
|
|
182
184
|
const argv = process.argv.slice(2)
|
|
183
185
|
const flags = {}
|
|
184
186
|
const roots = []
|
|
187
|
+
const wowRoots = []
|
|
185
188
|
for (const a of argv) {
|
|
186
189
|
if (!a.startsWith('--')) continue
|
|
187
190
|
const [k, ...rest] = a.slice(2).split('=')
|
|
188
191
|
const v = rest.join('=')
|
|
189
192
|
if (k === 'root') { if (v) roots.push(resolve(v)) }
|
|
193
|
+
else if (k === 'wow') { if (v) wowRoots.push(resolve(v)) }
|
|
190
194
|
else flags[k] = v
|
|
191
195
|
}
|
|
192
196
|
return {
|
|
193
197
|
key: flags.key,
|
|
194
198
|
roots,
|
|
199
|
+
wowRoots,
|
|
195
200
|
interval: flags.interval ? Number(flags.interval) : undefined,
|
|
196
201
|
url: flags.url ? flags.url.replace(/\/$/, '') : undefined,
|
|
197
202
|
install: 'install' in flags,
|
|
198
203
|
uninstall: 'uninstall' in flags,
|
|
199
204
|
doctor: 'doctor' in flags,
|
|
205
|
+
testWow: flags['test-wow'] ? resolve(flags['test-wow']) : undefined,
|
|
200
206
|
}
|
|
201
207
|
}
|
|
202
208
|
|
|
@@ -204,6 +210,7 @@ function parseArgs() {
|
|
|
204
210
|
function buildConfig(args) {
|
|
205
211
|
const existing = readConfig() ?? {}
|
|
206
212
|
const mergedRoots = Array.from(new Set([...(existing.roots ?? []), ...args.roots]))
|
|
213
|
+
const mergedWowRoots = Array.from(new Set([...(existing.wowRoots ?? []), ...args.wowRoots]))
|
|
207
214
|
return {
|
|
208
215
|
key: args.key ?? existing.key,
|
|
209
216
|
url: args.url ?? existing.url ?? 'https://gitdone.eu',
|
|
@@ -211,6 +218,8 @@ function buildConfig(args) {
|
|
|
211
218
|
machineId: existing.machineId ?? randomUUID(),
|
|
212
219
|
hostname: existing.hostname ?? hostname(),
|
|
213
220
|
roots: mergedRoots,
|
|
221
|
+
wowRoots: mergedWowRoots,
|
|
222
|
+
...(existing.auth ? { auth: existing.auth } : {}),
|
|
214
223
|
}
|
|
215
224
|
}
|
|
216
225
|
|
|
@@ -657,6 +666,20 @@ function runDoctor() {
|
|
|
657
666
|
console.log(` found repos : ${found.length}`)
|
|
658
667
|
for (const r of found) console.log(` - ${r.name} (${r.path})`)
|
|
659
668
|
}
|
|
669
|
+
console.log(` WoW roots : ${cfg.wowRoots?.length ? '' : '(not configured)'}`)
|
|
670
|
+
for (const root of cfg.wowRoots ?? []) console.log(` - ${root} ${existsSync(root) ? '' : '(MISSING)'}`)
|
|
671
|
+
if (cfg.wowRoots?.length) {
|
|
672
|
+
const files = findWowSavedVariableFiles(cfg.wowRoots)
|
|
673
|
+
console.log(` WoW snapshots : ${files.length}`)
|
|
674
|
+
for (const file of files) {
|
|
675
|
+
try {
|
|
676
|
+
const { snapshot } = decodeWowSavedVariables(file)
|
|
677
|
+
console.log(` - ${snapshot.character.name}-${snapshot.character.realm} (${file})`)
|
|
678
|
+
} catch (err) {
|
|
679
|
+
console.log(` - INVALID (${file}): ${err.message}`)
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
660
683
|
}
|
|
661
684
|
}
|
|
662
685
|
|
|
@@ -1068,6 +1091,94 @@ function scanRepos(roots, maxDepth = 5) {
|
|
|
1068
1091
|
return found
|
|
1069
1092
|
}
|
|
1070
1093
|
|
|
1094
|
+
// ─── World of Warcraft progress bridge ──────────────────────────────────────
|
|
1095
|
+
// WoW addons cannot open network connections. GitDoneProgress therefore writes
|
|
1096
|
+
// a Base64URL-encoded JSON snapshot to its normal SavedVariables file; this
|
|
1097
|
+
// opt-in companion path reads only that explicit file and uploads it with the
|
|
1098
|
+
// same API credential the agent already uses. WoW flushes SavedVariables on
|
|
1099
|
+
// /reload or logout, so there is no process-memory inspection involved.
|
|
1100
|
+
|
|
1101
|
+
function findWowSavedVariableFiles(wowRoots = []) {
|
|
1102
|
+
const files = new Set()
|
|
1103
|
+
|
|
1104
|
+
for (const configuredRoot of wowRoots) {
|
|
1105
|
+
const root = resolve(configuredRoot)
|
|
1106
|
+
if (/GitDoneProgress\.lua$/i.test(root) && existsSync(root)) {
|
|
1107
|
+
files.add(root)
|
|
1108
|
+
continue
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const normalized = root.replace(/[\\/]+$/, '')
|
|
1112
|
+
const accountRoots = new Set([
|
|
1113
|
+
join(normalized, 'WTF', 'Account'),
|
|
1114
|
+
join(normalized, '_retail_', 'WTF', 'Account'),
|
|
1115
|
+
])
|
|
1116
|
+
if (/[\\/]WTF$/i.test(normalized)) accountRoots.add(join(normalized, 'Account'))
|
|
1117
|
+
if (/[\\/]Account$/i.test(normalized)) accountRoots.add(normalized)
|
|
1118
|
+
|
|
1119
|
+
for (const accountRoot of accountRoots) {
|
|
1120
|
+
if (!existsSync(accountRoot)) continue
|
|
1121
|
+
let accounts = []
|
|
1122
|
+
try { accounts = readdirSync(accountRoot, { withFileTypes: true }) }
|
|
1123
|
+
catch { continue }
|
|
1124
|
+
for (const account of accounts) {
|
|
1125
|
+
if (!account.isDirectory()) continue
|
|
1126
|
+
const candidate = join(accountRoot, account.name, 'SavedVariables', 'GitDoneProgress.lua')
|
|
1127
|
+
if (existsSync(candidate)) files.add(candidate)
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
return [...files].sort()
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function decodeWowSavedVariables(path) {
|
|
1136
|
+
const source = readFileSync(path, 'utf8')
|
|
1137
|
+
const match = source.match(/(?:\["payload"\]|payload)\s*=\s*"([A-Za-z0-9_-]+)"/)
|
|
1138
|
+
if (!match) throw new Error('GitDoneProgressDB.payload is missing')
|
|
1139
|
+
|
|
1140
|
+
const json = Buffer.from(match[1], 'base64url').toString('utf8')
|
|
1141
|
+
const snapshot = JSON.parse(json)
|
|
1142
|
+
if (snapshot?.schemaVersion !== 1) {
|
|
1143
|
+
throw new Error(`unsupported snapshot schema ${snapshot?.schemaVersion ?? '?'}`)
|
|
1144
|
+
}
|
|
1145
|
+
if (!snapshot?.character?.guid || !snapshot?.character?.name || !snapshot?.character?.realm) {
|
|
1146
|
+
throw new Error('snapshot character identity is incomplete')
|
|
1147
|
+
}
|
|
1148
|
+
return { snapshot, signature: createHash('sha256').update(match[1]).digest('hex') }
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
const wowSnapshotSigCache = new Map() // SavedVariables path -> last uploaded payload hash
|
|
1152
|
+
|
|
1153
|
+
async function pushWowProgress(cfg) {
|
|
1154
|
+
const files = findWowSavedVariableFiles(cfg.wowRoots)
|
|
1155
|
+
let sent = 0
|
|
1156
|
+
let unchanged = 0
|
|
1157
|
+
let failed = 0
|
|
1158
|
+
|
|
1159
|
+
for (const path of files) {
|
|
1160
|
+
try {
|
|
1161
|
+
const { snapshot, signature } = decodeWowSavedVariables(path)
|
|
1162
|
+
if (wowSnapshotSigCache.get(path) === signature) {
|
|
1163
|
+
unchanged++
|
|
1164
|
+
continue
|
|
1165
|
+
}
|
|
1166
|
+
const result = await api(cfg, '/api/v1/wow/progress', {
|
|
1167
|
+
machineId: cfg.machineId,
|
|
1168
|
+
snapshot,
|
|
1169
|
+
})
|
|
1170
|
+
wowSnapshotSigCache.set(path, signature)
|
|
1171
|
+
sent++
|
|
1172
|
+
log(`✓ WoW progress synced — ${snapshot.character.name}-${snapshot.character.realm}${result.unchanged ? ' (server already current)' : ''}`)
|
|
1173
|
+
} catch (err) {
|
|
1174
|
+
failed++
|
|
1175
|
+
log(`✗ WoW progress sync failed @ ${path}: ${err.message}`)
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
return { found: files.length, sent, unchanged, failed }
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1071
1182
|
// ─── Server sync + commands ─────────────────────────────────────────────────────
|
|
1072
1183
|
|
|
1073
1184
|
// Keep the deadline below the server-side session stall window. Without a
|
|
@@ -3041,6 +3152,7 @@ async function runLoop(cfg) {
|
|
|
3041
3152
|
backupConfig(cfg)
|
|
3042
3153
|
log(`gitdone-agent started — machine: ${cfg.hostname}, server: ${cfg.url}, interval: ${cfg.interval}s`)
|
|
3043
3154
|
log(` roots: ${cfg.roots.join(', ') || '(none — add one with --root)'}`)
|
|
3155
|
+
log(` WoW: ${cfg.wowRoots?.join(', ') || '(disabled — add retail path with --wow)'}`)
|
|
3044
3156
|
|
|
3045
3157
|
async function tick() {
|
|
3046
3158
|
try {
|
|
@@ -3049,6 +3161,7 @@ async function runLoop(cfg) {
|
|
|
3049
3161
|
// A successful update exits here; the supervisor starts the new stable
|
|
3050
3162
|
// script before any more repo work is performed.
|
|
3051
3163
|
await selfUpdate(cfg, state.latestVersion, state.downloadUrl, state.downloadSha256)
|
|
3164
|
+
const wow = await pushWowProgress(cfg)
|
|
3052
3165
|
const tracked = state.tracked
|
|
3053
3166
|
let pushed = 0
|
|
3054
3167
|
let skipped = 0
|
|
@@ -3056,7 +3169,10 @@ async function runLoop(cfg) {
|
|
|
3056
3169
|
try { (await pushSnapshot(cfg, repo)) ? pushed++ : skipped++ }
|
|
3057
3170
|
catch (err) { log(`✗ snapshot failed @ ${repo.path}: ${err.message}`) }
|
|
3058
3171
|
}
|
|
3059
|
-
|
|
3172
|
+
const wowStatus = cfg.wowRoots?.length
|
|
3173
|
+
? `, WoW: ${wow.sent} sent/${wow.unchanged} unchanged/${wow.failed} failed`
|
|
3174
|
+
: ''
|
|
3175
|
+
log(`✓ tick — discovered: ${discovered.length}, tracked: ${tracked.length}, pushed: ${pushed}, unchanged: ${skipped}${wowStatus}`)
|
|
3060
3176
|
} catch (err) {
|
|
3061
3177
|
log(`✗ sync error: ${err.message}`)
|
|
3062
3178
|
}
|
|
@@ -3073,6 +3189,12 @@ async function runLoop(cfg) {
|
|
|
3073
3189
|
async function main() {
|
|
3074
3190
|
const args = parseArgs()
|
|
3075
3191
|
|
|
3192
|
+
if (args.testWow) {
|
|
3193
|
+
const { snapshot, signature } = decodeWowSavedVariables(args.testWow)
|
|
3194
|
+
console.log(JSON.stringify({ signature, snapshot }, null, 2))
|
|
3195
|
+
process.exit(0)
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3076
3198
|
if (args.doctor) {
|
|
3077
3199
|
runDoctor()
|
|
3078
3200
|
process.exit(0)
|
|
@@ -3084,7 +3206,7 @@ async function main() {
|
|
|
3084
3206
|
}
|
|
3085
3207
|
|
|
3086
3208
|
// Setup / install path: merge CLI args into config, optionally (re)install.
|
|
3087
|
-
if (args.install || args.key || args.roots.length || args.url || args.interval) {
|
|
3209
|
+
if (args.install || args.key || args.roots.length || args.wowRoots.length || args.url || args.interval) {
|
|
3088
3210
|
const cfg = buildConfig(args)
|
|
3089
3211
|
if (!cfg.key) {
|
|
3090
3212
|
console.error('Error: --key is required on first setup (e.g. --key=gdo_xxx)')
|
|
@@ -3100,6 +3222,8 @@ async function main() {
|
|
|
3100
3222
|
console.log(` Лог : ${LOG_PATH}`)
|
|
3101
3223
|
console.log(` Roots :`)
|
|
3102
3224
|
for (const r of cfg.roots) console.log(` - ${r}`)
|
|
3225
|
+
console.log(` WoW :`)
|
|
3226
|
+
for (const r of cfg.wowRoots ?? []) console.log(` - ${r}`)
|
|
3103
3227
|
console.log()
|
|
3104
3228
|
|
|
3105
3229
|
// Launch silently in the background right now.
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitdone-agent",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "Local
|
|
3
|
+
"version": "0.8.8",
|
|
4
|
+
"description": "Local gitDone companion for repository and optional World of Warcraft progress sync",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"index.js"
|
|
8
|
+
],
|
|
6
9
|
"bin": {
|
|
7
10
|
"gitdone-agent": "index.js"
|
|
8
11
|
},
|