@misterhuydo/sentinel 1.0.44 → 1.0.45

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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "message": "Auto-checkpoint at 2026-03-21T23:30:09.918Z",
3
- "checkpoint_at": "2026-03-21T23:30:09.918Z",
2
+ "message": "Auto-checkpoint at 2026-03-22T05:36:04.794Z",
3
+ "checkpoint_at": "2026-03-22T05:36:04.795Z",
4
4
  "active_files": [],
5
5
  "notes": [],
6
6
  "mtime_snapshot": {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misterhuydo/sentinel",
3
- "version": "1.0.44",
3
+ "version": "1.0.45",
4
4
  "description": "Sentinel — Autonomous DevOps Agent installer and manager",
5
5
  "bin": {
6
6
  "sentinel": "./bin/sentinel.js"
@@ -248,9 +248,68 @@ _TOOLS = [
248
248
  },
249
249
  },
250
250
  },
251
+ {
252
+ "name": "pull_config",
253
+ "description": (
254
+ "Run git pull on one or all Sentinel project config directories. "
255
+ "Projects are matched by short name ('1881', 'elprint') or full dir name ('sentinel-1881'). "
256
+ "Use for: 'pull config for 1881', 'update sentinel config', 'pull all configs'."
257
+ ),
258
+ "input_schema": {
259
+ "type": "object",
260
+ "properties": {
261
+ "project": {
262
+ "type": "string",
263
+ "description": "Project short name or dir name to pull (omit for all projects)",
264
+ },
265
+ },
266
+ },
267
+ },
251
268
  ]
252
269
 
253
270
 
271
+ # ── Workspace helpers ─────────────────────────────────────────────────────────
272
+
273
+ def _workspace_dir() -> Path:
274
+ return Path(".").resolve().parent
275
+
276
+ def _short_name(dir_name: str) -> str:
277
+ """'sentinel-1881' → '1881', 'sentinel-elprint' → 'elprint', others unchanged."""
278
+ if dir_name.startswith("sentinel-"):
279
+ return dir_name[len("sentinel-"):]
280
+ return dir_name
281
+
282
+ def _find_project_dirs(target: str = "") -> list[Path]:
283
+ """Return project dirs matching target (short or full name), or all if target empty."""
284
+ workspace = _workspace_dir()
285
+ results = []
286
+ try:
287
+ for d in sorted(workspace.iterdir()):
288
+ if not d.is_dir() or d.name in ("code", ".git"):
289
+ continue
290
+ if not (d / "config").exists():
291
+ continue
292
+ if target:
293
+ if target.lower() not in d.name.lower() and target.lower() not in _short_name(d.name).lower():
294
+ continue
295
+ results.append(d)
296
+ except Exception:
297
+ pass
298
+ return results
299
+
300
+ def _git_pull(path: Path) -> dict:
301
+ try:
302
+ r = subprocess.run(
303
+ ["git", "pull", "--rebase", "origin"],
304
+ cwd=str(path), capture_output=True, text=True, timeout=60,
305
+ )
306
+ last = r.stdout.strip().splitlines()[-1] if r.stdout.strip() else "already up to date"
307
+ return {"status": "ok" if r.returncode == 0 else "error",
308
+ "detail": last if r.returncode == 0 else r.stderr.strip()}
309
+ except Exception as e:
310
+ return {"status": "error", "detail": str(e)}
311
+
312
+
254
313
  # ── Tool execution ────────────────────────────────────────────────────────────
255
314
 
256
315
  def _run_tool(name: str, inputs: dict, cfg_loader, store) -> str:
@@ -339,27 +398,11 @@ def _run_tool(name: str, inputs: dict, cfg_loader, store) -> str:
339
398
  return json.dumps({"status": "resumed"})
340
399
 
341
400
  if name == "list_projects":
342
- # Repos this instance manages
343
- my_repos = [
344
- {
345
- "repo": r.repo_name,
346
- "url": r.repo_url,
347
- "branch": r.branch,
348
- "auto_publish": r.auto_publish,
349
- }
350
- for r in cfg_loader.repos.values()
351
- ]
352
- # Scan workspace for sibling project instances
353
- workspace = Path(".").resolve().parent
354
- other_projects = []
355
- try:
356
- for d in sorted(workspace.iterdir()):
357
- if not d.is_dir() or d.name in ("code", ".git"):
358
- continue
359
- repo_cfg_dir = d / "config" / "repo-configs"
360
- if not repo_cfg_dir.exists():
361
- continue
362
- repos_in_project = []
401
+ projects = []
402
+ for d in _find_project_dirs():
403
+ repo_cfg_dir = d / "config" / "repo-configs"
404
+ repos_in_project = []
405
+ if repo_cfg_dir.exists():
363
406
  for p in sorted(repo_cfg_dir.glob("*.properties")):
364
407
  if p.name.startswith("_"):
365
408
  continue
@@ -368,25 +411,15 @@ def _run_tool(name: str, inputs: dict, cfg_loader, store) -> str:
368
411
  if line.startswith("REPO_URL"):
369
412
  repo_url = line.split("=", 1)[-1].strip()
370
413
  break
371
- if repo_url:
372
- repos_in_project.append({"repo": p.stem, "url": repo_url})
373
- if repos_in_project:
374
- pid_file = d / "sentinel.pid"
375
- running = pid_file.exists()
376
- other_projects.append({
377
- "project": d.name,
378
- "running": running,
379
- "repos": repos_in_project,
380
- })
381
- except Exception as e:
382
- logger.warning("list_projects workspace scan failed: %s", e)
383
- return json.dumps({
384
- "this_instance": {
385
- "project": Path(".").resolve().name,
386
- "repos": my_repos,
387
- },
388
- "workspace_projects": other_projects,
389
- })
414
+ repos_in_project.append({"repo": p.stem, "url": repo_url})
415
+ projects.append({
416
+ "project": _short_name(d.name),
417
+ "dir": d.name,
418
+ "running": (d / "sentinel.pid").exists(),
419
+ "this": d.resolve() == Path(".").resolve(),
420
+ "repos": repos_in_project,
421
+ })
422
+ return json.dumps({"projects": projects})
390
423
 
391
424
  if name == "search_logs":
392
425
  query = inputs.get("query", "")
@@ -482,6 +515,18 @@ def _run_tool(name: str, inputs: dict, cfg_loader, store) -> str:
482
515
  results.append({"repo": repo_name, "status": "error", "detail": str(e)})
483
516
  return json.dumps({"results": results})
484
517
 
518
+ if name == "pull_config":
519
+ target = inputs.get("project", "")
520
+ dirs = _find_project_dirs(target)
521
+ if not dirs:
522
+ return json.dumps({"error": f"No project found matching '{target}'"})
523
+ results = []
524
+ for d in dirs:
525
+ res = _git_pull(d)
526
+ results.append({"project": _short_name(d.name), "dir": d.name, **res})
527
+ logger.info("Boss: pull_config %s → %s", d.name, res["status"])
528
+ return json.dumps({"results": results})
529
+
485
530
  return json.dumps({"error": f"unknown tool: {name}"})
486
531
 
487
532