@geekbeer/minion 2.25.0 → 2.32.0
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/core/lib/platform.js +117 -0
- package/{routes → core/routes}/health.js +1 -1
- package/{routes → core/routes}/routines.js +3 -3
- package/{routes → core/routes}/skills.js +3 -3
- package/{routes → core/routes}/workflows.js +4 -4
- package/{chat-store.js → core/stores/chat-store.js} +1 -1
- package/{execution-store.js → core/stores/execution-store.js} +1 -1
- package/{routine-store.js → core/stores/routine-store.js} +1 -1
- package/{workflow-store.js → core/stores/workflow-store.js} +1 -1
- package/{minion-cli.sh → linux/minion-cli.sh} +63 -6
- package/{routes → linux/routes}/chat.js +3 -3
- package/{routes → linux/routes}/commands.js +1 -1
- package/{routes → linux/routes}/config.js +3 -3
- package/{routes → linux/routes}/directives.js +5 -5
- package/{routes → linux/routes}/files.js +2 -2
- package/{routes → linux/routes}/terminal.js +2 -2
- package/{routine-runner.js → linux/routine-runner.js} +4 -4
- package/{server.js → linux/server.js} +71 -36
- package/{workflow-runner.js → linux/workflow-runner.js} +4 -4
- package/package.json +16 -20
- package/win/bin/hq-win.js +18 -0
- package/win/bin/hq.ps1 +108 -0
- package/win/bin/minion-cli-win.js +20 -0
- package/win/lib/llm-checker.js +115 -0
- package/win/lib/log-manager.js +119 -0
- package/win/lib/process-manager.js +112 -0
- package/win/minion-cli.ps1 +869 -0
- package/win/routes/chat.js +280 -0
- package/win/routes/commands.js +101 -0
- package/win/routes/config.js +227 -0
- package/win/routes/directives.js +136 -0
- package/win/routes/files.js +283 -0
- package/win/routes/terminal.js +316 -0
- package/win/routine-runner.js +324 -0
- package/win/server.js +230 -0
- package/win/terminal-server.js +234 -0
- package/win/workflow-runner.js +380 -0
- package/routes/index.js +0 -106
- /package/{api.js → core/api.js} +0 -0
- /package/{config.js → core/config.js} +0 -0
- /package/{lib → core/lib}/auth.js +0 -0
- /package/{lib → core/lib}/llm-checker.js +0 -0
- /package/{lib → core/lib}/log-manager.js +0 -0
- /package/{routes → core/routes}/auth.js +0 -0
- /package/{bin → linux/bin}/hq +0 -0
- /package/{lib → linux/lib}/process-manager.js +0 -0
- /package/{terminal-proxy.js → linux/terminal-proxy.js} +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform utility module
|
|
3
|
+
*
|
|
4
|
+
* Provides platform-aware paths, separators, and helpers.
|
|
5
|
+
* Used by win/ modules to avoid hardcoded Unix paths.
|
|
6
|
+
* This file does NOT modify any existing Linux module behavior.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const os = require('os')
|
|
10
|
+
const path = require('path')
|
|
11
|
+
const fs = require('fs')
|
|
12
|
+
|
|
13
|
+
const IS_WINDOWS = process.platform === 'win32'
|
|
14
|
+
const PATH_SEPARATOR = path.delimiter // ';' on Windows, ':' on Unix
|
|
15
|
+
const TEMP_DIR = os.tmpdir()
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the data directory for minion agent persistent files.
|
|
19
|
+
* Windows: %PROGRAMDATA%\minion-agent (or fallback to %USERPROFILE%\.minion-agent)
|
|
20
|
+
* Linux: /opt/minion-agent (existing behavior)
|
|
21
|
+
*/
|
|
22
|
+
function resolveDataDir() {
|
|
23
|
+
if (IS_WINDOWS) {
|
|
24
|
+
const programData = process.env.PROGRAMDATA || process.env.ALLUSERSPROFILE
|
|
25
|
+
if (programData) {
|
|
26
|
+
const dir = path.join(programData, 'minion-agent')
|
|
27
|
+
try {
|
|
28
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
29
|
+
return dir
|
|
30
|
+
} catch {
|
|
31
|
+
// Fall through to home-based path
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return path.join(os.homedir(), '.minion-agent')
|
|
35
|
+
}
|
|
36
|
+
return '/opt/minion-agent'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const DATA_DIR = resolveDataDir()
|
|
40
|
+
const LOG_DIR = path.join(DATA_DIR, 'logs')
|
|
41
|
+
const MARKER_DIR = path.join(TEMP_DIR, 'minion-executions')
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Build extended PATH including common CLI installation locations.
|
|
45
|
+
* Uses the correct platform separator.
|
|
46
|
+
* @param {string} homeDir - User home directory
|
|
47
|
+
* @returns {string} Extended PATH string
|
|
48
|
+
*/
|
|
49
|
+
function buildExtendedPath(homeDir) {
|
|
50
|
+
const additionalPaths = IS_WINDOWS
|
|
51
|
+
? [
|
|
52
|
+
path.join(homeDir, '.local', 'bin'),
|
|
53
|
+
path.join(homeDir, '.npm-global'),
|
|
54
|
+
path.join(homeDir, '.claude', 'bin'),
|
|
55
|
+
path.join(homeDir, 'AppData', 'Roaming', 'npm'),
|
|
56
|
+
path.join(process.env.PROGRAMFILES || 'C:\\Program Files', 'nodejs'),
|
|
57
|
+
]
|
|
58
|
+
: [
|
|
59
|
+
path.join(homeDir, 'bin'),
|
|
60
|
+
path.join(homeDir, '.npm-global', 'bin'),
|
|
61
|
+
path.join(homeDir, '.local', 'bin'),
|
|
62
|
+
path.join(homeDir, '.claude', 'bin'),
|
|
63
|
+
'/usr/local/bin',
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
const currentPath = process.env.PATH || ''
|
|
67
|
+
return [...additionalPaths, currentPath].join(PATH_SEPARATOR)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get exit code file path for a session.
|
|
72
|
+
* @param {string} sessionName - Session identifier
|
|
73
|
+
* @returns {string} Path to exit code file
|
|
74
|
+
*/
|
|
75
|
+
function getExitCodePath(sessionName) {
|
|
76
|
+
return path.join(TEMP_DIR, `minion-exit-${sessionName}`)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get the default shell for spawning processes.
|
|
81
|
+
* @returns {string} Shell executable path
|
|
82
|
+
*/
|
|
83
|
+
function getDefaultShell() {
|
|
84
|
+
if (IS_WINDOWS) {
|
|
85
|
+
return process.env.COMSPEC || 'cmd.exe'
|
|
86
|
+
}
|
|
87
|
+
return process.env.SHELL || '/bin/sh'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Resolve .env file path.
|
|
92
|
+
* Prefers DATA_DIR/.env, falls back to ~/minion.env.
|
|
93
|
+
* @param {string} homeDir - User home directory
|
|
94
|
+
* @returns {string} Path to .env file
|
|
95
|
+
*/
|
|
96
|
+
function resolveEnvFilePath(homeDir) {
|
|
97
|
+
const dataEnv = path.join(DATA_DIR, '.env')
|
|
98
|
+
try {
|
|
99
|
+
fs.accessSync(path.dirname(dataEnv), fs.constants.W_OK)
|
|
100
|
+
return dataEnv
|
|
101
|
+
} catch {
|
|
102
|
+
return path.join(homeDir, 'minion.env')
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
IS_WINDOWS,
|
|
108
|
+
PATH_SEPARATOR,
|
|
109
|
+
TEMP_DIR,
|
|
110
|
+
DATA_DIR,
|
|
111
|
+
LOG_DIR,
|
|
112
|
+
MARKER_DIR,
|
|
113
|
+
buildExtendedPath,
|
|
114
|
+
getExitCodePath,
|
|
115
|
+
getDefaultShell,
|
|
116
|
+
resolveEnvFilePath,
|
|
117
|
+
}
|
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const { verifyToken } = require('../lib/auth')
|
|
15
|
-
const
|
|
16
|
-
const routineStore = require('../routine-store')
|
|
15
|
+
const routineStore = require('../stores/routine-store')
|
|
17
16
|
const api = require('../api')
|
|
18
17
|
const { isHqConfigured } = require('../config')
|
|
19
18
|
|
|
@@ -21,7 +20,8 @@ const { isHqConfigured } = require('../config')
|
|
|
21
20
|
* Register routine routes as Fastify plugin
|
|
22
21
|
* @param {import('fastify').FastifyInstance} fastify
|
|
23
22
|
*/
|
|
24
|
-
async function routineRoutes(fastify) {
|
|
23
|
+
async function routineRoutes(fastify, opts) {
|
|
24
|
+
const { routineRunner } = opts
|
|
25
25
|
// Receive routines from HQ (upsert: add new, update existing)
|
|
26
26
|
fastify.post('/api/routines', async (request, reply) => {
|
|
27
27
|
if (!verifyToken(request)) {
|
|
@@ -17,8 +17,7 @@ const crypto = require('crypto')
|
|
|
17
17
|
const { verifyToken } = require('../lib/auth')
|
|
18
18
|
const api = require('../api')
|
|
19
19
|
const { config, isHqConfigured } = require('../config')
|
|
20
|
-
const
|
|
21
|
-
const executionStore = require('../execution-store')
|
|
20
|
+
const executionStore = require('../stores/execution-store')
|
|
22
21
|
const logManager = require('../lib/log-manager')
|
|
23
22
|
|
|
24
23
|
/**
|
|
@@ -128,7 +127,8 @@ async function pushSkillToHQ(name) {
|
|
|
128
127
|
* Register skill routes as Fastify plugin
|
|
129
128
|
* @param {import('fastify').FastifyInstance} fastify
|
|
130
129
|
*/
|
|
131
|
-
async function skillRoutes(fastify) {
|
|
130
|
+
async function skillRoutes(fastify, opts) {
|
|
131
|
+
const { workflowRunner } = opts
|
|
132
132
|
// List deployed skills from local .claude/skills directory
|
|
133
133
|
fastify.get('/api/list-skills', async (request, reply) => {
|
|
134
134
|
if (!verifyToken(request)) {
|
|
@@ -20,9 +20,8 @@ const fs = require('fs').promises
|
|
|
20
20
|
const path = require('path')
|
|
21
21
|
|
|
22
22
|
const { verifyToken } = require('../lib/auth')
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const executionStore = require('../execution-store')
|
|
23
|
+
const workflowStore = require('../stores/workflow-store')
|
|
24
|
+
const executionStore = require('../stores/execution-store')
|
|
26
25
|
const logManager = require('../lib/log-manager')
|
|
27
26
|
const api = require('../api')
|
|
28
27
|
const { config, isHqConfigured } = require('../config')
|
|
@@ -32,7 +31,8 @@ const { writeSkillToLocal, pushSkillToHQ } = require('./skills')
|
|
|
32
31
|
* Register workflow routes as Fastify plugin
|
|
33
32
|
* @param {import('fastify').FastifyInstance} fastify
|
|
34
33
|
*/
|
|
35
|
-
async function workflowRoutes(fastify) {
|
|
34
|
+
async function workflowRoutes(fastify, opts) {
|
|
35
|
+
const { workflowRunner } = opts
|
|
36
36
|
// Receive workflows from HQ (upsert: add new, update existing)
|
|
37
37
|
fastify.post('/api/workflows', async (request, reply) => {
|
|
38
38
|
if (!verifyToken(request)) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
const fs = require('fs').promises
|
|
8
8
|
const path = require('path')
|
|
9
9
|
|
|
10
|
-
const { config } = require('
|
|
10
|
+
const { config } = require('../config')
|
|
11
11
|
|
|
12
12
|
// Routine file location: /opt/minion-agent/routines.json (systemd/supervisord)
|
|
13
13
|
// or ~/routines.json (standalone)
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
const fs = require('fs').promises
|
|
8
8
|
const path = require('path')
|
|
9
9
|
|
|
10
|
-
const { config } = require('
|
|
10
|
+
const { config } = require('../config')
|
|
11
11
|
|
|
12
12
|
// Workflow file location: /opt/minion-agent/workflows.json (systemd/supervisord)
|
|
13
13
|
// or ~/workflows.json (standalone)
|
|
@@ -41,7 +41,7 @@ fi
|
|
|
41
41
|
|
|
42
42
|
# Resolve version from package.json (installed location)
|
|
43
43
|
CLI_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
|
|
44
|
-
CLI_VERSION="$(node -p "require('${CLI_DIR}
|
|
44
|
+
CLI_VERSION="$(node -p "require('${CLI_DIR}/../package.json').version")"
|
|
45
45
|
|
|
46
46
|
# Use sudo only when not running as root
|
|
47
47
|
SUDO=""
|
|
@@ -89,6 +89,28 @@ svc_control() {
|
|
|
89
89
|
|
|
90
90
|
AGENT_URL="${MINION_AGENT_URL:-http://localhost:8080}"
|
|
91
91
|
|
|
92
|
+
# Detect LAN IPv4 address (best-effort)
|
|
93
|
+
detect_lan_ip() {
|
|
94
|
+
local ip=""
|
|
95
|
+
# Method 1: Use ip route to find the source IP for default gateway
|
|
96
|
+
if command -v ip &>/dev/null; then
|
|
97
|
+
ip=$(ip route get 1.1.1.1 2>/dev/null | grep -oP 'src \K[\d.]+' | head -1)
|
|
98
|
+
fi
|
|
99
|
+
# Method 2: Parse ip addr for non-loopback, non-docker, non-APIPA addresses
|
|
100
|
+
if [ -z "$ip" ] && command -v ip &>/dev/null; then
|
|
101
|
+
ip=$(ip -4 addr show scope global 2>/dev/null \
|
|
102
|
+
| grep -oP 'inet \K[\d.]+' \
|
|
103
|
+
| grep -v '^172\.17\.' \
|
|
104
|
+
| grep -v '^169\.254\.' \
|
|
105
|
+
| head -1)
|
|
106
|
+
fi
|
|
107
|
+
# Method 3: hostname -I (available on most Linux distros)
|
|
108
|
+
if [ -z "$ip" ] && command -v hostname &>/dev/null; then
|
|
109
|
+
ip=$(hostname -I 2>/dev/null | awk '{print $1}')
|
|
110
|
+
fi
|
|
111
|
+
echo "$ip"
|
|
112
|
+
}
|
|
113
|
+
|
|
92
114
|
# Auto-load .env so that API_TOKEN etc. are available in interactive shells
|
|
93
115
|
ENV_FILE="/opt/minion-agent/.env"
|
|
94
116
|
if [ -f "$ENV_FILE" ] && [ -r "$ENV_FILE" ]; then
|
|
@@ -319,7 +341,7 @@ do_setup() {
|
|
|
319
341
|
echo "[6/${TOTAL_STEPS}] Creating service configuration ($PROC_MGR)..."
|
|
320
342
|
local NPM_ROOT
|
|
321
343
|
NPM_ROOT="$(npm root -g)"
|
|
322
|
-
local SERVER_PATH="${NPM_ROOT}/@geekbeer/minion/server.js"
|
|
344
|
+
local SERVER_PATH="${NPM_ROOT}/@geekbeer/minion/linux/server.js"
|
|
323
345
|
|
|
324
346
|
if [ ! -f "$SERVER_PATH" ]; then
|
|
325
347
|
echo " ERROR: server.js not found at $SERVER_PATH"
|
|
@@ -732,14 +754,33 @@ CFEOF
|
|
|
732
754
|
echo "Notifying HQ of setup completion..."
|
|
733
755
|
local NOTIFY_RESPONSE
|
|
734
756
|
local HOSTNAME_VAL
|
|
757
|
+
local LAN_IP
|
|
735
758
|
HOSTNAME_VAL=$(hostname 2>/dev/null || echo "")
|
|
759
|
+
LAN_IP=$(detect_lan_ip)
|
|
760
|
+
|
|
761
|
+
# Build request body with LAN IP detection
|
|
762
|
+
# Docker: use hostname (container name) for internal_ip_address (resolved via Docker DNS)
|
|
763
|
+
# Self-hosted: use LAN IP for both fields (hostname is not resolvable on LAN)
|
|
764
|
+
local BODY
|
|
765
|
+
if [ -f /.dockerenv ]; then
|
|
766
|
+
BODY="{\"internal_ip_address\":\"${HOSTNAME_VAL}\"}"
|
|
767
|
+
elif [ -n "$LAN_IP" ]; then
|
|
768
|
+
BODY="{\"internal_ip_address\":\"${LAN_IP}\",\"ip_address\":\"${LAN_IP}\"}"
|
|
769
|
+
else
|
|
770
|
+
BODY="{\"internal_ip_address\":\"${HOSTNAME_VAL}\"}"
|
|
771
|
+
fi
|
|
772
|
+
|
|
736
773
|
NOTIFY_RESPONSE=$(curl -sfL -X POST "${HQ_URL}/api/minion/setup-complete" \
|
|
737
774
|
-H "Content-Type: application/json" \
|
|
738
775
|
-H "Authorization: Bearer ${API_TOKEN}" \
|
|
739
|
-
-d "
|
|
776
|
+
-d "$BODY" 2>&1) || true
|
|
740
777
|
|
|
741
778
|
if echo "$NOTIFY_RESPONSE" | grep -q '"success":true' 2>/dev/null; then
|
|
742
|
-
|
|
779
|
+
if [ -n "$LAN_IP" ] && [ ! -f /.dockerenv ]; then
|
|
780
|
+
echo " -> HQ notified successfully (LAN IP: ${LAN_IP})"
|
|
781
|
+
else
|
|
782
|
+
echo " -> HQ notified successfully"
|
|
783
|
+
fi
|
|
743
784
|
else
|
|
744
785
|
echo " -> HQ notification skipped (HQ may not be reachable)"
|
|
745
786
|
fi
|
|
@@ -915,14 +956,30 @@ SUPEOF
|
|
|
915
956
|
echo "[4/4] Notifying HQ..."
|
|
916
957
|
local NOTIFY_RESPONSE
|
|
917
958
|
local HOSTNAME_VAL
|
|
959
|
+
local LAN_IP
|
|
918
960
|
HOSTNAME_VAL=$(hostname 2>/dev/null || echo "")
|
|
961
|
+
LAN_IP=$(detect_lan_ip)
|
|
962
|
+
|
|
963
|
+
local BODY
|
|
964
|
+
if [ -f /.dockerenv ]; then
|
|
965
|
+
BODY="{\"internal_ip_address\":\"${HOSTNAME_VAL}\"}"
|
|
966
|
+
elif [ -n "$LAN_IP" ]; then
|
|
967
|
+
BODY="{\"internal_ip_address\":\"${LAN_IP}\",\"ip_address\":\"${LAN_IP}\"}"
|
|
968
|
+
else
|
|
969
|
+
BODY="{\"internal_ip_address\":\"${HOSTNAME_VAL}\"}"
|
|
970
|
+
fi
|
|
971
|
+
|
|
919
972
|
NOTIFY_RESPONSE=$(curl -sfL -X POST "${HQ_URL}/api/minion/setup-complete" \
|
|
920
973
|
-H "Content-Type: application/json" \
|
|
921
974
|
-H "Authorization: Bearer ${API_TOKEN}" \
|
|
922
|
-
-d "
|
|
975
|
+
-d "$BODY" 2>&1) || true
|
|
923
976
|
|
|
924
977
|
if echo "$NOTIFY_RESPONSE" | grep -q '"success":true' 2>/dev/null; then
|
|
925
|
-
|
|
978
|
+
if [ -n "$LAN_IP" ] && [ ! -f /.dockerenv ]; then
|
|
979
|
+
echo " -> HQ notified successfully (LAN IP: ${LAN_IP})"
|
|
980
|
+
else
|
|
981
|
+
echo " -> HQ notified successfully"
|
|
982
|
+
fi
|
|
926
983
|
else
|
|
927
984
|
echo " -> Skipped (heartbeat will notify HQ within 30s)"
|
|
928
985
|
fi
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
const { spawn } = require('child_process')
|
|
18
18
|
const fs = require('fs')
|
|
19
19
|
const path = require('path')
|
|
20
|
-
const { verifyToken } = require('
|
|
21
|
-
const { config } = require('
|
|
22
|
-
const chatStore = require('
|
|
20
|
+
const { verifyToken } = require('../../core/lib/auth')
|
|
21
|
+
const { config } = require('../../core/config')
|
|
22
|
+
const chatStore = require('../../core/stores/chat-store')
|
|
23
23
|
|
|
24
24
|
/** @type {import('child_process').ChildProcess | null} */
|
|
25
25
|
let activeChatChild = null
|
|
@@ -10,7 +10,7 @@ const { exec } = require('child_process')
|
|
|
10
10
|
const { promisify } = require('util')
|
|
11
11
|
const execAsync = promisify(exec)
|
|
12
12
|
|
|
13
|
-
const { verifyToken } = require('
|
|
13
|
+
const { verifyToken } = require('../../core/lib/auth')
|
|
14
14
|
const { detectProcessManager, buildAllowedCommands } = require('../lib/process-manager')
|
|
15
15
|
|
|
16
16
|
const PROC_MGR = detectProcessManager()
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
const { execSync } = require('child_process')
|
|
11
11
|
const fs = require('fs')
|
|
12
12
|
const path = require('path')
|
|
13
|
-
const { verifyToken } = require('
|
|
14
|
-
const { clearLlmCache } = require('
|
|
15
|
-
const { config } = require('
|
|
13
|
+
const { verifyToken } = require('../../core/lib/auth')
|
|
14
|
+
const { clearLlmCache } = require('../../core/lib/llm-checker')
|
|
15
|
+
const { config } = require('../../core/config')
|
|
16
16
|
|
|
17
17
|
/** Keys that can be read/written via the config API */
|
|
18
18
|
const ALLOWED_ENV_KEYS = ['LLM_COMMAND']
|
|
@@ -12,12 +12,12 @@ const fs = require('fs').promises
|
|
|
12
12
|
const path = require('path')
|
|
13
13
|
const crypto = require('crypto')
|
|
14
14
|
|
|
15
|
-
const { verifyToken } = require('
|
|
16
|
-
const { config } = require('
|
|
17
|
-
const { writeSkillToLocal } = require('
|
|
15
|
+
const { verifyToken } = require('../../core/lib/auth')
|
|
16
|
+
const { config } = require('../../core/config')
|
|
17
|
+
const { writeSkillToLocal } = require('../../core/routes/skills')
|
|
18
18
|
const workflowRunner = require('../workflow-runner')
|
|
19
|
-
const executionStore = require('
|
|
20
|
-
const logManager = require('
|
|
19
|
+
const executionStore = require('../../core/stores/execution-store')
|
|
20
|
+
const logManager = require('../../core/lib/log-manager')
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Parse frontmatter from skill content to extract body
|
|
@@ -16,8 +16,8 @@ const fsSync = require('fs')
|
|
|
16
16
|
const path = require('path')
|
|
17
17
|
const { spawn } = require('child_process')
|
|
18
18
|
|
|
19
|
-
const { verifyToken } = require('
|
|
20
|
-
const { config } = require('
|
|
19
|
+
const { verifyToken } = require('../../core/lib/auth')
|
|
20
|
+
const { config } = require('../../core/config')
|
|
21
21
|
|
|
22
22
|
/** Base directory for file storage */
|
|
23
23
|
const FILES_DIR = path.join(config.HOME_DIR, 'files')
|
|
@@ -20,8 +20,8 @@ const path = require('path')
|
|
|
20
20
|
const net = require('net')
|
|
21
21
|
const execAsync = promisify(exec)
|
|
22
22
|
|
|
23
|
-
const { verifyToken } = require('
|
|
24
|
-
const { config } = require('
|
|
23
|
+
const { verifyToken } = require('../../core/lib/auth')
|
|
24
|
+
const { config } = require('../../core/config')
|
|
25
25
|
|
|
26
26
|
// Ensure consistent HOME for tmux socket path
|
|
27
27
|
const homeDir = config.HOME_DIR
|
|
@@ -18,10 +18,10 @@ const path = require('path')
|
|
|
18
18
|
const fs = require('fs').promises
|
|
19
19
|
const execAsync = promisify(exec)
|
|
20
20
|
|
|
21
|
-
const { config } = require('
|
|
22
|
-
const executionStore = require('
|
|
23
|
-
const routineStore = require('
|
|
24
|
-
const logManager = require('
|
|
21
|
+
const { config } = require('../core/config')
|
|
22
|
+
const executionStore = require('../core/stores/execution-store')
|
|
23
|
+
const routineStore = require('../core/stores/routine-store')
|
|
24
|
+
const logManager = require('../core/lib/log-manager')
|
|
25
25
|
|
|
26
26
|
// Active cron jobs keyed by routine ID
|
|
27
27
|
const activeJobs = new Map()
|
|
@@ -1,24 +1,60 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Minion Agent HTTP Server
|
|
2
|
+
* Minion Agent HTTP Server (Linux)
|
|
3
3
|
*
|
|
4
|
-
* Entry point for the minion agent
|
|
5
|
-
*
|
|
4
|
+
* Entry point for the minion agent on Linux.
|
|
5
|
+
* Registers shared routes (from core/) and Linux-specific routes.
|
|
6
|
+
*
|
|
7
|
+
* API Overview:
|
|
8
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
+
* Health & Status: GET/POST /api/health, /api/status
|
|
10
|
+
* Commands: GET /api/commands, POST /api/command
|
|
11
|
+
* Skills: GET /api/list-skills, POST /api/deploy-skill, etc.
|
|
12
|
+
* Workflows: GET/POST/PUT/DELETE /api/workflows, /api/workflows/trigger
|
|
13
|
+
* Routines: GET/POST/PUT/DELETE /api/routines, /api/routines/trigger
|
|
14
|
+
* Terminal: GET/POST /api/terminal/sessions, /send, /kill, /capture
|
|
15
|
+
* Files: GET/POST/DELETE /api/files
|
|
16
|
+
* Directives: POST /api/directive
|
|
17
|
+
* Auth: GET /api/auth/status
|
|
18
|
+
* Chat: POST /api/chat, GET /api/chat/session, POST /api/chat/clear
|
|
19
|
+
* Config: GET /api/config/backup, GET/PUT /api/config/env
|
|
20
|
+
* Executions: GET /api/executions, GET /api/executions/:id, etc.
|
|
21
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
6
22
|
*/
|
|
7
23
|
|
|
8
24
|
const fs = require('fs')
|
|
9
25
|
const path = require('path')
|
|
10
26
|
|
|
11
27
|
const fastify = require('fastify')({ logger: true })
|
|
12
|
-
|
|
28
|
+
|
|
29
|
+
// Package root (one level up from linux/)
|
|
30
|
+
const PACKAGE_ROOT = path.join(__dirname, '..')
|
|
31
|
+
|
|
32
|
+
// Core shared modules
|
|
33
|
+
const { config, validate, isHqConfigured } = require('../core/config')
|
|
34
|
+
const workflowStore = require('../core/stores/workflow-store')
|
|
35
|
+
const routineStore = require('../core/stores/routine-store')
|
|
36
|
+
|
|
37
|
+
// Linux-specific modules
|
|
13
38
|
const workflowRunner = require('./workflow-runner')
|
|
14
|
-
const workflowStore = require('./workflow-store')
|
|
15
39
|
const routineRunner = require('./routine-runner')
|
|
16
|
-
const routineStore = require('./routine-store')
|
|
17
|
-
|
|
18
|
-
const { registerRoutes, setOffline, getProcessManager, getAllowedCommands } = require('./routes')
|
|
19
40
|
const { cleanupTtyd, killStaleTtydProcesses } = require('./routes/terminal')
|
|
20
41
|
const { startTerminalProxy, stopTerminalProxy } = require('./terminal-proxy')
|
|
21
42
|
|
|
43
|
+
// Shared routes (from core/)
|
|
44
|
+
const { healthRoutes, setOffline } = require('../core/routes/health')
|
|
45
|
+
const { skillRoutes } = require('../core/routes/skills')
|
|
46
|
+
const { workflowRoutes } = require('../core/routes/workflows')
|
|
47
|
+
const { routineRoutes } = require('../core/routes/routines')
|
|
48
|
+
const { authRoutes } = require('../core/routes/auth')
|
|
49
|
+
|
|
50
|
+
// Linux-specific routes
|
|
51
|
+
const { commandRoutes, getProcessManager, getAllowedCommands } = require('./routes/commands')
|
|
52
|
+
const { terminalRoutes } = require('./routes/terminal')
|
|
53
|
+
const { fileRoutes } = require('./routes/files')
|
|
54
|
+
const { directiveRoutes } = require('./routes/directives')
|
|
55
|
+
const { chatRoutes } = require('./routes/chat')
|
|
56
|
+
const { configRoutes } = require('./routes/config')
|
|
57
|
+
|
|
22
58
|
// Validate configuration before starting
|
|
23
59
|
validate()
|
|
24
60
|
const PROC_MGR = getProcessManager()
|
|
@@ -50,11 +86,9 @@ process.on('SIGINT', () => shutdown('SIGINT'))
|
|
|
50
86
|
|
|
51
87
|
/**
|
|
52
88
|
* Sync bundled permissions into ~/.claude/settings.json.
|
|
53
|
-
* Merges package-defined allow/deny into the existing settings without
|
|
54
|
-
* removing user-added entries or non-permission keys (e.g. mcpServers).
|
|
55
89
|
*/
|
|
56
90
|
function syncPermissions() {
|
|
57
|
-
const bundledPath = path.join(
|
|
91
|
+
const bundledPath = path.join(PACKAGE_ROOT, 'settings', 'permissions.json')
|
|
58
92
|
const settingsDir = path.join(config.HOME_DIR, '.claude')
|
|
59
93
|
const settingsPath = path.join(settingsDir, 'settings.json')
|
|
60
94
|
|
|
@@ -63,13 +97,11 @@ function syncPermissions() {
|
|
|
63
97
|
|
|
64
98
|
const bundled = JSON.parse(fs.readFileSync(bundledPath, 'utf-8'))
|
|
65
99
|
|
|
66
|
-
// Read existing settings or start fresh
|
|
67
100
|
let settings = {}
|
|
68
101
|
if (fs.existsSync(settingsPath)) {
|
|
69
102
|
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'))
|
|
70
103
|
}
|
|
71
104
|
|
|
72
|
-
// Replace permissions section with bundled values
|
|
73
105
|
settings.permissions = {
|
|
74
106
|
allow: bundled.allow || [],
|
|
75
107
|
deny: bundled.deny || [],
|
|
@@ -85,10 +117,9 @@ function syncPermissions() {
|
|
|
85
117
|
|
|
86
118
|
/**
|
|
87
119
|
* Sync bundled tmux.conf to ~/.tmux.conf.
|
|
88
|
-
* Enables mouse-driven scrollback (copy-mode) for the WebSocket terminal.
|
|
89
120
|
*/
|
|
90
121
|
function syncTmuxConfig() {
|
|
91
|
-
const bundledPath = path.join(
|
|
122
|
+
const bundledPath = path.join(PACKAGE_ROOT, 'settings', 'tmux.conf')
|
|
92
123
|
const destPath = path.join(config.HOME_DIR, '.tmux.conf')
|
|
93
124
|
|
|
94
125
|
try {
|
|
@@ -103,11 +134,9 @@ function syncTmuxConfig() {
|
|
|
103
134
|
|
|
104
135
|
/**
|
|
105
136
|
* Sync bundled rules from the package to ~/.claude/rules/.
|
|
106
|
-
* Deploys core.md only. Role context is injected per-execution, not as rules.
|
|
107
|
-
* Removes legacy files (minion.md, role-*.md) if present.
|
|
108
137
|
*/
|
|
109
138
|
function syncBundledRules() {
|
|
110
|
-
const bundledRulesDir = path.join(
|
|
139
|
+
const bundledRulesDir = path.join(PACKAGE_ROOT, 'rules')
|
|
111
140
|
const targetRulesDir = path.join(config.HOME_DIR, '.claude', 'rules')
|
|
112
141
|
|
|
113
142
|
try {
|
|
@@ -115,14 +144,12 @@ function syncBundledRules() {
|
|
|
115
144
|
|
|
116
145
|
fs.mkdirSync(targetRulesDir, { recursive: true })
|
|
117
146
|
|
|
118
|
-
// Always deploy core.md
|
|
119
147
|
const coreSrc = path.join(bundledRulesDir, 'core.md')
|
|
120
148
|
if (fs.existsSync(coreSrc)) {
|
|
121
149
|
fs.copyFileSync(coreSrc, path.join(targetRulesDir, 'core.md'))
|
|
122
150
|
console.log('[Rules] Synced: core.md')
|
|
123
151
|
}
|
|
124
152
|
|
|
125
|
-
// Remove legacy files if present
|
|
126
153
|
for (const legacy of ['minion.md', 'role-pm.md', 'role-engineer.md']) {
|
|
127
154
|
const legacyPath = path.join(targetRulesDir, legacy)
|
|
128
155
|
if (fs.existsSync(legacyPath)) {
|
|
@@ -137,11 +164,9 @@ function syncBundledRules() {
|
|
|
137
164
|
|
|
138
165
|
/**
|
|
139
166
|
* Sync bundled role context files to ~/.minion/roles/.
|
|
140
|
-
* These are NOT loaded as Claude Code rules — they are injected
|
|
141
|
-
* into the prompt at execution time based on the minion's role.
|
|
142
167
|
*/
|
|
143
168
|
function syncBundledRoles() {
|
|
144
|
-
const bundledRolesDir = path.join(
|
|
169
|
+
const bundledRolesDir = path.join(PACKAGE_ROOT, 'roles')
|
|
145
170
|
const targetRolesDir = path.join(config.HOME_DIR, '.minion', 'roles')
|
|
146
171
|
|
|
147
172
|
try {
|
|
@@ -163,11 +188,9 @@ function syncBundledRoles() {
|
|
|
163
188
|
|
|
164
189
|
/**
|
|
165
190
|
* Sync bundled documentation files to ~/.minion/docs/.
|
|
166
|
-
* These are reference documents accessed on-demand by Claude Code,
|
|
167
|
-
* NOT loaded automatically as rules.
|
|
168
191
|
*/
|
|
169
192
|
function syncBundledDocs() {
|
|
170
|
-
const bundledDocsDir = path.join(
|
|
193
|
+
const bundledDocsDir = path.join(PACKAGE_ROOT, 'docs')
|
|
171
194
|
const targetDocsDir = path.join(config.HOME_DIR, '.minion', 'docs')
|
|
172
195
|
|
|
173
196
|
try {
|
|
@@ -187,26 +210,38 @@ function syncBundledDocs() {
|
|
|
187
210
|
}
|
|
188
211
|
}
|
|
189
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Register all routes (shared + Linux-specific)
|
|
215
|
+
*/
|
|
216
|
+
async function registerAllRoutes(app) {
|
|
217
|
+
// Shared routes (from core/) - inject runners via opts
|
|
218
|
+
await app.register(healthRoutes)
|
|
219
|
+
await app.register(skillRoutes, { workflowRunner })
|
|
220
|
+
await app.register(workflowRoutes, { workflowRunner })
|
|
221
|
+
await app.register(routineRoutes, { routineRunner })
|
|
222
|
+
await app.register(authRoutes)
|
|
223
|
+
|
|
224
|
+
// Linux-specific routes
|
|
225
|
+
await app.register(commandRoutes)
|
|
226
|
+
await app.register(terminalRoutes)
|
|
227
|
+
await app.register(fileRoutes)
|
|
228
|
+
await app.register(directiveRoutes)
|
|
229
|
+
await app.register(chatRoutes)
|
|
230
|
+
await app.register(configRoutes)
|
|
231
|
+
}
|
|
232
|
+
|
|
190
233
|
// Start server
|
|
191
234
|
async function start() {
|
|
192
235
|
try {
|
|
193
|
-
// Sync bundled
|
|
236
|
+
// Sync bundled assets
|
|
194
237
|
syncBundledRules()
|
|
195
|
-
|
|
196
|
-
// Sync bundled roles to ~/.minion/roles/ (injected per-execution)
|
|
197
238
|
syncBundledRoles()
|
|
198
|
-
|
|
199
|
-
// Sync bundled docs to ~/.minion/docs/ (on-demand reference)
|
|
200
239
|
syncBundledDocs()
|
|
201
|
-
|
|
202
|
-
// Sync bundled permissions to ~/.claude/settings.json (broad allow + deny-list)
|
|
203
240
|
syncPermissions()
|
|
204
|
-
|
|
205
|
-
// Sync tmux.conf for mouse scroll support in WebSocket terminal
|
|
206
241
|
syncTmuxConfig()
|
|
207
242
|
|
|
208
243
|
// Register all routes
|
|
209
|
-
await
|
|
244
|
+
await registerAllRoutes(fastify)
|
|
210
245
|
|
|
211
246
|
// Listen on all interfaces
|
|
212
247
|
await fastify.listen({ port: config.AGENT_PORT, host: '0.0.0.0' })
|