@misterhuydo/sentinel 1.0.59 → 1.0.60

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.60",
4
4
  "description": "Sentinel — Autonomous DevOps Agent installer and manager",
5
5
  "bin": {
6
6
  "sentinel": "./bin/sentinel.js"
@@ -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}"})