@misterhuydo/sentinel 1.0.20 → 1.0.22

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/.hint-lock CHANGED
@@ -1 +1 @@
1
- 2026-03-21T18:43:03.656Z
1
+ 2026-03-21T19:15:41.308Z
@@ -1,6 +1,6 @@
1
1
  {
2
- "message": "Auto-checkpoint at 2026-03-21T18:58:50.927Z",
3
- "checkpoint_at": "2026-03-21T18:58:50.928Z",
2
+ "message": "Auto-checkpoint at 2026-03-21T19:19:54.642Z",
3
+ "checkpoint_at": "2026-03-21T19:19:54.643Z",
4
4
  "active_files": [],
5
5
  "notes": [],
6
6
  "mtime_snapshot": {}
package/lib/generate.js CHANGED
@@ -92,7 +92,7 @@ rm -f "$PID_FILE"
92
92
 
93
93
  // ── Workspace-level startAll / stopAll ────────────────────────────────────────
94
94
 
95
- function generateWorkspaceScripts(workspace, smtpConfig = {}) {
95
+ function generateWorkspaceScripts(workspace, smtpConfig = {}, slackConfig = {}) {
96
96
  // Write shared sentinel.properties once (never overwrite existing)
97
97
  const workspaceProps = path.join(workspace, 'sentinel.properties');
98
98
  if (!fs.existsSync(workspaceProps)) {
@@ -104,6 +104,15 @@ function generateWorkspaceScripts(workspace, smtpConfig = {}) {
104
104
  if (smtpConfig.password) tpl = tpl.replace('SMTP_PASSWORD=<app-password>', 'SMTP_PASSWORD=' + smtpConfig.password);
105
105
  fs.writeFileSync(workspaceProps, tpl);
106
106
  }
107
+ // Always upsert Slack tokens so re-runs persist them
108
+ if (slackConfig.botToken || slackConfig.appToken) {
109
+ let props = fs.readFileSync(workspaceProps, 'utf8');
110
+ if (slackConfig.botToken)
111
+ props = props.replace(/^#?\s*SLACK_BOT_TOKEN=.*/m, 'SLACK_BOT_TOKEN=' + slackConfig.botToken);
112
+ if (slackConfig.appToken)
113
+ props = props.replace(/^#?\s*SLACK_APP_TOKEN=.*/m, 'SLACK_APP_TOKEN=' + slackConfig.appToken);
114
+ fs.writeFileSync(workspaceProps, props);
115
+ }
107
116
  // startAll.sh
108
117
  fs.writeFileSync(path.join(workspace, 'startAll.sh'), `#!/usr/bin/env bash
109
118
  # Start all valid Sentinel project instances.
@@ -114,7 +123,56 @@ skipped=0
114
123
  for project_dir in "$WORKSPACE"/*/; do
115
124
  name=$(basename "$project_dir")
116
125
  [[ "$name" == "code" ]] && continue
117
- [[ -f "$project_dir/start.sh" ]] || continue
126
+ # Auto-generate start.sh / stop.sh if missing (codeDir = $WORKSPACE/code)
127
+ if [[ ! -f "$project_dir/start.sh" ]]; then
128
+ code_dir="$WORKSPACE/code"
129
+ python_bin="$code_dir/.venv/bin/python3"
130
+ sed -e "s|__NAME__|$name|g" -e "s|__CODE_DIR__|$code_dir|g" -e "s|__PYTHON_BIN__|$python_bin|g" << 'STARTSH' > "$project_dir/start.sh"
131
+ #!/usr/bin/env bash
132
+ set -euo pipefail
133
+ DIR="$(cd "$(dirname "$0")" && pwd)"
134
+ PID_FILE="$DIR/sentinel.pid"
135
+ if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
136
+ echo "[sentinel] __NAME__ already running (PID $(cat "$PID_FILE"))"
137
+ exit 0
138
+ fi
139
+ AUTH_OUT=$(claude --print \"hi\" 2>&1 || true)
140
+ if echo "$AUTH_OUT" | grep -Eqi "not logged in|/login"; then
141
+ echo "[sentinel] Claude Code is not authenticated. Run: claude then /login"
142
+ exit 1
143
+ fi
144
+ mkdir -p "$DIR/logs" "$DIR/workspace/fetched" "$DIR/workspace/patches" "$DIR/issues"
145
+ cd "$DIR"
146
+ PYTHONPATH="__CODE_DIR__" "__PYTHON_BIN__" -m sentinel.main --config ./config \
147
+ >> "$DIR/logs/sentinel.log" 2>&1 &
148
+ echo $! > "$PID_FILE"
149
+ echo "[sentinel] __NAME__ started (PID $!)"
150
+ STARTSH
151
+ chmod +x "$project_dir/start.sh"
152
+ echo "[sentinel] Auto-generated start.sh for $name"
153
+ fi
154
+ if [[ ! -f "$project_dir/stop.sh" ]]; then
155
+ sed -e "s|__NAME__|$name|g" << 'STOPSH' > "$project_dir/stop.sh"
156
+ #!/usr/bin/env bash
157
+ set -euo pipefail
158
+ DIR="$(cd "$(dirname "$0")" && pwd)"
159
+ PID_FILE="$DIR/sentinel.pid"
160
+ if [[ ! -f "$PID_FILE" ]]; then
161
+ echo "[sentinel] __NAME__ — no PID file, not running"
162
+ exit 0
163
+ fi
164
+ PID=$(cat "$PID_FILE")
165
+ if kill -0 "$PID" 2>/dev/null; then
166
+ kill "$PID"
167
+ echo "[sentinel] __NAME__ stopped (PID $PID)"
168
+ else
169
+ echo "[sentinel] __NAME__ — PID $PID not running"
170
+ fi
171
+ rm -f "$PID_FILE"
172
+ STOPSH
173
+ chmod +x "$project_dir/stop.sh"
174
+ echo "[sentinel] Auto-generated stop.sh for $name"
175
+ fi
118
176
 
119
177
  # Must have at least one repo-config with a valid GitHub REPO_URL
120
178
  repo_configs_dir="$project_dir/config/repo-configs"
package/lib/init.js CHANGED
@@ -84,14 +84,14 @@ module.exports = async function init() {
84
84
  {
85
85
  type: prev => prev ? 'password' : null,
86
86
  name: 'slackBotToken',
87
- message: 'Slack Bot Token (xoxb-...)',
88
- validate: v => v.startsWith('xoxb-') ? true : 'Should start with xoxb-',
87
+ message: existing.SLACK_BOT_TOKEN ? 'Slack Bot Token (press Enter to keep current)' : 'Slack Bot Token (xoxb-...)',
88
+ validate: v => !v || v.startsWith('xoxb-') ? true : 'Should start with xoxb-',
89
89
  },
90
90
  {
91
- type: (_, { slackBotToken }) => slackBotToken ? 'password' : null,
91
+ type: (_, { setupSlack }) => setupSlack ? 'password' : null,
92
92
  name: 'slackAppToken',
93
- message: 'Slack App-Level Token (xapp-...)',
94
- validate: v => v.startsWith('xapp-') ? true : 'Should start with xapp-',
93
+ message: existing.SLACK_APP_TOKEN ? 'Slack App-Level Token (press Enter to keep current)' : 'Slack App-Level Token (xapp-...)',
94
+ validate: v => !v || v.startsWith('xapp-') ? true : 'Should start with xapp-',
95
95
  },
96
96
  ], { onCancel: () => process.exit(0) });
97
97
 
@@ -149,9 +149,9 @@ module.exports = async function init() {
149
149
  }
150
150
 
151
151
  // ── Slack Bot ────────────────────────────────────────────────────────────────
152
- if (slackBotToken && slackAppToken) {
152
+ if (effectiveSlackBotToken && effectiveSlackAppToken) {
153
153
  step('Slack Bot (Sentinel Boss)…');
154
- ok('Tokens will be written to the project sentinel.properties');
154
+ ok('Tokens will be written to workspace sentinel.properties');
155
155
  info('Sentinel Boss starts automatically when the project starts');
156
156
  } else if (setupSlack) {
157
157
  warn('Slack tokens not provided — add them to config/sentinel.properties later');
@@ -166,7 +166,7 @@ module.exports = async function init() {
166
166
  if (example) {
167
167
  step('Creating example project…');
168
168
  const exampleDir = path.join(workspace, 'my-project');
169
- writeExampleProject(exampleDir, codeDir, pythonBin, anthropicKey || '', { botToken: slackBotToken || '', appToken: slackAppToken || '' });
169
+ writeExampleProject(exampleDir, codeDir, pythonBin, anthropicKey || '', { botToken: effectiveSlackBotToken, appToken: effectiveSlackAppToken });
170
170
  ok(`Example project: ${exampleDir}`);
171
171
  }
172
172
 
@@ -174,7 +174,9 @@ module.exports = async function init() {
174
174
  step('Generating scripts…');
175
175
  // If user left password blank (pressed Enter to keep), fall back to existing
176
176
  const effectiveSmtpPassword = smtpPassword || existing.SMTP_PASSWORD || '';
177
- generateWorkspaceScripts(workspace, { host: smtpHost, user: smtpUser, password: effectiveSmtpPassword });
177
+ const effectiveSlackBotToken = slackBotToken || existing.SLACK_BOT_TOKEN || '';
178
+ const effectiveSlackAppToken = slackAppToken || existing.SLACK_APP_TOKEN || '';
179
+ generateWorkspaceScripts(workspace, { host: smtpHost, user: smtpUser, password: effectiveSmtpPassword }, { botToken: effectiveSlackBotToken, appToken: effectiveSlackAppToken });
178
180
  ok(`${workspace}/startAll.sh`);
179
181
  ok(`${workspace}/stopAll.sh`);
180
182
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misterhuydo/sentinel",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "Sentinel — Autonomous DevOps Agent installer and manager",
5
5
  "bin": {
6
6
  "sentinel": "./bin/sentinel.js"
@@ -26,3 +26,8 @@ UPGRADE_CHECK_HOURS=6
26
26
 
27
27
  # Config repo polling: if the project dir is a git repo, pull for config changes every N seconds
28
28
  CONFIG_POLL_INTERVAL=60
29
+
30
+ # Slack Bot (Sentinel Boss) — shared across all projects
31
+ # SLACK_BOT_TOKEN=xoxb-...
32
+ # SLACK_APP_TOKEN=xapp-...
33
+ # SLACK_CHANNEL=devops-sentinel