@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.
- package/.cairn/session.json +2 -2
- package/package.json +1 -1
- package/python/sentinel/sentinel_boss.py +85 -40
package/.cairn/session.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"message": "Auto-checkpoint at 2026-03-
|
|
3
|
-
"checkpoint_at": "2026-03-
|
|
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
|
@@ -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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
|