@misterhuydo/sentinel 1.0.59 → 1.0.61

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 +1,8 @@
1
- {}
1
+ {
2
+ "J:\\Projects\\Sentinel\\cli\\lib\\upgrade.js": {
3
+ "tempPath": "J:\\Projects\\Sentinel\\cli\\.cairn\\views\\fb78ac_upgrade.js",
4
+ "state": "compressed",
5
+ "minifiedAt": 1774129312316.9353,
6
+ "readCount": 1
7
+ }
8
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
- "message": "Auto-checkpoint at 2026-03-22T14:38:12.904Z",
3
- "checkpoint_at": "2026-03-22T14:38:12.905Z",
2
+ "message": "Auto-checkpoint at 2026-03-22T14:43:35.624Z",
3
+ "checkpoint_at": "2026-03-22T14:43:35.625Z",
4
4
  "active_files": [],
5
5
  "notes": [],
6
6
  "mtime_snapshot": {}
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+ const os = require('os');
5
+ const { execSync, spawnSync } = require('child_process');
6
+ const chalk = require('chalk');
7
+ const ok = msg => console.log(chalk.green(' ✔'), msg);
8
+ const info = msg => console.log(chalk.cyan(' →'), msg);
9
+ const warn = msg => console.log(chalk.yellow(' ⚠'), msg);
10
+ module.exports = async function upgrade() {
11
+ const { version: current } = require('../package.json');
12
+ const defaultWorkspace = path.join(os.homedir(), 'sentinel');
13
+ const codeDir = path.join(defaultWorkspace, 'code');
14
+ if (!fs.existsSync(codeDir)) {
15
+ console.error(chalk.red(' ✖ Sentinel code directory not found at ' + codeDir));
16
+ console.error(' Run: sentinel init');
17
+ process.exit(1);
18
+ }
19
+ info(`Current version: ${current}`);
20
+ info('Installing latest @misterhuydo/sentinel...');
21
+ const install = spawnSync('npm', ['install', '-g', '@misterhuydo/sentinel@latest'], { stdio: 'inherit' });
22
+ if (install.status !== 0) {
23
+ console.error(chalk.red(' ✖ npm install failed'));
24
+ process.exit(1);
25
+ }
26
+ const npmRoot = execSync('npm root -g', { encoding: 'utf8' }).trim();
27
+ const pkgDir = path.join(npmRoot, '@misterhuydo', 'sentinel');
28
+ const src = path.join(pkgDir, 'python');
29
+ if (!fs.existsSync(src)) {
30
+ console.error(chalk.red(' ✖ Bundled Python source not found in installed package'));
31
+ process.exit(1);
32
+ }
33
+ info('Deploying Python source...');
34
+ fs.copySync(src, codeDir, { overwrite: true });
35
+ const scriptsDir = path.join(codeDir, 'scripts');
36
+ if (fs.existsSync(scriptsDir)) {
37
+ const shFiles = fs.readdirSync(scriptsDir)
38
+ .filter(f => f.endsWith('.sh'))
39
+ .map(f => path.join(scriptsDir, f));
40
+ if (shFiles.length) spawnSync('chmod', ['+x', ...shFiles], { stdio: 'inherit' });
41
+ }
42
+ ok('Python source updated');
43
+ const { version: latest } = require(path.join(pkgDir, 'package.json'));
44
+ ok(`Upgraded: ${current} → ${latest}`);
45
+ const startAll = path.join(defaultWorkspace, 'startAll.sh');
46
+ const stopAll = path.join(defaultWorkspace, 'stopAll.sh');
47
+ if (fs.existsSync(stopAll) && fs.existsSync(startAll)) {
48
+ info('Restarting Sentinel...');
49
+ spawnSync('bash', [stopAll], { stdio: 'inherit' });
50
+ spawnSync('bash', [startAll], { stdio: 'inherit' });
51
+ ok('Sentinel restarted');
52
+ } else {
53
+ warn('No startAll.sh found — restart manually');
54
+ }
55
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misterhuydo/sentinel",
3
- "version": "1.0.59",
3
+ "version": "1.0.61",
4
4
  "description": "Sentinel — Autonomous DevOps Agent installer and manager",
5
5
  "bin": {
6
6
  "sentinel": "./bin/sentinel.js"
@@ -553,6 +553,13 @@ async def run_loop(cfg_loader: ConfigLoader, store: StateStore):
553
553
  for key in ("repos", "cairn", "ssh")
554
554
  for item in results[key]
555
555
  )
556
+ for key in ("repos", "cairn", "ssh"):
557
+ for item in results[key]:
558
+ if item["status"] == "error":
559
+ logger.warning("Startup check failed [%s] %s: %s",
560
+ key, item.get("name", ""), item.get("message", ""))
561
+ for w in results.get("warnings", []):
562
+ logger.warning("Startup warning: %s", w)
556
563
  if has_errors:
557
564
  logger.warning("Startup completed with errors — check config and logs")
558
565
  else:
@@ -875,70 +875,36 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
875
875
 
876
876
  if name == "upgrade_sentinel":
877
877
  import threading
878
- import signal as _sig
879
-
880
- code_dir = Path(__file__).resolve().parent.parent # sentinel repo root
881
- project_dir = Path(".").resolve()
882
- steps: list[dict] = []
883
-
884
- # Step 1: git pull the sentinel agent code
885
- pull = _git_pull(code_dir)
886
- steps.append({"step": "git_pull", **pull})
887
- already_latest = "already up to date" in pull.get("detail", "").lower()
888
-
889
- if pull["status"] == "error":
890
- return json.dumps({"status": "error", "steps": steps,
891
- "note": "git pull failed — check network / SSH key"})
892
-
893
- # Step 2: pip install (update Python deps)
894
- venv_pip = code_dir / ".venv" / "bin" / "pip"
895
- pip_cmd = str(venv_pip) if venv_pip.exists() else "pip3"
896
- req_file = code_dir / "requirements.txt"
897
- if req_file.exists():
898
- try:
899
- r = subprocess.run(
900
- [pip_cmd, "install", "-q", "-r", str(req_file)],
901
- capture_output=True, text=True, timeout=120,
902
- )
903
- steps.append({
904
- "step": "pip_install",
905
- "status": "ok" if r.returncode == 0 else "warn",
906
- "detail": (r.stderr or "").strip()[:200],
907
- })
908
- except Exception as e:
909
- steps.append({"step": "pip_install", "status": "warn", "detail": str(e)})
910
878
 
911
- if already_latest:
879
+ # Sentinel is installed via npm — use `sentinel upgrade` which handles
880
+ # npm install + Python bundle copy + restart via stopAll/startAll.
881
+ # Run it in the background after a short delay so the Slack reply is
882
+ # sent before the process is replaced.
883
+ try:
884
+ r = subprocess.run(
885
+ ["sentinel", "--version"],
886
+ capture_output=True, text=True, timeout=10,
887
+ )
888
+ sentinel_bin_ok = r.returncode == 0
889
+ except Exception:
890
+ sentinel_bin_ok = False
891
+
892
+ if not sentinel_bin_ok:
912
893
  return json.dumps({
913
- "status": "already_latest",
914
- "steps": steps,
915
- "note": "Sentinel is already up to date. No restart needed.",
894
+ "status": "error",
895
+ "note": "`sentinel` CLI not found. Run: npm install -g @misterhuydo/sentinel",
916
896
  })
917
897
 
918
- # Step 3: restart — schedule after response is sent
919
- stop_sh = project_dir / "stop.sh"
920
- start_sh = project_dir / "start.sh"
921
- if stop_sh.exists() and start_sh.exists():
922
- def _restart_scripts():
923
- import time; time.sleep(10)
924
- subprocess.Popen(
925
- f"bash {stop_sh} && sleep 2 && bash {start_sh}",
926
- shell=True,
927
- )
928
- threading.Thread(target=_restart_scripts, daemon=True).start()
929
- restart_method = "stop.sh + start.sh"
930
- else:
931
- # SIGTERM self — systemd (Restart=always) will bring it back up
932
- # 10s delay gives Claude time to generate + post the reply before we die
933
- threading.Timer(10.0, lambda: os.kill(os.getpid(), _sig.SIGTERM)).start()
934
- restart_method = "SIGTERM → systemd restart"
898
+ def _do_upgrade():
899
+ import time
900
+ time.sleep(10) # give Slack time to post the reply
901
+ subprocess.Popen(["sentinel", "upgrade"], close_fds=True)
935
902
 
936
- steps.append({"step": "restart", "status": "scheduled", "method": restart_method})
937
- logger.info("Boss: upgrade_sentinel complete, restarting via %s", restart_method)
903
+ threading.Thread(target=_do_upgrade, daemon=True).start()
904
+ logger.info("Boss: upgrade_sentinel scheduled via `sentinel upgrade`")
938
905
  return json.dumps({
939
906
  "status": "ok",
940
- "steps": steps,
941
- "note": "Upgrade complete. Sentinel is restarting — give it a few seconds then I'll be back.",
907
+ "note": "Upgrade started — pulling latest version via npm and restarting. Give me ~30 seconds then I'll be back.",
942
908
  })
943
909
 
944
910
  return json.dumps({"error": f"unknown tool: {name}"})