@misterhuydo/sentinel 1.4.13 → 1.4.15

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.
@@ -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/upgrade.js CHANGED
@@ -62,6 +62,16 @@ module.exports = async function upgrade() {
62
62
  process.exit(1);
63
63
  }
64
64
 
65
+ // If a newer version was installed, re-exec so the rest of the upgrade runs with fresh code
66
+ const npmRootEarly = execSync('npm root -g', { encoding: 'utf8' }).trim();
67
+ const { version: installed } = require(path.join(npmRootEarly, '@misterhuydo', 'sentinel', 'package.json'));
68
+ if (installed !== current) {
69
+ ok(`Upgraded: ${current} → ${installed} — re-running with new version...`);
70
+ const newBin = path.join(path.dirname(npmRootEarly), 'bin', 'sentinel');
71
+ const r = spawnSync(newBin, ['upgrade'], { stdio: 'inherit' });
72
+ process.exit(r.status || 0);
73
+ }
74
+
65
75
  info('Upgrading @misterhuydo/cairn-mcp...');
66
76
  spawnSync('npm', ['install', '-g', '@misterhuydo/cairn-mcp@latest', '--prefer-online'], { stdio: 'inherit' });
67
77
  ok('@misterhuydo/cairn-mcp upgraded');
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@misterhuydo/sentinel",
3
- "version": "1.4.13",
3
+ "version": "1.4.15",
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",