@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 +1 -1
- package/.cairn/session.json +2 -2
- package/lib/add.js +57 -6
- package/lib/init.js +1 -101
- package/lib/slack-setup.js +113 -0
- package/package.json +1 -1
package/.cairn/.hint-lock
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2026-03-
|
|
1
|
+
2026-03-27T02:03:40.951Z
|
package/.cairn/session.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"message": "Auto-checkpoint at 2026-03-
|
|
3
|
-
"checkpoint_at": "2026-03-
|
|
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 };
|