@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.
Files changed (3) hide show
  1. package/lib/generate.js +141 -127
  2. package/lib/init.js +2 -15
  3. 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
- mkdir -p "$DIR/logs" "$DIR/workspace/fetched" "$DIR/workspace/patches"
63
- cd "$DIR"
64
- PYTHONPATH="${codeDir}" "${pythonBin}" -m sentinel.main --config ./config \\
65
- >> "$DIR/logs/sentinel.log" 2>&1 &
66
- echo $! > "$PID_FILE"
67
- echo "[sentinel] ${name} started (PID $!)"
68
- `, { mode: 0o755 });
69
-
70
- // stop.sh
71
- fs.writeFileSync(path.join(projectDir, 'stop.sh'), `#!/usr/bin/env bash
72
- # Stop this Sentinel instance
73
- set -euo pipefail
74
- DIR="$(cd "$(dirname "$0")" && pwd)"
75
- PID_FILE="$DIR/sentinel.pid"
76
-
77
- if [[ ! -f "$PID_FILE" ]]; then
78
- echo "[sentinel] ${name} no PID file, not running"
79
- exit 0
80
- fi
81
-
82
- PID=$(cat "$PID_FILE")
83
- if kill -0 "$PID" 2>/dev/null; then
84
- kill "$PID"
85
- echo "[sentinel] ${name} stopped (PID $PID)"
86
- else
87
- echo "[sentinel] ${name} — PID $PID not running"
88
- fi
89
- rm -f "$PID_FILE"
90
- `, { mode: 0o755 });
91
- }
92
-
93
- // ── Workspace-level startAll / stopAll ────────────────────────────────────────
94
-
95
- function generateWorkspaceScripts(workspace) {
96
- // startAll.sh
97
- fs.writeFileSync(path.join(workspace, 'startAll.sh'), `#!/usr/bin/env bash
98
- # Start all Sentinel project instances
99
- WORKSPACE="$(cd "$(dirname "$0")" && pwd)"
100
- started=0
101
- for project_dir in "$WORKSPACE"/*/; do
102
- name=$(basename "$project_dir")
103
- [[ "$name" == "code" ]] && continue
104
- [[ -f "$project_dir/start.sh" ]] || continue
105
- bash "$project_dir/start.sh"
106
- started=$((started + 1))
107
- done
108
- echo "[sentinel] $started project(s) started"
109
- `, { mode: 0o755 });
110
-
111
- // stopAll.sh
112
- fs.writeFileSync(path.join(workspace, 'stopAll.sh'), `#!/usr/bin/env bash
113
- # Stop all Sentinel project instances
114
- WORKSPACE="$(cd "$(dirname "$0")" && pwd)"
115
- stopped=0
116
- for project_dir in "$WORKSPACE"/*/; do
117
- name=$(basename "$project_dir")
118
- [[ "$name" == "code" ]] && continue
119
- [[ -f "$project_dir/stop.sh" ]] || continue
120
- bash "$project_dir/stop.sh"
121
- stopped=$((stopped + 1))
122
- done
123
- echo "[sentinel] $stopped project(s) stopped"
124
- `, { mode: 0o755 });
125
- }
126
-
127
- module.exports = { writeExampleProject, generateProjectScripts, generateWorkspaceScripts };
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
- console.log(chalk.yellow(
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
- warn('Skipping auth — set ANTHROPIC_API_KEY in sentinel.properties or run "claude" to authenticate');
106
+ info('Skipping auth — start.sh will prompt for login if needed');
120
107
  }
121
108
 
122
109
  // ── Workspace structure ─────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misterhuydo/sentinel",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Sentinel — Autonomous DevOps Agent installer and manager",
5
5
  "bin": {
6
6
  "sentinel": "./bin/sentinel.js"