@misterhuydo/sentinel 1.4.12 → 1.4.14
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/minify-map.json +6 -0
- package/.cairn/views/ff8fde_test.js +172 -0
- package/lib/generate.js +4 -4
- package/lib/upgrade.js +4 -3
- package/package.json +2 -2
package/.cairn/minify-map.json
CHANGED
|
@@ -4,5 +4,11 @@
|
|
|
4
4
|
"state": "compressed",
|
|
5
5
|
"minifiedAt": 1774252515044.4768,
|
|
6
6
|
"readCount": 1
|
|
7
|
+
},
|
|
8
|
+
"J:\\Projects\\Sentinel\\cli\\lib\\test.js": {
|
|
9
|
+
"tempPath": "J:\\Projects\\Sentinel\\cli\\.cairn\\views\\ff8fde_test.js",
|
|
10
|
+
"state": "edit-ready",
|
|
11
|
+
"minifiedAt": 1774252437350.0059,
|
|
12
|
+
"readCount": 1
|
|
7
13
|
}
|
|
8
14
|
}
|
|
@@ -0,0 +1,172 @@
|
|
|
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 fail = msg => console.log(chalk.red(' ✖'), msg);
|
|
9
|
+
const warn = msg => console.log(chalk.yellow(' ⚠'), msg);
|
|
10
|
+
const info = msg => console.log(chalk.cyan(' →'), msg);
|
|
11
|
+
module.exports = async function testInstall(projectName) {
|
|
12
|
+
const defaultWorkspace = path.join(os.homedir(), 'sentinel');
|
|
13
|
+
const projectDir = projectName
|
|
14
|
+
? path.join(defaultWorkspace, projectName)
|
|
15
|
+
: _findActiveProject(defaultWorkspace);
|
|
16
|
+
console.log(chalk.bold('\nSentinel — Installation Check\n'));
|
|
17
|
+
let passed = 0;
|
|
18
|
+
let failed = 0;
|
|
19
|
+
info('Checking required tools...');
|
|
20
|
+
const tools = [
|
|
21
|
+
{ cmd: 'python3 --version', label: 'Python 3' },
|
|
22
|
+
{ cmd: 'node --version', label: 'Node.js' },
|
|
23
|
+
{ cmd: 'git --version', label: 'git' },
|
|
24
|
+
];
|
|
25
|
+
for (const { cmd, label } of tools) {
|
|
26
|
+
try {
|
|
27
|
+
const out = execSync(cmd, { encoding: 'utf8', stdio: ['pipe','pipe','pipe'] }).trim();
|
|
28
|
+
ok(`${label}: ${out}`);
|
|
29
|
+
passed++;
|
|
30
|
+
} catch {
|
|
31
|
+
fail(`${label} not found`);
|
|
32
|
+
failed++;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
info('Checking npm globals...');
|
|
36
|
+
const npms = [
|
|
37
|
+
{ cmd: 'sentinel --version', label: '@misterhuydo/sentinel' },
|
|
38
|
+
{ cmd: 'cairn --version', label: '@misterhuydo/cairn-mcp' },
|
|
39
|
+
{ cmd: 'claude --version', label: '@anthropic-ai/claude-code'},
|
|
40
|
+
];
|
|
41
|
+
for (const { cmd, label } of npms) {
|
|
42
|
+
try {
|
|
43
|
+
const out = execSync(cmd, { encoding: 'utf8', stdio: ['pipe','pipe','pipe'] }).trim();
|
|
44
|
+
ok(`${label}: ${out}`);
|
|
45
|
+
passed++;
|
|
46
|
+
} catch {
|
|
47
|
+
fail(`${label} not installed — run: npm install -g ${label}`);
|
|
48
|
+
failed++;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
info('Checking Claude auth...');
|
|
52
|
+
const apiKey = _readSentinelProp(projectDir, 'ANTHROPIC_API_KEY')
|
|
53
|
+
|| process.env.ANTHROPIC_API_KEY || '';
|
|
54
|
+
const claudeProTasks = (_readSentinelProp(projectDir, 'CLAUDE_PRO_FOR_TASKS') || 'true').toLowerCase() !== 'false';
|
|
55
|
+
if (apiKey) {
|
|
56
|
+
ok(`ANTHROPIC_API_KEY configured (${apiKey.slice(0, 12)}...)`);
|
|
57
|
+
passed++;
|
|
58
|
+
} else {
|
|
59
|
+
warn('ANTHROPIC_API_KEY not set — Sentinel Boss will use CLI fallback only');
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const r = spawnSync('claude', ['--version'], { encoding: 'utf8', stdio: ['pipe','pipe','pipe'] });
|
|
63
|
+
if (r.status === 0) {
|
|
64
|
+
ok(`claude CLI available: ${(r.stdout || '').trim()}`);
|
|
65
|
+
if (claudeProTasks) {
|
|
66
|
+
ok('CLAUDE_PRO_FOR_TASKS=true — fix_engine will use Claude Pro subscription');
|
|
67
|
+
}
|
|
68
|
+
passed++;
|
|
69
|
+
} else {
|
|
70
|
+
warn('claude CLI found but --version failed');
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
fail('claude CLI not found');
|
|
74
|
+
failed++;
|
|
75
|
+
}
|
|
76
|
+
if (!apiKey) {
|
|
77
|
+
try {
|
|
78
|
+
const r = spawnSync('claude', ['--print', 'ping', '--no-interactive'],
|
|
79
|
+
{ encoding: 'utf8', timeout: 10000, stdio: ['pipe','pipe','pipe'] });
|
|
80
|
+
if (r.status === 0) {
|
|
81
|
+
ok('claude OAuth session active');
|
|
82
|
+
passed++;
|
|
83
|
+
} else {
|
|
84
|
+
warn('claude OAuth session may be expired — run: claude login');
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
warn('Could not verify claude OAuth session');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (projectDir && fs.existsSync(projectDir)) {
|
|
91
|
+
info(`Checking project config at ${projectDir}...`);
|
|
92
|
+
const sentinelProps = path.join(projectDir, 'config', 'sentinel.properties');
|
|
93
|
+
if (fs.existsSync(sentinelProps)) {
|
|
94
|
+
ok('sentinel.properties found');
|
|
95
|
+
passed++;
|
|
96
|
+
} else {
|
|
97
|
+
fail(`sentinel.properties missing at ${sentinelProps}`);
|
|
98
|
+
failed++;
|
|
99
|
+
}
|
|
100
|
+
const logConfigs = path.join(projectDir, 'config', 'log-configs');
|
|
101
|
+
const repoConfigs = path.join(projectDir, 'config', 'repo-configs');
|
|
102
|
+
const logCount = fs.existsSync(logConfigs) ? fs.readdirSync(logConfigs).filter(f => f.endsWith('.properties') && !f.startsWith('_')).length : 0;
|
|
103
|
+
const repoCount = fs.existsSync(repoConfigs) ? fs.readdirSync(repoConfigs).filter(f => f.endsWith('.properties') && !f.startsWith('_')).length : 0;
|
|
104
|
+
if (logCount > 0) { ok(`${logCount} log-config(s) found`); passed++; }
|
|
105
|
+
else { warn('No log-configs found — add at least one in config/log-configs/'); }
|
|
106
|
+
if (repoCount > 0) { ok(`${repoCount} repo-config(s) found`); passed++; }
|
|
107
|
+
else { warn('No repo-configs found — add at least one in config/repo-configs/'); }
|
|
108
|
+
const ghToken = _readSentinelProp(projectDir, 'GITHUB_TOKEN') || '';
|
|
109
|
+
if (ghToken) { ok('GITHUB_TOKEN configured'); passed++; }
|
|
110
|
+
else { warn('GITHUB_TOKEN not set — cannot open PRs'); }
|
|
111
|
+
const slackBot = _readSentinelProp(projectDir, 'SLACK_BOT_TOKEN') || '';
|
|
112
|
+
const slackApp = _readSentinelProp(projectDir, 'SLACK_APP_TOKEN') || '';
|
|
113
|
+
if (slackBot && slackApp) { ok('Slack tokens configured (Boss enabled)'); passed++; }
|
|
114
|
+
else { warn('Slack tokens not set — Boss disabled'); }
|
|
115
|
+
} else if (projectName) {
|
|
116
|
+
fail(`Project '${projectName}' not found at ${projectDir}`);
|
|
117
|
+
failed++;
|
|
118
|
+
} else {
|
|
119
|
+
warn('No project specified — skipping project config checks');
|
|
120
|
+
warn('Run: sentinel test <project-name>');
|
|
121
|
+
}
|
|
122
|
+
info('Checking Python dependencies...');
|
|
123
|
+
const codeDir = path.join(defaultWorkspace, 'code');
|
|
124
|
+
const reqFile = path.join(codeDir, 'requirements.txt');
|
|
125
|
+
if (fs.existsSync(reqFile)) {
|
|
126
|
+
try {
|
|
127
|
+
execSync('python3 -c "import paramiko, schedule, requests, jinja2"',
|
|
128
|
+
{ encoding: 'utf8', stdio: ['pipe','pipe','pipe'] });
|
|
129
|
+
ok('Python dependencies installed');
|
|
130
|
+
passed++;
|
|
131
|
+
} catch {
|
|
132
|
+
fail('Python dependencies missing — run: pip install -r requirements.txt');
|
|
133
|
+
failed++;
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
warn('requirements.txt not found — run: sentinel init');
|
|
137
|
+
}
|
|
138
|
+
console.log('');
|
|
139
|
+
if (failed === 0) {
|
|
140
|
+
console.log(chalk.green.bold(` ✔ All checks passed (${passed} ok)`));
|
|
141
|
+
} else {
|
|
142
|
+
console.log(chalk.yellow.bold(` ${passed} passed, ${failed} failed`));
|
|
143
|
+
console.log(chalk.gray(' Fix the issues above before starting Sentinel.'));
|
|
144
|
+
}
|
|
145
|
+
console.log('');
|
|
146
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
147
|
+
};
|
|
148
|
+
function _findActiveProject(workspace) {
|
|
149
|
+
if (!fs.existsSync(workspace)) return null;
|
|
150
|
+
const dirs = fs.readdirSync(workspace)
|
|
151
|
+
.map(d => path.join(workspace, d))
|
|
152
|
+
.filter(d => fs.statSync(d).isDirectory()
|
|
153
|
+
&& fs.existsSync(path.join(d, 'config', 'sentinel.properties')));
|
|
154
|
+
return dirs.length === 1 ? dirs[0] : null;
|
|
155
|
+
}
|
|
156
|
+
function _readSentinelProp(projectDir, key) {
|
|
157
|
+
if (!projectDir) return '';
|
|
158
|
+
const candidates = [
|
|
159
|
+
path.join(projectDir, 'config', 'sentinel.properties'),
|
|
160
|
+
path.join(os.homedir(), 'sentinel', 'sentinel.properties'),
|
|
161
|
+
];
|
|
162
|
+
for (const f of candidates) {
|
|
163
|
+
if (!fs.existsSync(f)) continue;
|
|
164
|
+
for (const line of fs.readFileSync(f, 'utf8').split('\n')) {
|
|
165
|
+
const stripped = line.trim();
|
|
166
|
+
if (stripped.startsWith('#') || !stripped.includes('=')) continue;
|
|
167
|
+
const [k, ...rest] = stripped.split('=');
|
|
168
|
+
if (k.trim() === key) return rest.join('=').split('#')[0].trim();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return '';
|
|
172
|
+
}
|
package/lib/generate.js
CHANGED
|
@@ -171,11 +171,11 @@ function generateWorkspaceScripts(workspace, smtpConfig = {}, slackConfig = {},
|
|
|
171
171
|
WORKSPACE="$(cd "$(dirname "$0")" && pwd)"
|
|
172
172
|
started=0
|
|
173
173
|
skipped=0
|
|
174
|
+
NON_PROJECT="code repos logs issues workspace"
|
|
174
175
|
for project_dir in "$WORKSPACE"/*/; do
|
|
175
176
|
[[ -d "$project_dir" ]] || continue
|
|
176
177
|
name=$(basename "$project_dir")
|
|
177
|
-
|
|
178
|
-
[[ "$name" == "repos" ]] && continue
|
|
178
|
+
echo " $NON_PROJECT " | grep -qw "$name" && continue
|
|
179
179
|
# Auto-generate start.sh / stop.sh if missing (codeDir = $WORKSPACE/code)
|
|
180
180
|
if [[ ! -f "$project_dir/start.sh" ]]; then
|
|
181
181
|
code_dir="$WORKSPACE/code"
|
|
@@ -294,11 +294,11 @@ echo "[sentinel] $started project(s) started, $skipped skipped"
|
|
|
294
294
|
# Stop all Sentinel project instances
|
|
295
295
|
WORKSPACE="$(cd "$(dirname "$0")" && pwd)"
|
|
296
296
|
stopped=0
|
|
297
|
+
NON_PROJECT="code repos logs issues workspace"
|
|
297
298
|
for project_dir in "$WORKSPACE"/*/; do
|
|
298
299
|
[[ -d "$project_dir" ]] || continue
|
|
299
300
|
name=$(basename "$project_dir")
|
|
300
|
-
|
|
301
|
-
[[ "$name" == "repos" ]] && continue
|
|
301
|
+
echo " $NON_PROJECT " | grep -qw "$name" && continue
|
|
302
302
|
[[ -f "$project_dir/stop.sh" ]] || continue
|
|
303
303
|
bash "$project_dir/stop.sh"
|
|
304
304
|
stopped=$((stopped + 1))
|
package/lib/upgrade.js
CHANGED
|
@@ -97,9 +97,10 @@ module.exports = async function upgrade() {
|
|
|
97
97
|
const { version: latest } = require(path.join(pkgDir, 'package.json'));
|
|
98
98
|
ok(`Upgraded: ${current} → ${latest}`);
|
|
99
99
|
|
|
100
|
-
// Regenerate
|
|
101
|
-
info('Regenerating
|
|
102
|
-
const { generateProjectScripts } = require('./generate');
|
|
100
|
+
// Regenerate workspace startAll.sh / stopAll.sh and all project start.sh / stop.sh
|
|
101
|
+
info('Regenerating scripts...');
|
|
102
|
+
const { generateProjectScripts, generateWorkspaceScripts } = require('./generate');
|
|
103
|
+
generateWorkspaceScripts(defaultWorkspace);
|
|
103
104
|
const pythonBin = fs.existsSync('/usr/bin/python3') ? '/usr/bin/python3' : 'python3';
|
|
104
105
|
const NON_PROJECT_DIRS = new Set(['logs', 'code', 'repos', 'workspace', 'issues']);
|
|
105
106
|
let regenerated = 0;
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@misterhuydo/sentinel",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.14",
|
|
4
4
|
"description": "Sentinel — Autonomous DevOps Agent installer and manager",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sentinel": "./bin/sentinel.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"prepublishOnly": "node scripts/bundle.js"
|
|
9
|
+
"prepublishOnly": "node scripts/test-scripts.js && node scripts/bundle.js"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"chalk": "^4.1.2",
|