@misterhuydo/sentinel 1.0.46 → 1.0.48

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-22T05:40:32.023Z",
3
- "checkpoint_at": "2026-03-22T05:40:32.024Z",
2
+ "message": "Auto-checkpoint at 2026-03-22T05:57:18.189Z",
3
+ "checkpoint_at": "2026-03-22T05:57:18.190Z",
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.46",
3
+ "version": "1.0.48",
4
4
  "description": "Sentinel — Autonomous DevOps Agent installer and manager",
5
5
  "bin": {
6
6
  "sentinel": "./bin/sentinel.js"
@@ -60,6 +60,7 @@ class SentinelConfig:
60
60
  slack_bot_token: str = "" # xoxb-...
61
61
  slack_app_token: str = "" # xapp-... (Socket Mode)
62
62
  slack_channel: str = "" # optional: restrict to one channel ID or name
63
+ project_name: str = "" # optional: friendly name used by Sentinel Boss (e.g. "1881")
63
64
 
64
65
 
65
66
  @dataclass
@@ -152,6 +153,7 @@ class ConfigLoader:
152
153
  c.slack_bot_token = d.get("SLACK_BOT_TOKEN", "")
153
154
  c.slack_app_token = d.get("SLACK_APP_TOKEN", "")
154
155
  c.slack_channel = d.get("SLACK_CHANNEL", "")
156
+ c.project_name = d.get("PROJECT_NAME", "")
155
157
  self.sentinel = c
156
158
 
157
159
  def _load_log_sources(self):
@@ -290,8 +290,24 @@ def _short_name(dir_name: str) -> str:
290
290
  return dir_name[len("sentinel-"):]
291
291
  return dir_name
292
292
 
293
+ def _read_project_name(project_dir: Path) -> str:
294
+ """Return PROJECT_NAME from sentinel.properties if set, else fall back to _short_name(dir)."""
295
+ props = project_dir / "config" / "sentinel.properties"
296
+ if props.exists():
297
+ try:
298
+ for line in props.read_text(encoding="utf-8", errors="ignore").splitlines():
299
+ line = line.strip()
300
+ if line.startswith("PROJECT_NAME"):
301
+ _, _, val = line.partition("=")
302
+ val = val.partition("#")[0].strip()
303
+ if val:
304
+ return val
305
+ except Exception:
306
+ pass
307
+ return _short_name(project_dir.name)
308
+
293
309
  def _find_project_dirs(target: str = "") -> list[Path]:
294
- """Return project dirs matching target (short or full name), or all if target empty."""
310
+ """Return project dirs matching target (PROJECT_NAME, short name, or full dir name), or all if target empty."""
295
311
  workspace = _workspace_dir()
296
312
  results = []
297
313
  try:
@@ -301,7 +317,10 @@ def _find_project_dirs(target: str = "") -> list[Path]:
301
317
  if not (d / "config").exists():
302
318
  continue
303
319
  if target:
304
- if target.lower() not in d.name.lower() and target.lower() not in _short_name(d.name).lower():
320
+ t = target.lower()
321
+ if (t not in d.name.lower()
322
+ and t not in _short_name(d.name).lower()
323
+ and t not in _read_project_name(d).lower()):
305
324
  continue
306
325
  results.append(d)
307
326
  except Exception:
@@ -365,14 +384,14 @@ def _run_tool(name: str, inputs: dict, cfg_loader, store) -> str:
365
384
  if project_arg:
366
385
  project_dirs = _find_project_dirs(project_arg)
367
386
  if not project_dirs:
368
- all_names = [_short_name(d.name) for d in _find_project_dirs()]
387
+ all_names = [_read_project_name(d) for d in _find_project_dirs()]
369
388
  return json.dumps({
370
389
  "error": f"No project found matching '{project_arg}'",
371
390
  "available_projects": all_names,
372
391
  "action_needed": "Ask the user which project they meant.",
373
392
  })
374
393
  if len(project_dirs) > 1:
375
- matches = [_short_name(d.name) for d in project_dirs]
394
+ matches = [_read_project_name(d) for d in project_dirs]
376
395
  return json.dumps({
377
396
  "error": f"Ambiguous project name '{project_arg}' — matches: {matches}",
378
397
  "action_needed": "Ask the user to clarify which project they mean.",
@@ -390,7 +409,7 @@ def _run_tool(name: str, inputs: dict, cfg_loader, store) -> str:
390
409
  # Touch SENTINEL_POLL_NOW so the target instance picks it up immediately
391
410
  (project_dir / "SENTINEL_POLL_NOW").touch()
392
411
 
393
- project_label = _short_name(project_dir.resolve().name) if project_arg else "this project"
412
+ project_label = _read_project_name(project_dir.resolve()) if project_arg else "this project"
394
413
  logger.info("Boss created issue for %s: %s", project_label, fname)
395
414
  return json.dumps({
396
415
  "status": "queued",
@@ -451,7 +470,7 @@ def _run_tool(name: str, inputs: dict, cfg_loader, store) -> str:
451
470
  break
452
471
  repos_in_project.append({"repo": p.stem, "url": repo_url})
453
472
  projects.append({
454
- "project": _short_name(d.name),
473
+ "project": _read_project_name(d),
455
474
  "dir": d.name,
456
475
  "running": (d / "sentinel.pid").exists(),
457
476
  "this": d.resolve() == Path(".").resolve(),
@@ -561,7 +580,7 @@ def _run_tool(name: str, inputs: dict, cfg_loader, store) -> str:
561
580
  results = []
562
581
  for d in dirs:
563
582
  res = _git_pull(d)
564
- results.append({"project": _short_name(d.name), "dir": d.name, **res})
583
+ results.append({"project": _read_project_name(d), "dir": d.name, **res})
565
584
  logger.info("Boss: pull_config %s → %s", d.name, res["status"])
566
585
  return json.dumps({"results": results})
567
586
 
@@ -703,7 +722,7 @@ async def handle_message(
703
722
  paused = Path("SENTINEL_PAUSE").exists()
704
723
  repos = list(cfg_loader.repos.keys())
705
724
  ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
706
- known_projects = [_short_name(d.name) for d in _find_project_dirs()]
725
+ known_projects = [_read_project_name(d) for d in _find_project_dirs()]
707
726
  system = (
708
727
  _SYSTEM
709
728
  + f"\n\nCurrent time: {ts}"
@@ -2,6 +2,10 @@
2
2
  # Shared settings (SMTP, schedule, etc.) live in the workspace-level sentinel.properties
3
3
  # one directory above this project. Values here override workspace defaults.
4
4
 
5
+ # Friendly project name for Sentinel Boss (optional).
6
+ # Boss will match this name when you say "tell 1881 to do X". If omitted, derived from dir name.
7
+ # PROJECT_NAME=1881
8
+
5
9
  # Who receives fix notifications and health reports for this project
6
10
  MAILS=you@yourdomain.com
7
11
 
@@ -9,8 +13,10 @@ MAILS=you@yourdomain.com
9
13
  SEND_HEALTH=disabled
10
14
  REPORT_INTERVAL_HOURS=1
11
15
 
12
- # GitHub token for opening PRs (required when AUTO_PUBLISH=false)
13
- GITHUB_TOKEN=<github-pat>
16
+ # GitHub token for opening PRs (when AUTO_PUBLISH=false).
17
+ # Usually set once in the workspace sentinel.properties and shared across all projects.
18
+ # Uncomment here only if this project needs a different token.
19
+ # GITHUB_TOKEN=<github-pat>
14
20
 
15
21
  # State DB and workspace paths (relative to this project dir)
16
22
  STATE_DB=./sentinel.db
@@ -10,6 +10,10 @@ SMTP_PORT=587
10
10
  SMTP_USER=sentinel@yourdomain.com
11
11
  SMTP_PASSWORD=<app-password>
12
12
 
13
+ # GitHub token for opening PRs (when AUTO_PUBLISH=false).
14
+ # Shared across all projects — override per-project in config/sentinel.properties if needed.
15
+ GITHUB_TOKEN=<github-pat>
16
+
13
17
  # Fix confidence threshold (0.0 - 1.0)
14
18
  FIX_CONFIDENCE_THRESHOLD=0.7
15
19