@misterhuydo/sentinel 1.0.21 → 1.0.23

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-21T19:16:11.175Z",
3
- "checkpoint_at": "2026-03-21T19:16:11.176Z",
2
+ "message": "Auto-checkpoint at 2026-03-21T19:29:35.017Z",
3
+ "checkpoint_at": "2026-03-21T19:29:35.026Z",
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.
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
 
@@ -217,9 +219,26 @@ module.exports = async function init() {
217
219
  // ── Helpers ──────────────────────────────────────────────────────────────────
218
220
 
219
221
  function readExistingConfig(workspace) {
220
- const propsPath = path.join(workspace, 'sentinel.properties');
221
- if (!fs.existsSync(propsPath)) return {};
222
222
  const result = {};
223
+ _parsePropsInto(path.join(workspace, 'sentinel.properties'), result);
224
+ // Migrate: if Slack tokens not in workspace config, scan project configs as fallback
225
+ if (!result.SLACK_BOT_TOKEN || !result.SLACK_APP_TOKEN) {
226
+ try {
227
+ for (const entry of fs.readdirSync(workspace)) {
228
+ const p = path.join(workspace, entry, 'config', 'sentinel.properties');
229
+ const proj = {};
230
+ _parsePropsInto(p, proj);
231
+ if (!result.SLACK_BOT_TOKEN && proj.SLACK_BOT_TOKEN) result.SLACK_BOT_TOKEN = proj.SLACK_BOT_TOKEN;
232
+ if (!result.SLACK_APP_TOKEN && proj.SLACK_APP_TOKEN) result.SLACK_APP_TOKEN = proj.SLACK_APP_TOKEN;
233
+ if (result.SLACK_BOT_TOKEN && result.SLACK_APP_TOKEN) break;
234
+ }
235
+ } catch (_) {}
236
+ }
237
+ return result;
238
+ }
239
+
240
+ function _parsePropsInto(propsPath, result) {
241
+ if (!fs.existsSync(propsPath)) return;
223
242
  try {
224
243
  const lines = fs.readFileSync(propsPath, 'utf8').split(/\r?\n/);
225
244
  for (const raw of lines) {
@@ -230,7 +249,6 @@ function readExistingConfig(workspace) {
230
249
  result[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
231
250
  }
232
251
  } catch (_) {}
233
- return result;
234
252
  }
235
253
 
236
254
  function findPython() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misterhuydo/sentinel",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
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