@misterhuydo/sentinel 1.0.62 → 1.0.63
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
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"J:\\Projects\\Sentinel\\cli\\lib\\upgrade.js": {
|
|
3
3
|
"tempPath": "J:\\Projects\\Sentinel\\cli\\.cairn\\views\\fb78ac_upgrade.js",
|
|
4
|
-
"state": "
|
|
4
|
+
"state": "edit-ready",
|
|
5
5
|
"minifiedAt": 1774129312316.9353,
|
|
6
6
|
"readCount": 1
|
|
7
|
+
},
|
|
8
|
+
"J:\\Projects\\Sentinel\\cli\\bin\\sentinel.js": {
|
|
9
|
+
"tempPath": "J:\\Projects\\Sentinel\\cli\\.cairn\\views\\a348d8_sentinel.js",
|
|
10
|
+
"state": "compressed",
|
|
11
|
+
"minifiedAt": 1774128147034.2527,
|
|
12
|
+
"readCount": 1
|
|
7
13
|
}
|
|
8
14
|
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
function writeExampleProject(projectDir, codeDir, pythonBin, anthropicKey = '', slackTokens = {}) {
|
|
5
|
+
const configDir = path.join(projectDir, 'config', 'log-configs');
|
|
6
|
+
const repoDir = path.join(projectDir, 'config', 'repo-configs');
|
|
7
|
+
fs.ensureDirSync(configDir);
|
|
8
|
+
fs.ensureDirSync(repoDir);
|
|
9
|
+
const tplDir = path.join(__dirname, '..', 'templates');
|
|
10
|
+
let sentinelProps = fs.readFileSync(path.join(tplDir, 'sentinel.properties'), 'utf8');
|
|
11
|
+
if (anthropicKey) {
|
|
12
|
+
sentinelProps = sentinelProps.replace(/^# ANTHROPIC_API_KEY=.*/m, `ANTHROPIC_API_KEY=${anthropicKey}`);
|
|
13
|
+
}
|
|
14
|
+
if (slackTokens.botToken) {
|
|
15
|
+
sentinelProps = sentinelProps.replace(/^# SLACK_BOT_TOKEN=.*/m, `SLACK_BOT_TOKEN=${slackTokens.botToken}`);
|
|
16
|
+
}
|
|
17
|
+
if (slackTokens.appToken) {
|
|
18
|
+
sentinelProps = sentinelProps.replace(/^# SLACK_APP_TOKEN=.*/m, `SLACK_APP_TOKEN=${slackTokens.appToken}`);
|
|
19
|
+
}
|
|
20
|
+
fs.writeFileSync(path.join(projectDir, 'config', 'sentinel.properties'), sentinelProps);
|
|
21
|
+
fs.copySync(path.join(tplDir, 'log-configs', '_example.properties'), path.join(configDir, '_example.properties'));
|
|
22
|
+
fs.copySync(path.join(tplDir, 'repo-configs', '_example.properties'), path.join(repoDir, '_example.properties'));
|
|
23
|
+
generateProjectScripts(projectDir, codeDir, pythonBin);
|
|
24
|
+
}
|
|
25
|
+
function generateProjectScripts(projectDir, codeDir, pythonBin) {
|
|
26
|
+
const name = path.basename(projectDir);
|
|
27
|
+
fs.writeFileSync(path.join(projectDir, 'start.sh'), `#!/usr/bin/env bash
|
|
28
|
+
# Start this Sentinel instance
|
|
29
|
+
set -euo pipefail
|
|
30
|
+
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
31
|
+
PID_FILE="$DIR/sentinel.pid"
|
|
32
|
+
if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
|
|
33
|
+
echo "[sentinel] ${name} already running (PID $(cat "$PID_FILE"))"
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
# Kill any orphaned sentinel processes for this project (stale PIDs not in PID file)
|
|
37
|
+
pkill -f "sentinel.main --config ${DIR}/config" 2>/dev/null || true
|
|
38
|
+
rm -f "$PID_FILE"
|
|
39
|
+
# Check Claude Code authentication
|
|
40
|
+
AUTH_OUT=$(claude --print \"hi\" 2>&1 || true)
|
|
41
|
+
if echo "$AUTH_OUT" | grep -Eqi "not logged in|/login"; then
|
|
42
|
+
echo ""
|
|
43
|
+
echo "[sentinel] Claude Code is not authenticated."
|
|
44
|
+
echo " 1. Open a new terminal and run: claude"
|
|
45
|
+
echo " 2. Type /login at the prompt"
|
|
46
|
+
echo " 3. Open the URL in any browser and log in"
|
|
47
|
+
echo " 4. Type /exit when done"
|
|
48
|
+
echo " 5. Re-run this script"
|
|
49
|
+
echo ""
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
mkdir -p "$DIR/logs" "$DIR/workspace/fetched" "$DIR/workspace/patches" "$DIR/issues"
|
|
53
|
+
cd "$DIR"
|
|
54
|
+
PYTHONPATH="${codeDir}" "${pythonBin}" -m sentinel.main --config ./config \\
|
|
55
|
+
>> "$DIR/logs/sentinel.log" 2>&1 &
|
|
56
|
+
echo $! > "$PID_FILE"
|
|
57
|
+
echo "[sentinel] ${name} started (PID $!)"
|
|
58
|
+
`, { mode: 0o755 });
|
|
59
|
+
fs.writeFileSync(path.join(projectDir, 'stop.sh'), `#!/usr/bin/env bash
|
|
60
|
+
# Stop this Sentinel instance
|
|
61
|
+
set -euo pipefail
|
|
62
|
+
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
63
|
+
PID_FILE="$DIR/sentinel.pid"
|
|
64
|
+
if [[ ! -f "$PID_FILE" ]]; then
|
|
65
|
+
echo "[sentinel] ${name} — no PID file, not running"
|
|
66
|
+
exit 0
|
|
67
|
+
fi
|
|
68
|
+
PID=$(cat "$PID_FILE")
|
|
69
|
+
if kill -0 "$PID" 2>/dev/null; then
|
|
70
|
+
kill "$PID"
|
|
71
|
+
echo "[sentinel] ${name} stopped (PID $PID)"
|
|
72
|
+
else
|
|
73
|
+
echo "[sentinel] ${name} — PID $PID not running"
|
|
74
|
+
fi
|
|
75
|
+
rm -f "$PID_FILE"
|
|
76
|
+
`, { mode: 0o755 });
|
|
77
|
+
}
|
|
78
|
+
function generateWorkspaceScripts(workspace, smtpConfig = {}, slackConfig = {}) {
|
|
79
|
+
const workspaceProps = path.join(workspace, 'sentinel.properties');
|
|
80
|
+
if (!fs.existsSync(workspaceProps)) {
|
|
81
|
+
const tplDir = path.join(__dirname, '..', 'templates');
|
|
82
|
+
let tpl = fs.readFileSync(path.join(tplDir, 'workspace-sentinel.properties'), 'utf8');
|
|
83
|
+
if (smtpConfig.host) tpl = tpl.replace('SMTP_HOST=smtp.gmail.com', 'SMTP_HOST=' + smtpConfig.host);
|
|
84
|
+
if (smtpConfig.port) tpl = tpl.replace('SMTP_PORT=587', 'SMTP_PORT=' + smtpConfig.port);
|
|
85
|
+
if (smtpConfig.user) tpl = tpl.replace('SMTP_USER=sentinel@yourdomain.com', 'SMTP_USER=' + smtpConfig.user);
|
|
86
|
+
if (smtpConfig.password) tpl = tpl.replace('SMTP_PASSWORD=<app-password>', 'SMTP_PASSWORD=' + smtpConfig.password);
|
|
87
|
+
fs.writeFileSync(workspaceProps, tpl);
|
|
88
|
+
}
|
|
89
|
+
if (slackConfig.botToken || slackConfig.appToken) {
|
|
90
|
+
let props = fs.readFileSync(workspaceProps, 'utf8');
|
|
91
|
+
if (slackConfig.botToken) {
|
|
92
|
+
const replaced = props.replace(/^#?\s*SLACK_BOT_TOKEN=.*/m, 'SLACK_BOT_TOKEN=' + slackConfig.botToken);
|
|
93
|
+
props = replaced !== props ? replaced : props.trimEnd() + '\nSLACK_BOT_TOKEN=' + slackConfig.botToken + '\n';
|
|
94
|
+
}
|
|
95
|
+
if (slackConfig.appToken) {
|
|
96
|
+
const replaced = props.replace(/^#?\s*SLACK_APP_TOKEN=.*/m, 'SLACK_APP_TOKEN=' + slackConfig.appToken);
|
|
97
|
+
props = replaced !== props ? replaced : props.trimEnd() + '\nSLACK_APP_TOKEN=' + slackConfig.appToken + '\n';
|
|
98
|
+
}
|
|
99
|
+
fs.writeFileSync(workspaceProps, props);
|
|
100
|
+
}
|
|
101
|
+
fs.writeFileSync(path.join(workspace, 'startAll.sh'), `#!/usr/bin/env bash
|
|
102
|
+
# Start all valid Sentinel project instances.
|
|
103
|
+
# A valid project must have config/repo-configs; do
|
|
104
|
+
name=$(basename "$project_dir")
|
|
105
|
+
[[ "$name" == "code" ]] && continue
|
|
106
|
+
# Auto-generate start.sh / stop.sh if missing (codeDir = $WORKSPACE/code)
|
|
107
|
+
if [[ ! -f "$project_dir/start.sh" ]]; then
|
|
108
|
+
code_dir="$WORKSPACE/code"
|
|
109
|
+
python_bin="$code_dir/.venv/bin/python3"
|
|
110
|
+
sed -e "s|__NAME__|$name|g" -e "s|__CODE_DIR__|$code_dir|g" -e "s|__PYTHON_BIN__|$python_bin|g" << 'STARTSH' > "$project_dir/start.sh"
|
|
111
|
+
#!/usr/bin/env bash
|
|
112
|
+
set -euo pipefail
|
|
113
|
+
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
114
|
+
PID_FILE="$DIR/sentinel.pid"
|
|
115
|
+
if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
|
|
116
|
+
echo "[sentinel] __NAME__ already running (PID $(cat "$PID_FILE"))"
|
|
117
|
+
exit 0
|
|
118
|
+
fi
|
|
119
|
+
pkill -f "sentinel.main --config ${DIR}/config" 2>/dev/null || true
|
|
120
|
+
rm -f "$PID_FILE"
|
|
121
|
+
AUTH_OUT=$(claude --print \"hi\" 2>&1 || true)
|
|
122
|
+
if echo "$AUTH_OUT" | grep -Eqi "not logged in|/login"; then
|
|
123
|
+
echo "[sentinel] Claude Code is not authenticated. Run: claude then /login"
|
|
124
|
+
exit 1
|
|
125
|
+
fi
|
|
126
|
+
mkdir -p "$DIR/logs" "$DIR/workspace/fetched" "$DIR/workspace/patches" "$DIR/issues"
|
|
127
|
+
cd "$DIR"
|
|
128
|
+
PYTHONPATH="__CODE_DIR__" "__PYTHON_BIN__" -m sentinel.main --config ./config \
|
|
129
|
+
>> "$DIR/logs/sentinel.log" 2>&1 &
|
|
130
|
+
echo $! > "$PID_FILE"
|
|
131
|
+
echo "[sentinel] __NAME__ started (PID $!)"
|
|
132
|
+
STARTSH
|
|
133
|
+
chmod +x "$project_dir/start.sh"
|
|
134
|
+
echo "[sentinel] Auto-generated start.sh for $name"
|
|
135
|
+
fi
|
|
136
|
+
if [[ ! -f "$project_dir/stop.sh" ]]; then
|
|
137
|
+
sed -e "s|__NAME__|$name|g" << 'STOPSH' > "$project_dir/stop.sh"
|
|
138
|
+
#!/usr/bin/env bash
|
|
139
|
+
set -euo pipefail
|
|
140
|
+
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
141
|
+
PID_FILE="$DIR/sentinel.pid"
|
|
142
|
+
if [[ ! -f "$PID_FILE" ]]; then
|
|
143
|
+
echo "[sentinel] __NAME__ — no PID file, not running"
|
|
144
|
+
exit 0
|
|
145
|
+
fi
|
|
146
|
+
PID=$(cat "$PID_FILE")
|
|
147
|
+
if kill -0 "$PID" 2>/dev/null; then
|
|
148
|
+
kill "$PID"
|
|
149
|
+
echo "[sentinel] __NAME__ stopped (PID $PID)"
|
|
150
|
+
else
|
|
151
|
+
echo "[sentinel] __NAME__ — PID $PID not running"
|
|
152
|
+
fi
|
|
153
|
+
rm -f "$PID_FILE"
|
|
154
|
+
STOPSH
|
|
155
|
+
chmod +x "$project_dir/stop.sh"
|
|
156
|
+
echo "[sentinel] Auto-generated stop.sh for $name"
|
|
157
|
+
fi
|
|
158
|
+
# Must have at least one repo-config with a valid GitHub REPO_URL
|
|
159
|
+
repo_configs_dir="$project_dir/config/repo-configs"
|
|
160
|
+
if [[ ! -d "$repo_configs_dir" ]]; then
|
|
161
|
+
echo "[sentinel] Skipping $name — config/repo-configs/ directory not found"
|
|
162
|
+
skipped=$((skipped + 1))
|
|
163
|
+
continue
|
|
164
|
+
fi
|
|
165
|
+
has_config=false
|
|
166
|
+
valid_repo=false
|
|
167
|
+
for props in "$repo_configs_dir/"*.properties; do
|
|
168
|
+
[[ -f "$props" ]] || continue
|
|
169
|
+
[[ "$(basename "$props")" == _* ]] && continue
|
|
170
|
+
has_config=true
|
|
171
|
+
if grep -qE "^REPO_URL[[:space:]]*=[[:space:]]*(git@github\.com:|https://github\.com/)" "$props"; then
|
|
172
|
+
valid_repo=true
|
|
173
|
+
break
|
|
174
|
+
else
|
|
175
|
+
repo_url=$(grep -E "^REPO_URL[[:space:]]*=" "$props" | head -1 | cut -d= -f2- | xargs 2>/dev/null || true)
|
|
176
|
+
if [[ -z "$repo_url" ]]; then
|
|
177
|
+
echo "[sentinel] Skipping $name — REPO_URL not set in $(basename \"$props\")"
|
|
178
|
+
else
|
|
179
|
+
echo "[sentinel] Skipping $name — REPO_URL in $(basename \"$props\") is not a GitHub URL: $repo_url"
|
|
180
|
+
fi
|
|
181
|
+
fi
|
|
182
|
+
done
|
|
183
|
+
if [[ "$has_config" == "false" ]]; then
|
|
184
|
+
echo "[sentinel] Skipping $name — no .properties files in config/repo-configs/ (only _example?)"
|
|
185
|
+
skipped=$((skipped + 1))
|
|
186
|
+
continue
|
|
187
|
+
fi
|
|
188
|
+
if [[ "$valid_repo" == "false" ]]; then
|
|
189
|
+
skipped=$((skipped + 1))
|
|
190
|
+
continue
|
|
191
|
+
fi
|
|
192
|
+
bash "$project_dir/start.sh"
|
|
193
|
+
started=$((started + 1))
|
|
194
|
+
done
|
|
195
|
+
echo "[sentinel] $started project(s) started, $skipped skipped"
|
|
196
|
+
`, { mode: 0o755 });
|
|
197
|
+
// stopAll.sh
|
|
198
|
+
fs.writeFileSync(path.join(workspace, 'stopAll.sh'), `#!/usr/bin/env bash
|
|
199
|
+
# Stop all Sentinel project instances
|
|
200
|
+
WORKSPACE="$(cd "$(dirname "$0")" && pwd)"
|
|
201
|
+
stopped=0
|
|
202
|
+
for project_dir in "$WORKSPACE"/*/; do
|
|
203
|
+
name=$(basename "$project_dir")
|
|
204
|
+
[[ "$name" == "code" ]] && continue
|
|
205
|
+
[[ -f "$project_dir/stop.sh" ]] || continue
|
|
206
|
+
bash "$project_dir/stop.sh"
|
|
207
|
+
stopped=$((stopped + 1))
|
|
208
|
+
done
|
|
209
|
+
echo "[sentinel] $stopped project(s) stopped"
|
|
210
|
+
`, { mode: 0o755 });
|
|
211
|
+
}
|
|
212
|
+
module.exports = { writeExampleProject, generateProjectScripts, generateWorkspaceScripts };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const [,, command = 'help', ...args] = process.argv;
|
|
5
|
+
if (command === '--version' || command === '-v') {
|
|
6
|
+
const { version } = require('../package.json');
|
|
7
|
+
console.log(version);
|
|
8
|
+
process.exit(0);
|
|
9
|
+
}
|
|
10
|
+
if (command === '--help' || command === '-h') {
|
|
11
|
+
printUsage();
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
const BANNER = `
|
|
15
|
+
${chalk.cyan('███████╗███████╗███╗ ██╗████████╗██╗███╗ ██╗███████╗██╗')}
|
|
16
|
+
${chalk.cyan('██╔════╝██╔════╝████╗ ██║╚══██╔══╝██║████╗ ██║██╔════╝██║')}
|
|
17
|
+
${chalk.cyan('███████╗█████╗ ██╔██╗ ██║ ██║ ██║██╔██╗ ██║█████╗ ██║')}
|
|
18
|
+
${chalk.cyan('╚════██║██╔══╝ ██║╚██╗██║ ██║ ██║██║╚██╗██║██╔══╝ ██║')}
|
|
19
|
+
${chalk.cyan('███████║███████╗██║ ╚████║ ██║ ██║██║ ╚████║███████╗███████╗')}
|
|
20
|
+
${chalk.cyan('╚══════╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝')}
|
|
21
|
+
${chalk.gray(' Autonomous DevOps Agent')}
|
|
22
|
+
`;
|
|
23
|
+
async function main() {
|
|
24
|
+
console.log(BANNER);
|
|
25
|
+
switch (command) {
|
|
26
|
+
case 'init':
|
|
27
|
+
await require('../lib/init')();
|
|
28
|
+
break;
|
|
29
|
+
case 'add':
|
|
30
|
+
await require('../lib/add')(args[0]);
|
|
31
|
+
break;
|
|
32
|
+
case 'upgrade':
|
|
33
|
+
await require('../lib/upgrade')();
|
|
34
|
+
break;
|
|
35
|
+
case 'help':
|
|
36
|
+
default:
|
|
37
|
+
printUsage();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function printUsage() {
|
|
41
|
+
const { version } = require('../package.json');
|
|
42
|
+
console.log(`${chalk.bold('sentinel')} v${version} — Autonomous DevOps Agent
|
|
43
|
+
${chalk.bold('Usage:')}
|
|
44
|
+
sentinel init Interactive setup — install everything and create workspace
|
|
45
|
+
sentinel add <name> Add a blank project (fill config manually)
|
|
46
|
+
sentinel add <git-url> Add a project pre-configured for a GitHub repo
|
|
47
|
+
sentinel add <project.json> Add a project from a local JSON config file
|
|
48
|
+
sentinel add <https://host/cfg.json> Add a project from a remote JSON config URL
|
|
49
|
+
sentinel upgrade Pull latest version and hot-deploy Python source
|
|
50
|
+
${chalk.bold('Options:')}
|
|
51
|
+
--version, -v Print version
|
|
52
|
+
--help, -h Print this help
|
|
53
|
+
`);
|
|
54
|
+
}
|
|
55
|
+
main().catch(err => {
|
|
56
|
+
console.error(chalk.red('Error:'), err.message);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
});
|
package/lib/generate.js
CHANGED
|
@@ -46,7 +46,7 @@ if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
|
|
|
46
46
|
fi
|
|
47
47
|
|
|
48
48
|
# Kill any orphaned sentinel processes for this project (stale PIDs not in PID file)
|
|
49
|
-
pkill -f "sentinel.main --config $
|
|
49
|
+
pkill -f "sentinel.main --config $DIR/config" 2>/dev/null || true
|
|
50
50
|
rm -f "$PID_FILE"
|
|
51
51
|
|
|
52
52
|
# Check Claude Code authentication
|
|
@@ -144,7 +144,7 @@ if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
|
|
|
144
144
|
echo "[sentinel] __NAME__ already running (PID $(cat "$PID_FILE"))"
|
|
145
145
|
exit 0
|
|
146
146
|
fi
|
|
147
|
-
pkill -f "sentinel.main --config $
|
|
147
|
+
pkill -f "sentinel.main --config $DIR/config" 2>/dev/null || true
|
|
148
148
|
rm -f "$PID_FILE"
|
|
149
149
|
AUTH_OUT=$(claude --print \"hi\" 2>&1 || true)
|
|
150
150
|
if echo "$AUTH_OUT" | grep -Eqi "not logged in|/login"; then
|