gitdone-agent 0.6.9 → 0.6.11

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 +60 -4
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -27,7 +27,7 @@ import { randomUUID } from 'node:crypto'
27
27
  // Reported to the server on every sync so the web UI can flag outdated agents.
28
28
  // Keep in lockstep with packages/agent/package.json "version" AND
29
29
  // src/lib/agentVersion.ts LATEST_AGENT_VERSION.
30
- const AGENT_VERSION = '0.6.9'
30
+ const AGENT_VERSION = '0.6.11'
31
31
 
32
32
  const AGENT_DIR = join(homedir(), '.gitdone-agent')
33
33
  const CONFIG_PATH = join(AGENT_DIR, 'config.json')
@@ -231,9 +231,15 @@ function runDoctor() {
231
231
 
232
232
  // ─── Git helpers ─────────────────────────────────────────────────────────────
233
233
 
234
+ // Room for large command output. `git status --porcelain -uall` (gd-369) lists
235
+ // every untracked file individually, so a repo with a big new/untracked tree can
236
+ // blow past execSync's default 1 MB buffer — which would throw and silently
237
+ // return '' (an EMPTY snapshot, worse than the collapsed view). 64 MB is plenty.
238
+ const GIT_MAX_BUFFER = 64 * 1024 * 1024
239
+
234
240
  function git(cmd, cwd) {
235
241
  try {
236
- return execSync(cmd, { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim()
242
+ return execSync(cmd, { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], maxBuffer: GIT_MAX_BUFFER }).trim()
237
243
  } catch {
238
244
  return ''
239
245
  }
@@ -245,7 +251,7 @@ function git(cmd, cwd) {
245
251
  // (gd-276). Callers decode per-file via decodeDiffText().
246
252
  function gitRaw(cmd, cwd) {
247
253
  try {
248
- return execSync(cmd, { cwd, encoding: 'buffer', stdio: ['pipe', 'pipe', 'pipe'] })
254
+ return execSync(cmd, { cwd, encoding: 'buffer', stdio: ['pipe', 'pipe', 'pipe'], maxBuffer: GIT_MAX_BUFFER })
249
255
  } catch {
250
256
  return Buffer.alloc(0)
251
257
  }
@@ -461,7 +467,10 @@ function parseDiffByFile(diffBuf) {
461
467
  function getSnapshot(repoPath) {
462
468
  const branch = git('git branch --show-current', repoPath) || 'HEAD'
463
469
 
464
- const statusLines = git('git status --porcelain', repoPath).split('\n').filter(Boolean)
470
+ // -uall lists every untracked file individually instead of collapsing a new
471
+ // directory into a single `dir/` entry — so the change list matches what
472
+ // GitHub Desktop shows, file-for-file (gd-369).
473
+ const statusLines = git('git status --porcelain -uall', repoPath).split('\n').filter(Boolean)
465
474
  const modified = []
466
475
  const staged = []
467
476
  const statuses = {}
@@ -1260,13 +1269,60 @@ async function executeCommand(cfg, cmd, repoPath) {
1260
1269
  }
1261
1270
 
1262
1271
  // Report discovered repos + machine info, get back which repos to track.
1272
+ // ─── Claude plan usage (gd-355) ────────────────────────────────────────────
1273
+ // Claude Code persists an OAuth token at ~/.claude/.credentials.json and keeps
1274
+ // it fresh. We reuse it to ask Anthropic for the SAME plan-usage numbers the
1275
+ // /usage command shows (account-level: a 5-hour "session" window + a weekly
1276
+ // limit). The АИ Конзола then renders these live. Best-effort: on a missing or
1277
+ // expired token, being offline, or any error we return null and the console
1278
+ // simply keeps the last snapshot. Cached ~60s so the 30s sync loop doesn't hit
1279
+ // the endpoint twice as often as it changes.
1280
+ let usageCache = { at: 0, data: null }
1281
+ async function readClaudeUsage() {
1282
+ const now = Date.now()
1283
+ if (usageCache.data && now - usageCache.at < 60_000) return usageCache.data
1284
+ try {
1285
+ const creds = JSON.parse(readFileSync(join(homedir(), '.claude', '.credentials.json'), 'utf8'))
1286
+ const token = creds?.claudeAiOauth?.accessToken
1287
+ if (!token) return null
1288
+ const ctrl = new AbortController()
1289
+ const to = setTimeout(() => ctrl.abort(), 8000)
1290
+ let res
1291
+ try {
1292
+ res = await fetch('https://api.anthropic.com/api/oauth/usage', {
1293
+ headers: {
1294
+ Authorization: `Bearer ${token}`,
1295
+ 'Content-Type': 'application/json',
1296
+ 'anthropic-beta': 'oauth-2025-04-20',
1297
+ 'User-Agent': 'claude-cli',
1298
+ },
1299
+ signal: ctrl.signal,
1300
+ })
1301
+ } finally { clearTimeout(to) }
1302
+ if (!res.ok) return null // 401 = token expired; Claude Code refreshes it — skip this tick
1303
+ const j = await res.json()
1304
+ const data = {
1305
+ sessionPct: Math.round(j?.five_hour?.utilization ?? 0),
1306
+ sessionResetsAt: j?.five_hour?.resets_at ?? null,
1307
+ weeklyPct: Math.round(j?.seven_day?.utilization ?? 0),
1308
+ weeklyResetsAt: j?.seven_day?.resets_at ?? null,
1309
+ }
1310
+ usageCache = { at: now, data }
1311
+ return data
1312
+ } catch {
1313
+ return null // no creds file / malformed / offline — stay quiet
1314
+ }
1315
+ }
1316
+
1263
1317
  async function sync(cfg, discovered) {
1318
+ const usage = await readClaudeUsage()
1264
1319
  const data = await api(cfg, '/api/v1/agent/sync', {
1265
1320
  machineId: cfg.machineId,
1266
1321
  hostname: cfg.hostname,
1267
1322
  agentVersion: AGENT_VERSION,
1268
1323
  roots: cfg.roots,
1269
1324
  repos: discovered,
1325
+ ...(usage ? { usage } : {}),
1270
1326
  })
1271
1327
  return data.tracked ?? []
1272
1328
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitdone-agent",
3
- "version": "0.6.9",
3
+ "version": "0.6.11",
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": {