@misterhuydo/sentinel 1.0.3 → 1.0.4
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/lib/generate.js +141 -127
- package/lib/init.js +2 -15
- package/package.json +1 -1
package/lib/generate.js
CHANGED
|
@@ -1,127 +1,141 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs-extra');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
|
|
6
|
-
// ── Per-project files ─────────────────────────────────────────────────────────
|
|
7
|
-
|
|
8
|
-
function writeExampleProject(projectDir, codeDir, pythonBin, anthropicKey = '') {
|
|
9
|
-
const configDir = path.join(projectDir, 'config', 'log-configs');
|
|
10
|
-
const repoDir = path.join(projectDir, 'config', 'repo-configs');
|
|
11
|
-
fs.ensureDirSync(configDir);
|
|
12
|
-
fs.ensureDirSync(repoDir);
|
|
13
|
-
|
|
14
|
-
const tplDir = path.join(__dirname, '..', 'templates');
|
|
15
|
-
// Inject API key into sentinel.properties if provided
|
|
16
|
-
let sentinelProps = fs.readFileSync(path.join(tplDir, 'sentinel.properties'), 'utf8');
|
|
17
|
-
if (anthropicKey) {
|
|
18
|
-
sentinelProps += `\n# Anthropic API key for Claude Code (headless server auth)\nANTHROPIC_API_KEY=${anthropicKey}\n`;
|
|
19
|
-
} else {
|
|
20
|
-
sentinelProps += `\n# Anthropic API key — set this if using API key auth, or leave blank for OAuth\n# ANTHROPIC_API_KEY=sk-ant-...\n`;
|
|
21
|
-
}
|
|
22
|
-
fs.writeFileSync(path.join(projectDir, 'config', 'sentinel.properties'), sentinelProps);
|
|
23
|
-
fs.copySync(path.join(tplDir, 'log-configs', '_example.properties'), path.join(configDir, '_example.properties'));
|
|
24
|
-
fs.copySync(path.join(tplDir, 'repo-configs', '_example.properties'), path.join(repoDir, '_example.properties'));
|
|
25
|
-
|
|
26
|
-
generateProjectScripts(projectDir, codeDir, pythonBin);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function generateProjectScripts(projectDir, codeDir, pythonBin) {
|
|
30
|
-
const name = path.basename(projectDir);
|
|
31
|
-
|
|
32
|
-
// init.sh
|
|
33
|
-
fs.writeFileSync(path.join(projectDir, 'init.sh'), `#!/usr/bin/env bash
|
|
34
|
-
# First-time setup for this Sentinel project instance.
|
|
35
|
-
#
|
|
36
|
-
# What this does:
|
|
37
|
-
# - Clones any repos defined in config/repo-configs/ that don't exist locally yet
|
|
38
|
-
# (skips repos that are already cloned — safe to run multiple times)
|
|
39
|
-
# - Indexes each repo with Cairn MCP for codebase context
|
|
40
|
-
# - Tests SSH connectivity to each configured log source
|
|
41
|
-
# - Sends a test email to verify SMTP settings
|
|
42
|
-
#
|
|
43
|
-
# Note: ongoing repo management (git pull, conflict resolution) is handled
|
|
44
|
-
# automatically by Sentinel on each fix cycle — you don't need to do it manually.
|
|
45
|
-
set -euo pipefail
|
|
46
|
-
cd "$(dirname "$0")"
|
|
47
|
-
PYTHONPATH="${codeDir}" "${pythonBin}" -m sentinel.main --config ./config --init
|
|
48
|
-
`, { mode: 0o755 });
|
|
49
|
-
|
|
50
|
-
// start.sh
|
|
51
|
-
fs.writeFileSync(path.join(projectDir, 'start.sh'), `#!/usr/bin/env bash
|
|
52
|
-
# Start this Sentinel instance
|
|
53
|
-
set -euo pipefail
|
|
54
|
-
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
55
|
-
PID_FILE="$DIR/sentinel.pid"
|
|
56
|
-
|
|
57
|
-
if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
|
|
58
|
-
echo "[sentinel] ${name} already running (PID $(cat "$PID_FILE"))"
|
|
59
|
-
exit 0
|
|
60
|
-
fi
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
echo
|
|
67
|
-
echo "
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
[[ "$
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// ── Per-project files ─────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
function writeExampleProject(projectDir, codeDir, pythonBin, anthropicKey = '') {
|
|
9
|
+
const configDir = path.join(projectDir, 'config', 'log-configs');
|
|
10
|
+
const repoDir = path.join(projectDir, 'config', 'repo-configs');
|
|
11
|
+
fs.ensureDirSync(configDir);
|
|
12
|
+
fs.ensureDirSync(repoDir);
|
|
13
|
+
|
|
14
|
+
const tplDir = path.join(__dirname, '..', 'templates');
|
|
15
|
+
// Inject API key into sentinel.properties if provided
|
|
16
|
+
let sentinelProps = fs.readFileSync(path.join(tplDir, 'sentinel.properties'), 'utf8');
|
|
17
|
+
if (anthropicKey) {
|
|
18
|
+
sentinelProps += `\n# Anthropic API key for Claude Code (headless server auth)\nANTHROPIC_API_KEY=${anthropicKey}\n`;
|
|
19
|
+
} else {
|
|
20
|
+
sentinelProps += `\n# Anthropic API key — set this if using API key auth, or leave blank for OAuth\n# ANTHROPIC_API_KEY=sk-ant-...\n`;
|
|
21
|
+
}
|
|
22
|
+
fs.writeFileSync(path.join(projectDir, 'config', 'sentinel.properties'), sentinelProps);
|
|
23
|
+
fs.copySync(path.join(tplDir, 'log-configs', '_example.properties'), path.join(configDir, '_example.properties'));
|
|
24
|
+
fs.copySync(path.join(tplDir, 'repo-configs', '_example.properties'), path.join(repoDir, '_example.properties'));
|
|
25
|
+
|
|
26
|
+
generateProjectScripts(projectDir, codeDir, pythonBin);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function generateProjectScripts(projectDir, codeDir, pythonBin) {
|
|
30
|
+
const name = path.basename(projectDir);
|
|
31
|
+
|
|
32
|
+
// init.sh
|
|
33
|
+
fs.writeFileSync(path.join(projectDir, 'init.sh'), `#!/usr/bin/env bash
|
|
34
|
+
# First-time setup for this Sentinel project instance.
|
|
35
|
+
#
|
|
36
|
+
# What this does:
|
|
37
|
+
# - Clones any repos defined in config/repo-configs/ that don't exist locally yet
|
|
38
|
+
# (skips repos that are already cloned — safe to run multiple times)
|
|
39
|
+
# - Indexes each repo with Cairn MCP for codebase context
|
|
40
|
+
# - Tests SSH connectivity to each configured log source
|
|
41
|
+
# - Sends a test email to verify SMTP settings
|
|
42
|
+
#
|
|
43
|
+
# Note: ongoing repo management (git pull, conflict resolution) is handled
|
|
44
|
+
# automatically by Sentinel on each fix cycle — you don't need to do it manually.
|
|
45
|
+
set -euo pipefail
|
|
46
|
+
cd "$(dirname "$0")"
|
|
47
|
+
PYTHONPATH="${codeDir}" "${pythonBin}" -m sentinel.main --config ./config --init
|
|
48
|
+
`, { mode: 0o755 });
|
|
49
|
+
|
|
50
|
+
// start.sh
|
|
51
|
+
fs.writeFileSync(path.join(projectDir, 'start.sh'), `#!/usr/bin/env bash
|
|
52
|
+
# Start this Sentinel instance
|
|
53
|
+
set -euo pipefail
|
|
54
|
+
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
55
|
+
PID_FILE="$DIR/sentinel.pid"
|
|
56
|
+
|
|
57
|
+
if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
|
|
58
|
+
echo "[sentinel] ${name} already running (PID $(cat "$PID_FILE"))"
|
|
59
|
+
exit 0
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Check Claude Code authentication
|
|
63
|
+
AUTH_OUT=$(claude --print \"hi\" 2>&1 || true)
|
|
64
|
+
if echo "$AUTH_OUT" | grep -Eqi "not logged in|/login"; then
|
|
65
|
+
echo ""
|
|
66
|
+
echo "[sentinel] Claude Code is not authenticated."
|
|
67
|
+
echo " 1. Open a new terminal and run: claude"
|
|
68
|
+
echo " 2. Type /login at the prompt"
|
|
69
|
+
echo " 3. Open the URL in any browser and log in"
|
|
70
|
+
echo " 4. Type /exit when done"
|
|
71
|
+
echo " 5. Re-run this script"
|
|
72
|
+
echo ""
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
mkdir -p "$DIR/logs" "$DIR/workspace/fetched" "$DIR/workspace/patches"
|
|
77
|
+
cd "$DIR"
|
|
78
|
+
PYTHONPATH="${codeDir}" "${pythonBin}" -m sentinel.main --config ./config \\
|
|
79
|
+
>> "$DIR/logs/sentinel.log" 2>&1 &
|
|
80
|
+
echo $! > "$PID_FILE"
|
|
81
|
+
echo "[sentinel] ${name} started (PID $!)"
|
|
82
|
+
`, { mode: 0o755 });
|
|
83
|
+
|
|
84
|
+
// stop.sh
|
|
85
|
+
fs.writeFileSync(path.join(projectDir, 'stop.sh'), `#!/usr/bin/env bash
|
|
86
|
+
# Stop this Sentinel instance
|
|
87
|
+
set -euo pipefail
|
|
88
|
+
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
89
|
+
PID_FILE="$DIR/sentinel.pid"
|
|
90
|
+
|
|
91
|
+
if [[ ! -f "$PID_FILE" ]]; then
|
|
92
|
+
echo "[sentinel] ${name} — no PID file, not running"
|
|
93
|
+
exit 0
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
PID=$(cat "$PID_FILE")
|
|
97
|
+
if kill -0 "$PID" 2>/dev/null; then
|
|
98
|
+
kill "$PID"
|
|
99
|
+
echo "[sentinel] ${name} stopped (PID $PID)"
|
|
100
|
+
else
|
|
101
|
+
echo "[sentinel] ${name} — PID $PID not running"
|
|
102
|
+
fi
|
|
103
|
+
rm -f "$PID_FILE"
|
|
104
|
+
`, { mode: 0o755 });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── Workspace-level startAll / stopAll ────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
function generateWorkspaceScripts(workspace) {
|
|
110
|
+
// startAll.sh
|
|
111
|
+
fs.writeFileSync(path.join(workspace, 'startAll.sh'), `#!/usr/bin/env bash
|
|
112
|
+
# Start all Sentinel project instances
|
|
113
|
+
WORKSPACE="$(cd "$(dirname "$0")" && pwd)"
|
|
114
|
+
started=0
|
|
115
|
+
for project_dir in "$WORKSPACE"/*/; do
|
|
116
|
+
name=$(basename "$project_dir")
|
|
117
|
+
[[ "$name" == "code" ]] && continue
|
|
118
|
+
[[ -f "$project_dir/start.sh" ]] || continue
|
|
119
|
+
bash "$project_dir/start.sh"
|
|
120
|
+
started=$((started + 1))
|
|
121
|
+
done
|
|
122
|
+
echo "[sentinel] $started project(s) started"
|
|
123
|
+
`, { mode: 0o755 });
|
|
124
|
+
|
|
125
|
+
// stopAll.sh
|
|
126
|
+
fs.writeFileSync(path.join(workspace, 'stopAll.sh'), `#!/usr/bin/env bash
|
|
127
|
+
# Stop all Sentinel project instances
|
|
128
|
+
WORKSPACE="$(cd "$(dirname "$0")" && pwd)"
|
|
129
|
+
stopped=0
|
|
130
|
+
for project_dir in "$WORKSPACE"/*/; do
|
|
131
|
+
name=$(basename "$project_dir")
|
|
132
|
+
[[ "$name" == "code" ]] && continue
|
|
133
|
+
[[ -f "$project_dir/stop.sh" ]] || continue
|
|
134
|
+
bash "$project_dir/stop.sh"
|
|
135
|
+
stopped=$((stopped + 1))
|
|
136
|
+
done
|
|
137
|
+
echo "[sentinel] $stopped project(s) stopped"
|
|
138
|
+
`, { mode: 0o755 });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = { writeExampleProject, generateProjectScripts, generateWorkspaceScripts };
|
package/lib/init.js
CHANGED
|
@@ -101,22 +101,9 @@ module.exports = async function init() {
|
|
|
101
101
|
if (authMode === 'apikey' && anthropicKey) {
|
|
102
102
|
ok('API key will be written to each project\'s sentinel.properties');
|
|
103
103
|
} else if (authMode === 'oauth') {
|
|
104
|
-
|
|
105
|
-
'\n Launching Claude Code login — type /login at the prompt to get your auth URL.\n' +
|
|
106
|
-
' Open the URL in any browser, log in with your Claude Pro account,\n' +
|
|
107
|
-
' then type /exit (or Ctrl-C) to return here.\n'
|
|
108
|
-
));
|
|
109
|
-
spawnSync('claude', [], { stdio: 'inherit' });
|
|
110
|
-
// Verify auth succeeded after the user exits claude
|
|
111
|
-
const authCheck = spawnSync('claude', ['--print', 'hi'], { encoding: 'utf8', timeout: 15000 });
|
|
112
|
-
const authOut = (authCheck.stdout || '') + (authCheck.stderr || '');
|
|
113
|
-
if (authOut.toLowerCase().includes('not logged in') || authOut.toLowerCase().includes('/login')) {
|
|
114
|
-
warn('Not authenticated yet — run "claude" and type /login when ready');
|
|
115
|
-
} else {
|
|
116
|
-
ok('Claude Code authenticated via OAuth');
|
|
117
|
-
}
|
|
104
|
+
info('OAuth selected — start.sh will prompt for login if not yet authenticated');
|
|
118
105
|
} else {
|
|
119
|
-
|
|
106
|
+
info('Skipping auth — start.sh will prompt for login if needed');
|
|
120
107
|
}
|
|
121
108
|
|
|
122
109
|
// ── Workspace structure ─────────────────────────────────────────────────────
|