@misterhuydo/sentinel 1.4.79 → 1.4.80

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-27T01:29:06.939Z
1
+ 2026-03-27T02:03:40.951Z
@@ -1,6 +1,6 @@
1
1
  {
2
- "message": "Auto-checkpoint at 2026-03-27T01:50:53.961Z",
3
- "checkpoint_at": "2026-03-27T01:50:53.962Z",
2
+ "message": "Auto-checkpoint at 2026-03-27T02:01:01.379Z",
3
+ "checkpoint_at": "2026-03-27T02:01:01.380Z",
4
4
  "active_files": [],
5
5
  "notes": [],
6
6
  "mtime_snapshot": {}
package/lib/add.js CHANGED
@@ -6,6 +6,7 @@ const { execSync, spawnSync } = require('child_process');
6
6
  const prompts = require('prompts');
7
7
  const chalk = require('chalk');
8
8
  const { writeExampleProject, generateWorkspaceScripts, generateProjectScripts } = require('./generate');
9
+ const { printSlackSetupGuide } = require('./slack-setup');
9
10
  const ok = msg => console.log(chalk.green(' ✔'), msg);
10
11
  const info = msg => console.log(chalk.cyan(' →'), msg);
11
12
  const warn = msg => console.log(chalk.yellow(' ⚠'), msg);
@@ -539,6 +540,38 @@ async function addFromGit(gitUrl, workspace) {
539
540
  info('GITHUB_TOKEN will be written when project files are created');
540
541
  }
541
542
 
543
+ // ── Project-specific Slack (optional override) ─────────────────────────────
544
+ const workspaceSlackToken = fs.existsSync(workspaceProps)
545
+ ? (fs.readFileSync(workspaceProps, 'utf8').match(/^SLACK_BOT_TOKEN\s*=\s*(.+)$/m) || [])[1]?.trim()
546
+ : '';
547
+ const { ownSlack } = await prompts({
548
+ type: 'confirm', name: 'ownSlack',
549
+ message: workspaceSlackToken
550
+ ? 'Does this project use a different Slack workspace than the default?'
551
+ : 'Does this project use Slack (Sentinel Boss)?',
552
+ initial: false,
553
+ }, { onCancel: () => process.exit(0) });
554
+
555
+ let projectSlackBotToken = '';
556
+ let projectSlackAppToken = '';
557
+ if (ownSlack) {
558
+ printSlackSetupGuide(false, 'Setting up project Slack Bot…');
559
+ const slackAnswers = await prompts([
560
+ {
561
+ type: 'password', name: 'botToken',
562
+ message: 'Slack Bot Token (xoxb-...)',
563
+ validate: v => !v || v.startsWith('xoxb-') ? true : 'Should start with xoxb-',
564
+ },
565
+ {
566
+ type: 'password', name: 'appToken',
567
+ message: 'Slack App-Level Token (xapp-...)',
568
+ validate: v => !v || v.startsWith('xapp-') ? true : 'Should start with xapp-',
569
+ },
570
+ ], { onCancel: () => process.exit(0) });
571
+ projectSlackBotToken = slackAnswers.botToken || '';
572
+ projectSlackAppToken = slackAnswers.appToken || '';
573
+ }
574
+
542
575
  // ── Preview + confirm ──────────────────────────────────────────────────────
543
576
  step('Dry-run preview');
544
577
  info(`Will create: ${projectDir}/`);
@@ -568,9 +601,6 @@ async function addFromGit(gitUrl, workspace) {
568
601
  if (discovered.length > 0) {
569
602
  // Config already exists in the cloned repo — just generate scripts
570
603
  generateProjectScripts(projectDir, codeDir, pythonBin);
571
- ok(`Project "${name}" ready at ${projectDir}`);
572
- printNextSteps(projectDir, autoPublish);
573
- await offerToStart(projectDir);
574
604
  } else {
575
605
  // No existing repo-configs — scaffold fresh project
576
606
  writeExampleProject(projectDir, codeDir, pythonBin);
@@ -584,10 +614,31 @@ async function addFromGit(gitUrl, workspace) {
584
614
  const example = path.join(repoDir, '_example.properties');
585
615
  if (fs.existsSync(example)) fs.removeSync(example);
586
616
  generateWorkspaceScripts(workspace, {}, {}, {}, effectiveToken);
587
- ok(`Project "${name}" created at ${projectDir}`);
588
- printNextSteps(projectDir, autoPublish);
589
- await offerToStart(projectDir);
590
617
  }
618
+
619
+ // Write project-level Slack tokens if collected
620
+ if (projectSlackBotToken || projectSlackAppToken) {
621
+ const projProps = path.join(projectDir, 'config', 'sentinel.properties');
622
+ if (fs.existsSync(projProps)) {
623
+ let props = fs.readFileSync(projProps, 'utf8');
624
+ if (projectSlackBotToken) {
625
+ props = /^#?\s*SLACK_BOT_TOKEN\s*=/m.test(props)
626
+ ? props.replace(/^#?\s*SLACK_BOT_TOKEN\s*=.*/m, `SLACK_BOT_TOKEN=${projectSlackBotToken}`)
627
+ : props.trimEnd() + `\nSLACK_BOT_TOKEN=${projectSlackBotToken}\n`;
628
+ }
629
+ if (projectSlackAppToken) {
630
+ props = /^#?\s*SLACK_APP_TOKEN\s*=/m.test(props)
631
+ ? props.replace(/^#?\s*SLACK_APP_TOKEN\s*=.*/m, `SLACK_APP_TOKEN=${projectSlackAppToken}`)
632
+ : props.trimEnd() + `\nSLACK_APP_TOKEN=${projectSlackAppToken}\n`;
633
+ }
634
+ fs.writeFileSync(projProps, props);
635
+ ok('Project-level Slack tokens saved to config/sentinel.properties');
636
+ }
637
+ }
638
+
639
+ ok(`Project "${name}" ${discovered.length > 0 ? 'ready' : 'created'} at ${projectDir}`);
640
+ printNextSteps(projectDir, autoPublish);
641
+ await offerToStart(projectDir);
591
642
  }
592
643
 
593
644
  // ── addFromName ───────────────────────────────────────────────────────────────
package/lib/init.js CHANGED
@@ -7,6 +7,7 @@ const { execSync, spawnSync } = require('child_process');
7
7
  const prompts = require('prompts');
8
8
  const chalk = require('chalk');
9
9
  const { generateProjectScripts, generateWorkspaceScripts, writeExampleProject } = require('./generate');
10
+ const { buildSlackManifest, printSlackSetupGuide } = require('./slack-setup');
10
11
 
11
12
  const ok = msg => console.log(chalk.green(' ✔'), msg);
12
13
  const info = msg => console.log(chalk.cyan(' →'), msg);
@@ -401,107 +402,6 @@ function ensureNpmUserPrefix() {
401
402
  }
402
403
  }
403
404
 
404
- function buildSlackManifest() {
405
- return {
406
- display_information: {
407
- name: 'Sentinel',
408
- description: 'Autonomous DevOps Agent — monitors logs, fixes bugs, manages deployments',
409
- background_color: '#1a1a2e',
410
- },
411
- features: {
412
- bot_user: {
413
- display_name: 'Sentinel',
414
- always_online: true,
415
- },
416
- },
417
- oauth_config: {
418
- scopes: {
419
- bot: [
420
- 'app_mentions:read', // receive @Sentinel mentions
421
- 'channels:history', // read public channel messages
422
- 'channels:read', // list channels
423
- 'chat:write', // post messages
424
- 'chat:write.customize', // post with custom name/icon
425
- 'chat:write.public', // post in channels without joining
426
- 'files:read', // read files shared with the bot
427
- 'files:write', // upload files (DB exports, reports)
428
- 'groups:history', // read private channel messages
429
- 'groups:read', // list private channels
430
- 'im:history', // read DM messages
431
- 'im:read', // list DMs
432
- 'im:write', // open DM channels (for user notifications)
433
- 'mpim:history', // read multi-person DMs
434
- 'mpim:read', // list multi-person DMs
435
- 'reactions:write', // add emoji reactions
436
- 'users:read', // look up user info / timezone
437
- 'users:read.email', // look up user by email
438
- 'usergroups:read', // resolve @here / @channel for admin checks
439
- ],
440
- },
441
- },
442
- settings: {
443
- event_subscriptions: {
444
- bot_events: [
445
- 'app_mention', // @Sentinel in any channel
446
- 'message.channels', // messages in public channels (thread replies)
447
- 'message.groups', // messages in private channels (thread replies)
448
- 'message.im', // direct messages to Sentinel
449
- 'message.mpim', // multi-person DMs
450
- ],
451
- },
452
- interactivity: { is_enabled: false },
453
- org_deploy_enabled: false,
454
- socket_mode_enabled: true, // no public URL needed
455
- token_rotation_enabled: false,
456
- },
457
- };
458
- }
459
-
460
- function printSlackSetupGuide(hasExisting) {
461
- const manifest = buildSlackManifest();
462
- const manifestJson = JSON.stringify(manifest, null, 2);
463
-
464
- step('Setting up Slack Bot (Sentinel Boss)…');
465
-
466
- if (hasExisting) {
467
- console.log(`
468
- ${chalk.bold('Update your existing Slack app manifest:')}
469
-
470
- ${chalk.white('1.')} Open: ${chalk.cyan('https://api.slack.com/apps')} → select your Sentinel app
471
- ${chalk.white('2.')} Click ${chalk.cyan('"App Manifest"')} in the left sidebar
472
- ${chalk.white('3.')} Replace the entire manifest with:
473
-
474
- ${manifestJson.split('\n').map(l => ' ' + l).join('\n')}
475
-
476
- ${chalk.white('4.')} Click ${chalk.green('"Save Changes"')} → reinstall to workspace if prompted
477
- ${chalk.white('5.')} Paste your tokens below ↓
478
- `);
479
- } else {
480
- const createUrlEncoded = `https://api.slack.com/apps?new_app=1&manifest_json=${encodeURIComponent(JSON.stringify(buildSlackManifest()))}`;
481
- console.log(`
482
- ${chalk.bold('One-click Slack app setup:')}
483
-
484
- ${chalk.cyan(createUrlEncoded)}
485
-
486
- ${chalk.bold('Steps after clicking the link:')}
487
- ${chalk.white('1.')} Slack opens with all permissions pre-filled → click ${chalk.green('"Create App"')}
488
- ${chalk.white('2.')} Click ${chalk.green('"Install to Workspace"')} → Allow
489
- ${chalk.white('3.')} Copy ${chalk.yellow('Bot Token')} (OAuth & Permissions → Bot User OAuth Token → xoxb-...)
490
- ${chalk.white('4.')} Go to ${chalk.cyan('Settings → Basic Information → App-Level Tokens')}
491
- → ${chalk.green('"Generate Token and Scopes"')} name it anything, add scope: ${chalk.yellow('connections:write')}
492
- → Copy ${chalk.yellow('App-Level Token')} (xapp-...)
493
- ${chalk.white('5.')} Paste both tokens below ↓
494
- `);
495
- try {
496
- const { execSync: _exec } = require('child_process');
497
- const opener = process.platform === 'darwin' ? 'open'
498
- : process.platform === 'win32' ? 'start'
499
- : 'xdg-open';
500
- _exec(`${opener} "${createUrlEncoded}"`, { stdio: 'ignore' });
501
- console.log(chalk.green(' ✔') + ' Opened in your browser\n');
502
- } catch (_) {}
503
- }
504
- }
505
405
 
506
406
  function setupSystemd(workspace) {
507
407
  const user = os.userInfo().username;
@@ -0,0 +1,113 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+ const step = msg => console.log('\n' + chalk.bold.white(msg));
5
+
6
+ function buildSlackManifest() {
7
+ return {
8
+ display_information: {
9
+ name: 'Sentinel',
10
+ description: 'Autonomous DevOps Agent — monitors logs, fixes bugs, manages deployments',
11
+ background_color: '#1a1a2e',
12
+ },
13
+ features: {
14
+ bot_user: {
15
+ display_name: 'Sentinel',
16
+ always_online: true,
17
+ },
18
+ },
19
+ oauth_config: {
20
+ scopes: {
21
+ bot: [
22
+ 'app_mentions:read', // receive @Sentinel mentions
23
+ 'channels:history', // read public channel messages
24
+ 'channels:read', // list channels
25
+ 'chat:write', // post messages
26
+ 'chat:write.customize', // post with custom name/icon
27
+ 'chat:write.public', // post in channels without joining
28
+ 'files:read', // read files shared with the bot
29
+ 'files:write', // upload files (DB exports, reports)
30
+ 'groups:history', // read private channel messages
31
+ 'groups:read', // list private channels
32
+ 'im:history', // read DM messages
33
+ 'im:read', // list DMs
34
+ 'im:write', // open DM channels (for user notifications)
35
+ 'mpim:history', // read multi-person DMs
36
+ 'mpim:read', // list multi-person DMs
37
+ 'reactions:write', // add emoji reactions
38
+ 'users:read', // look up user info / timezone
39
+ 'users:read.email', // look up user by email
40
+ 'usergroups:read', // resolve @here / @channel for admin checks
41
+ ],
42
+ },
43
+ },
44
+ settings: {
45
+ event_subscriptions: {
46
+ bot_events: [
47
+ 'app_mention', // @Sentinel in any channel
48
+ 'message.channels', // messages in public channels (thread replies)
49
+ 'message.groups', // messages in private channels (thread replies)
50
+ 'message.im', // direct messages to Sentinel
51
+ 'message.mpim', // multi-person DMs
52
+ ],
53
+ },
54
+ interactivity: { is_enabled: false },
55
+ org_deploy_enabled: false,
56
+ socket_mode_enabled: true,
57
+ token_rotation_enabled: false,
58
+ },
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Print Slack app setup guide.
64
+ * @param {boolean} hasExisting true = update existing app, false = create new
65
+ * @param {string} [stepTitle] optional override for the step heading
66
+ */
67
+ function printSlackSetupGuide(hasExisting, stepTitle) {
68
+ const manifest = buildSlackManifest();
69
+ const manifestJson = JSON.stringify(manifest, null, 2);
70
+
71
+ step(stepTitle || 'Setting up Slack Bot (Sentinel Boss)…');
72
+
73
+ if (hasExisting) {
74
+ console.log(`
75
+ ${chalk.bold('Update your existing Slack app manifest:')}
76
+
77
+ ${chalk.white('1.')} Open: ${chalk.cyan('https://api.slack.com/apps')} → select your Sentinel app
78
+ ${chalk.white('2.')} Click ${chalk.cyan('"App Manifest"')} in the left sidebar
79
+ ${chalk.white('3.')} Replace the entire manifest with:
80
+
81
+ ${manifestJson.split('\n').map(l => ' ' + l).join('\n')}
82
+
83
+ ${chalk.white('4.')} Click ${chalk.green('"Save Changes"')} → reinstall to workspace if prompted
84
+ ${chalk.white('5.')} Paste your tokens below ↓
85
+ `);
86
+ } else {
87
+ const createUrl = `https://api.slack.com/apps?new_app=1&manifest_json=${encodeURIComponent(JSON.stringify(manifest))}`;
88
+ console.log(`
89
+ ${chalk.bold('One-click Slack app setup:')}
90
+
91
+ ${chalk.cyan(createUrl)}
92
+
93
+ ${chalk.bold('Steps after clicking the link:')}
94
+ ${chalk.white('1.')} Slack opens with all permissions pre-filled → click ${chalk.green('"Create App"')}
95
+ ${chalk.white('2.')} Click ${chalk.green('"Install to Workspace"')} → Allow
96
+ ${chalk.white('3.')} Copy ${chalk.yellow('Bot Token')} (OAuth & Permissions → Bot User OAuth Token → xoxb-...)
97
+ ${chalk.white('4.')} Go to ${chalk.cyan('Settings → Basic Information → App-Level Tokens')}
98
+ → ${chalk.green('"Generate Token and Scopes"')} name it anything, add scope: ${chalk.yellow('connections:write')}
99
+ → Copy ${chalk.yellow('App-Level Token')} (xapp-...)
100
+ ${chalk.white('5.')} Paste both tokens below ↓
101
+ `);
102
+ try {
103
+ const { execSync: _exec } = require('child_process');
104
+ const opener = process.platform === 'darwin' ? 'open'
105
+ : process.platform === 'win32' ? 'start'
106
+ : 'xdg-open';
107
+ _exec(`${opener} "${createUrl}"`, { stdio: 'ignore' });
108
+ console.log(chalk.green(' ✔') + ' Opened in your browser\n');
109
+ } catch (_) {}
110
+ }
111
+ }
112
+
113
+ module.exports = { buildSlackManifest, printSlackSetupGuide };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misterhuydo/sentinel",
3
- "version": "1.4.79",
3
+ "version": "1.4.80",
4
4
  "description": "Sentinel — Autonomous DevOps Agent installer and manager",
5
5
  "bin": {
6
6
  "sentinel": "./bin/sentinel.js"