@geekbeer/minion 2.32.0 → 2.33.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/core/config.js +4 -4
- package/core/lib/llm-checker.js +9 -16
- package/core/lib/log-manager.js +3 -2
- package/core/lib/platform.js +2 -2
- package/core/stores/chat-store.js +4 -4
- package/core/stores/execution-store.js +4 -4
- package/core/stores/routine-store.js +5 -5
- package/core/stores/workflow-store.js +5 -6
- package/package.json +1 -1
- package/win/minion-cli.ps1 +15 -7
- package/win/routes/config.js +1 -1
- package/win/routes/directives.js +1 -1
- package/win/routes/terminal.js +19 -0
- package/win/routine-runner.js +1 -1
- package/win/terminal-server.js +8 -0
- package/win/workflow-runner.js +1 -1
- package/win/lib/llm-checker.js +0 -115
- package/win/lib/log-manager.js +0 -119
package/core/config.js
CHANGED
|
@@ -19,13 +19,13 @@ const { execSync } = require('child_process')
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Resolve the correct home directory for the minion user.
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* On Linux, supervisord environments may set HOME=/root incorrectly.
|
|
23
|
+
* This function uses MINION_USER + getent passwd to find the correct home.
|
|
24
|
+
* On Windows, os.homedir() is always correct (returns %USERPROFILE%).
|
|
25
25
|
*/
|
|
26
26
|
function resolveHomeDir() {
|
|
27
27
|
const minionUser = process.env.MINION_USER
|
|
28
|
-
if (minionUser) {
|
|
28
|
+
if (minionUser && process.platform !== 'win32') {
|
|
29
29
|
try {
|
|
30
30
|
const entry = execSync(`getent passwd ${minionUser}`, { encoding: 'utf-8' }).trim()
|
|
31
31
|
const home = entry.split(':')[5]
|
package/core/lib/llm-checker.js
CHANGED
|
@@ -10,26 +10,13 @@ const fs = require('fs')
|
|
|
10
10
|
const path = require('path')
|
|
11
11
|
const { execSync } = require('child_process')
|
|
12
12
|
const { config } = require('../config')
|
|
13
|
+
const { IS_WINDOWS, buildExtendedPath } = require('./platform')
|
|
13
14
|
|
|
14
15
|
const CACHE_TTL_MS = 60000
|
|
15
16
|
|
|
16
17
|
let cachedResult = null
|
|
17
18
|
let cachedAt = 0
|
|
18
19
|
|
|
19
|
-
/**
|
|
20
|
-
* Build extended PATH that includes common Claude CLI installation locations
|
|
21
|
-
*/
|
|
22
|
-
function getExtendedPath() {
|
|
23
|
-
const additionalPaths = [
|
|
24
|
-
path.join(config.HOME_DIR, '.local', 'bin'),
|
|
25
|
-
path.join(config.HOME_DIR, 'bin'),
|
|
26
|
-
path.join(config.HOME_DIR, '.npm-global', 'bin'),
|
|
27
|
-
path.join(config.HOME_DIR, '.claude', 'bin'),
|
|
28
|
-
'/usr/local/bin',
|
|
29
|
-
]
|
|
30
|
-
return [...additionalPaths, process.env.PATH || '/usr/bin:/bin'].join(':')
|
|
31
|
-
}
|
|
32
|
-
|
|
33
20
|
/**
|
|
34
21
|
* Check Claude Code authentication.
|
|
35
22
|
* First checks known credential file locations (fast path),
|
|
@@ -57,14 +44,16 @@ function isClaudeAuthenticated() {
|
|
|
57
44
|
// Fallback: check via claude CLI command (handles newer credential storage)
|
|
58
45
|
try {
|
|
59
46
|
const claudePath = path.join(config.HOME_DIR, '.local', 'bin', 'claude')
|
|
60
|
-
const claudeBin = fs.existsSync(claudePath) ? claudePath : 'claude'
|
|
47
|
+
const claudeBin = (!IS_WINDOWS && fs.existsSync(claudePath)) ? claudePath : 'claude'
|
|
61
48
|
execSync(`${claudeBin} auth whoami`, {
|
|
62
49
|
encoding: 'utf-8',
|
|
63
50
|
timeout: 5000,
|
|
64
51
|
stdio: 'pipe',
|
|
65
52
|
env: {
|
|
53
|
+
...process.env,
|
|
66
54
|
HOME: config.HOME_DIR,
|
|
67
|
-
|
|
55
|
+
...(IS_WINDOWS && { USERPROFILE: config.HOME_DIR }),
|
|
56
|
+
PATH: buildExtendedPath(config.HOME_DIR),
|
|
68
57
|
},
|
|
69
58
|
})
|
|
70
59
|
return true
|
|
@@ -83,6 +72,10 @@ function isGeminiAuthenticated() {
|
|
|
83
72
|
const possiblePaths = [
|
|
84
73
|
path.join(config.HOME_DIR, '.config', 'gemini'),
|
|
85
74
|
path.join(config.HOME_DIR, '.config', 'gcloud', 'application_default_credentials.json'),
|
|
75
|
+
// Windows-specific locations
|
|
76
|
+
...(IS_WINDOWS ? [
|
|
77
|
+
path.join(config.HOME_DIR, 'AppData', 'Roaming', 'gcloud', 'application_default_credentials.json'),
|
|
78
|
+
] : []),
|
|
86
79
|
]
|
|
87
80
|
for (const p of possiblePaths) {
|
|
88
81
|
try {
|
package/core/lib/log-manager.js
CHANGED
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
const fs = require('fs').promises
|
|
8
8
|
const path = require('path')
|
|
9
|
+
const platform = require('./platform')
|
|
9
10
|
|
|
10
|
-
// Log storage configuration
|
|
11
|
-
const LOG_DIR =
|
|
11
|
+
// Log storage configuration (platform-aware via platform.js)
|
|
12
|
+
const LOG_DIR = platform.LOG_DIR
|
|
12
13
|
const MAX_LOG_FILES = 100
|
|
13
14
|
|
|
14
15
|
/**
|
package/core/lib/platform.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Cross-platform utility module
|
|
3
3
|
*
|
|
4
4
|
* Provides platform-aware paths, separators, and helpers.
|
|
5
|
-
* Used by
|
|
6
|
-
*
|
|
5
|
+
* Used by core/ modules (stores, log-manager, llm-checker) and win/ modules
|
|
6
|
+
* to provide consistent cross-platform behavior.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const os = require('os')
|
|
@@ -8,18 +8,18 @@ const fs = require('fs').promises
|
|
|
8
8
|
const path = require('path')
|
|
9
9
|
|
|
10
10
|
const { config } = require('../config')
|
|
11
|
+
const { DATA_DIR } = require('../lib/platform')
|
|
11
12
|
|
|
12
13
|
const MAX_MESSAGES = 100
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Get chat session file path
|
|
16
|
-
* Uses
|
|
17
|
+
* Uses DATA_DIR if available (platform-aware), otherwise home dir
|
|
17
18
|
*/
|
|
18
19
|
function getFilePath() {
|
|
19
|
-
const optPath = '/opt/minion-agent/chat-session.json'
|
|
20
20
|
try {
|
|
21
|
-
require('fs').accessSync(
|
|
22
|
-
return
|
|
21
|
+
require('fs').accessSync(DATA_DIR)
|
|
22
|
+
return path.join(DATA_DIR, 'chat-session.json')
|
|
23
23
|
} catch {
|
|
24
24
|
return path.join(config.HOME_DIR, 'chat-session.json')
|
|
25
25
|
}
|
|
@@ -8,19 +8,19 @@ const fs = require('fs').promises
|
|
|
8
8
|
const path = require('path')
|
|
9
9
|
|
|
10
10
|
const { config } = require('../config')
|
|
11
|
+
const { DATA_DIR } = require('../lib/platform')
|
|
11
12
|
|
|
12
13
|
// Max executions to keep (older ones are pruned)
|
|
13
14
|
const MAX_EXECUTIONS = 200
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Get execution file path
|
|
17
|
-
* Uses
|
|
18
|
+
* Uses DATA_DIR if available (platform-aware), otherwise home dir
|
|
18
19
|
*/
|
|
19
20
|
function getExecutionFilePath() {
|
|
20
|
-
const optPath = '/opt/minion-agent/executions.json'
|
|
21
21
|
try {
|
|
22
|
-
require('fs').accessSync(
|
|
23
|
-
return
|
|
22
|
+
require('fs').accessSync(DATA_DIR)
|
|
23
|
+
return path.join(DATA_DIR, 'executions.json')
|
|
24
24
|
} catch {
|
|
25
25
|
return path.join(config.HOME_DIR, 'executions.json')
|
|
26
26
|
}
|
|
@@ -8,14 +8,14 @@ const fs = require('fs').promises
|
|
|
8
8
|
const path = require('path')
|
|
9
9
|
|
|
10
10
|
const { config } = require('../config')
|
|
11
|
+
const { DATA_DIR } = require('../lib/platform')
|
|
11
12
|
|
|
12
|
-
// Routine file location: /
|
|
13
|
-
// or ~/routines.json (
|
|
13
|
+
// Routine file location: DATA_DIR/routines.json (platform-aware)
|
|
14
|
+
// or ~/routines.json (fallback)
|
|
14
15
|
function getRoutineFilePath() {
|
|
15
|
-
const optPath = '/opt/minion-agent/routines.json'
|
|
16
16
|
try {
|
|
17
|
-
require('fs').accessSync(
|
|
18
|
-
return
|
|
17
|
+
require('fs').accessSync(DATA_DIR)
|
|
18
|
+
return path.join(DATA_DIR, 'routines.json')
|
|
19
19
|
} catch {
|
|
20
20
|
return path.join(config.HOME_DIR, 'routines.json')
|
|
21
21
|
}
|
|
@@ -8,15 +8,14 @@ const fs = require('fs').promises
|
|
|
8
8
|
const path = require('path')
|
|
9
9
|
|
|
10
10
|
const { config } = require('../config')
|
|
11
|
+
const { DATA_DIR } = require('../lib/platform')
|
|
11
12
|
|
|
12
|
-
// Workflow file location: /
|
|
13
|
-
// or ~/workflows.json (
|
|
13
|
+
// Workflow file location: DATA_DIR/workflows.json (platform-aware)
|
|
14
|
+
// or ~/workflows.json (fallback)
|
|
14
15
|
function getWorkflowFilePath() {
|
|
15
|
-
const optPath = '/opt/minion-agent/workflows.json'
|
|
16
|
-
// Use /opt path if it exists (production), otherwise home dir
|
|
17
16
|
try {
|
|
18
|
-
require('fs').accessSync(
|
|
19
|
-
return
|
|
17
|
+
require('fs').accessSync(DATA_DIR)
|
|
18
|
+
return path.join(DATA_DIR, 'workflows.json')
|
|
20
19
|
} catch {
|
|
21
20
|
return path.join(config.HOME_DIR, 'workflows.json')
|
|
22
21
|
}
|
package/package.json
CHANGED
package/win/minion-cli.ps1
CHANGED
|
@@ -423,8 +423,16 @@ if (`$vncExe) {
|
|
|
423
423
|
|
|
424
424
|
# Start cloudflared tunnel if configured
|
|
425
425
|
`$cfConfig = Join-Path `$env:USERPROFILE '.cloudflared\config.yml'
|
|
426
|
-
if (
|
|
427
|
-
|
|
426
|
+
if (Test-Path `$cfConfig) {
|
|
427
|
+
`$cfExe = `$null
|
|
428
|
+
if (Get-Command cloudflared -ErrorAction SilentlyContinue) {
|
|
429
|
+
`$cfExe = (Get-Command cloudflared).Source
|
|
430
|
+
} elseif (Test-Path (Join-Path `$DataDir 'cloudflared.exe')) {
|
|
431
|
+
`$cfExe = Join-Path `$DataDir 'cloudflared.exe'
|
|
432
|
+
}
|
|
433
|
+
if (`$cfExe) {
|
|
434
|
+
Start-Process -FilePath `$cfExe -ArgumentList 'tunnel', 'run' -WindowStyle Hidden
|
|
435
|
+
}
|
|
428
436
|
}
|
|
429
437
|
|
|
430
438
|
# Watchdog loop: restart node if it crashes
|
|
@@ -606,18 +614,18 @@ Remove-Item `$PidFile -Force -ErrorAction SilentlyContinue
|
|
|
606
614
|
Write-Host " Fetching tunnel configuration from HQ..."
|
|
607
615
|
try {
|
|
608
616
|
$headers = @{ 'Authorization' = "Bearer $ApiToken" }
|
|
609
|
-
$tunnelData = Invoke-RestMethod -Uri "$HqUrl/api/minion/tunnel-credentials" -Headers $headers
|
|
617
|
+
$tunnelData = Invoke-RestMethod -Uri "$HqUrl/api/minion/tunnel-credentials?platform=windows" -Headers $headers
|
|
610
618
|
|
|
611
619
|
if ($tunnelData.tunnel_id) {
|
|
612
620
|
$cfConfigDir = Join-Path $env:USERPROFILE '.cloudflared'
|
|
613
621
|
New-Item -Path $cfConfigDir -ItemType Directory -Force | Out-Null
|
|
614
622
|
|
|
615
|
-
# Save credentials
|
|
616
|
-
|
|
623
|
+
# Save credentials (BOM-free UTF-8, required by cloudflared)
|
|
624
|
+
[System.IO.File]::WriteAllText((Join-Path $cfConfigDir "$($tunnelData.tunnel_id).json"), $tunnelData.credentials_json, [System.Text.UTF8Encoding]::new($false))
|
|
617
625
|
Write-Detail "Tunnel credentials saved"
|
|
618
626
|
|
|
619
|
-
# Save config
|
|
620
|
-
|
|
627
|
+
# Save config (BOM-free UTF-8)
|
|
628
|
+
[System.IO.File]::WriteAllText((Join-Path $cfConfigDir 'config.yml'), $tunnelData.config_yml, [System.Text.UTF8Encoding]::new($false))
|
|
621
629
|
Write-Detail "Tunnel config saved (will run as user process on start)"
|
|
622
630
|
}
|
|
623
631
|
else {
|
package/win/routes/config.js
CHANGED
|
@@ -9,7 +9,7 @@ const fs = require('fs')
|
|
|
9
9
|
const path = require('path')
|
|
10
10
|
const zlib = require('zlib')
|
|
11
11
|
const { verifyToken } = require('../../core/lib/auth')
|
|
12
|
-
const { clearLlmCache } = require('
|
|
12
|
+
const { clearLlmCache } = require('../../core/lib/llm-checker')
|
|
13
13
|
const { config } = require('../../core/config')
|
|
14
14
|
const { resolveEnvFilePath } = require('../../core/lib/platform')
|
|
15
15
|
|
package/win/routes/directives.js
CHANGED
|
@@ -13,7 +13,7 @@ const { config } = require('../../core/config')
|
|
|
13
13
|
const { writeSkillToLocal } = require('../../core/routes/skills')
|
|
14
14
|
const workflowRunner = require('../workflow-runner')
|
|
15
15
|
const executionStore = require('../../core/stores/execution-store')
|
|
16
|
-
const logManager = require('
|
|
16
|
+
const logManager = require('../../core/lib/log-manager')
|
|
17
17
|
|
|
18
18
|
function parseFrontmatter(content) {
|
|
19
19
|
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/)
|
package/win/routes/terminal.js
CHANGED
|
@@ -58,18 +58,37 @@ function createPtySession(sessionName, command) {
|
|
|
58
58
|
completed: false,
|
|
59
59
|
exitCode: null,
|
|
60
60
|
startedAt: new Date().toISOString(),
|
|
61
|
+
wsClients: new Set(),
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
// ttyd message type for output (must match terminal-server.js)
|
|
65
|
+
const MSG_OUTPUT = 0x30 // '0'
|
|
66
|
+
|
|
63
67
|
ptyProcess.onData((data) => {
|
|
64
68
|
session.buffer += data
|
|
65
69
|
if (session.buffer.length > 1024 * 1024) {
|
|
66
70
|
session.buffer = session.buffer.slice(-512 * 1024)
|
|
67
71
|
}
|
|
72
|
+
// Broadcast to all WebSocket clients using ttyd OUTPUT format
|
|
73
|
+
if (session.wsClients && session.wsClients.size > 0) {
|
|
74
|
+
const outBuf = Buffer.alloc(1 + Buffer.byteLength(data))
|
|
75
|
+
outBuf[0] = MSG_OUTPUT
|
|
76
|
+
outBuf.write(data, 1)
|
|
77
|
+
for (const ws of session.wsClients) {
|
|
78
|
+
try {
|
|
79
|
+
if (ws.readyState === 1) ws.send(outBuf)
|
|
80
|
+
} catch { /* ignore */ }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
68
83
|
})
|
|
69
84
|
|
|
70
85
|
ptyProcess.onExit(({ exitCode }) => {
|
|
71
86
|
session.completed = true
|
|
72
87
|
session.exitCode = exitCode
|
|
88
|
+
// Close all WebSocket clients
|
|
89
|
+
for (const ws of (session.wsClients || [])) {
|
|
90
|
+
try { ws.close() } catch { /* ignore */ }
|
|
91
|
+
}
|
|
73
92
|
})
|
|
74
93
|
|
|
75
94
|
activeSessions.set(sessionName, session)
|
package/win/routine-runner.js
CHANGED
|
@@ -14,7 +14,7 @@ const fsSync = require('fs')
|
|
|
14
14
|
const { config } = require('../core/config')
|
|
15
15
|
const executionStore = require('../core/stores/execution-store')
|
|
16
16
|
const routineStore = require('../core/stores/routine-store')
|
|
17
|
-
const logManager = require('
|
|
17
|
+
const logManager = require('../core/lib/log-manager')
|
|
18
18
|
const { MARKER_DIR, buildExtendedPath } = require('../core/lib/platform')
|
|
19
19
|
const { activeSessions } = require('./workflow-runner')
|
|
20
20
|
|
package/win/terminal-server.js
CHANGED
|
@@ -168,6 +168,14 @@ function startTerminalServer() {
|
|
|
168
168
|
const titleMsg = Buffer.from([MSG_SET_TITLE, ...Buffer.from(JSON.stringify({ title: sessionName }))])
|
|
169
169
|
ws.send(titleMsg)
|
|
170
170
|
|
|
171
|
+
// Replay buffered output so reconnecting clients see prior content
|
|
172
|
+
if (session.buffer && session.buffer.length > 0) {
|
|
173
|
+
const outBuf = Buffer.alloc(1 + Buffer.byteLength(session.buffer))
|
|
174
|
+
outBuf[0] = MSG_OUTPUT
|
|
175
|
+
outBuf.write(session.buffer, 1)
|
|
176
|
+
try { ws.send(outBuf) } catch { /* ignore */ }
|
|
177
|
+
}
|
|
178
|
+
|
|
171
179
|
// Handle incoming messages (ttyd protocol)
|
|
172
180
|
ws.on('message', (data) => {
|
|
173
181
|
if (!session.pty || session.completed) return
|
package/win/workflow-runner.js
CHANGED
|
@@ -20,7 +20,7 @@ const fsSync = require('fs')
|
|
|
20
20
|
const { config } = require('../core/config')
|
|
21
21
|
const executionStore = require('../core/stores/execution-store')
|
|
22
22
|
const workflowStore = require('../core/stores/workflow-store')
|
|
23
|
-
const logManager = require('
|
|
23
|
+
const logManager = require('../core/lib/log-manager')
|
|
24
24
|
const { MARKER_DIR, buildExtendedPath } = require('../core/lib/platform')
|
|
25
25
|
|
|
26
26
|
// Active cron jobs keyed by workflow ID
|
package/win/lib/llm-checker.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Windows LLM Service authentication checker
|
|
3
|
-
*
|
|
4
|
-
* Same logic as lib/llm-checker.js but with Windows-compatible paths
|
|
5
|
-
* and PATH separator handling.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const fs = require('fs')
|
|
9
|
-
const path = require('path')
|
|
10
|
-
const { execSync } = require('child_process')
|
|
11
|
-
const { config } = require('../../core/config')
|
|
12
|
-
const { buildExtendedPath } = require('../../core/lib/platform')
|
|
13
|
-
|
|
14
|
-
const CACHE_TTL_MS = 60000
|
|
15
|
-
let cachedResult = null
|
|
16
|
-
let cachedAt = 0
|
|
17
|
-
|
|
18
|
-
function isClaudeAuthenticated() {
|
|
19
|
-
const candidates = [
|
|
20
|
-
path.join(config.HOME_DIR, '.claude', '.credentials.json'),
|
|
21
|
-
path.join(config.HOME_DIR, '.claude', 'credentials.json'),
|
|
22
|
-
]
|
|
23
|
-
for (const p of candidates) {
|
|
24
|
-
try {
|
|
25
|
-
if (fs.existsSync(p)) {
|
|
26
|
-
const content = fs.readFileSync(p, 'utf-8')
|
|
27
|
-
const parsed = JSON.parse(content)
|
|
28
|
-
if (parsed && Object.keys(parsed).length > 0) return true
|
|
29
|
-
}
|
|
30
|
-
} catch { /* not authenticated */ }
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
const claudePath = path.join(config.HOME_DIR, '.local', 'bin', 'claude')
|
|
35
|
-
const claudeBin = fs.existsSync(claudePath) ? claudePath : 'claude'
|
|
36
|
-
execSync(`${claudeBin} auth whoami`, {
|
|
37
|
-
encoding: 'utf-8',
|
|
38
|
-
timeout: 5000,
|
|
39
|
-
stdio: 'pipe',
|
|
40
|
-
env: {
|
|
41
|
-
...process.env,
|
|
42
|
-
HOME: config.HOME_DIR,
|
|
43
|
-
USERPROFILE: config.HOME_DIR,
|
|
44
|
-
PATH: buildExtendedPath(config.HOME_DIR),
|
|
45
|
-
},
|
|
46
|
-
})
|
|
47
|
-
return true
|
|
48
|
-
} catch {
|
|
49
|
-
return false
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function isGeminiAuthenticated() {
|
|
54
|
-
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return true
|
|
55
|
-
|
|
56
|
-
const possiblePaths = [
|
|
57
|
-
path.join(config.HOME_DIR, '.config', 'gemini'),
|
|
58
|
-
path.join(config.HOME_DIR, '.config', 'gcloud', 'application_default_credentials.json'),
|
|
59
|
-
// Windows-specific locations
|
|
60
|
-
path.join(config.HOME_DIR, 'AppData', 'Roaming', 'gcloud', 'application_default_credentials.json'),
|
|
61
|
-
]
|
|
62
|
-
for (const p of possiblePaths) {
|
|
63
|
-
try {
|
|
64
|
-
if (!fs.existsSync(p)) continue
|
|
65
|
-
const stat = fs.statSync(p)
|
|
66
|
-
if (stat.isDirectory()) {
|
|
67
|
-
if (fs.readdirSync(p).length > 0) return true
|
|
68
|
-
} else {
|
|
69
|
-
if (fs.readFileSync(p, 'utf-8').trim().length > 0) return true
|
|
70
|
-
}
|
|
71
|
-
} catch { /* ignore */ }
|
|
72
|
-
}
|
|
73
|
-
return false
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function isCodexAuthenticated() {
|
|
77
|
-
if (process.env.OPENAI_API_KEY) return true
|
|
78
|
-
const codexConfig = path.join(config.HOME_DIR, '.codex')
|
|
79
|
-
try {
|
|
80
|
-
if (fs.existsSync(codexConfig) && fs.statSync(codexConfig).isDirectory()) {
|
|
81
|
-
if (fs.readdirSync(codexConfig).length > 0) return true
|
|
82
|
-
}
|
|
83
|
-
} catch { /* ignore */ }
|
|
84
|
-
return false
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const SERVICE_DEFINITIONS = [
|
|
88
|
-
{ name: 'claude', display_name: 'Claude Code', check: isClaudeAuthenticated },
|
|
89
|
-
{ name: 'gemini', display_name: 'Gemini CLI', check: isGeminiAuthenticated },
|
|
90
|
-
{ name: 'codex', display_name: 'Codex', check: isCodexAuthenticated },
|
|
91
|
-
]
|
|
92
|
-
|
|
93
|
-
function isLlmCommandConfigured() {
|
|
94
|
-
return !!config.LLM_COMMAND
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function getLlmServices() {
|
|
98
|
-
const now = Date.now()
|
|
99
|
-
if (cachedResult && (now - cachedAt) < CACHE_TTL_MS) {
|
|
100
|
-
return cachedResult
|
|
101
|
-
}
|
|
102
|
-
const services = SERVICE_DEFINITIONS.map(({ name, display_name, check }) => ({
|
|
103
|
-
name, display_name, authenticated: check(),
|
|
104
|
-
}))
|
|
105
|
-
cachedResult = services
|
|
106
|
-
cachedAt = now
|
|
107
|
-
return services
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function clearLlmCache() {
|
|
111
|
-
cachedResult = null
|
|
112
|
-
cachedAt = 0
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
module.exports = { getLlmServices, clearLlmCache, isLlmCommandConfigured }
|
package/win/lib/log-manager.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Windows Log Manager
|
|
3
|
-
*
|
|
4
|
-
* Manages workflow execution log files using platform-aware paths.
|
|
5
|
-
* Drop-in replacement for lib/log-manager.js on Windows.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const fs = require('fs').promises
|
|
9
|
-
const path = require('path')
|
|
10
|
-
const { LOG_DIR } = require('../../core/lib/platform')
|
|
11
|
-
|
|
12
|
-
const MAX_LOG_FILES = 100
|
|
13
|
-
|
|
14
|
-
async function ensureLogDir() {
|
|
15
|
-
try {
|
|
16
|
-
await fs.mkdir(LOG_DIR, { recursive: true })
|
|
17
|
-
} catch (err) {
|
|
18
|
-
console.error(`[LogManager] Failed to create log directory: ${err.message}`)
|
|
19
|
-
throw err
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function getLogPath(executionId) {
|
|
24
|
-
return path.join(LOG_DIR, `${executionId}.log`)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function logExists(executionId) {
|
|
28
|
-
try {
|
|
29
|
-
await fs.access(getLogPath(executionId))
|
|
30
|
-
return true
|
|
31
|
-
} catch {
|
|
32
|
-
return false
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function readLog(executionId, options = {}) {
|
|
37
|
-
const logPath = getLogPath(executionId)
|
|
38
|
-
try {
|
|
39
|
-
const stats = await fs.stat(logPath)
|
|
40
|
-
const content = await fs.readFile(logPath, 'utf-8')
|
|
41
|
-
const allLines = content.split('\n')
|
|
42
|
-
|
|
43
|
-
let resultContent = content
|
|
44
|
-
if (options.tail && options.tail > 0) {
|
|
45
|
-
resultContent = allLines.slice(-options.tail).join('\n')
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return { content: resultContent, lines: allLines.length, size: stats.size }
|
|
49
|
-
} catch (err) {
|
|
50
|
-
if (err.code === 'ENOENT') return null
|
|
51
|
-
throw err
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function pruneOldLogs() {
|
|
56
|
-
try {
|
|
57
|
-
try { await fs.access(LOG_DIR) } catch { return }
|
|
58
|
-
|
|
59
|
-
const files = await fs.readdir(LOG_DIR)
|
|
60
|
-
const logFiles = files.filter(f => f.endsWith('.log'))
|
|
61
|
-
if (logFiles.length <= MAX_LOG_FILES) return
|
|
62
|
-
|
|
63
|
-
console.log(`[LogManager] Pruning logs: ${logFiles.length} files (max: ${MAX_LOG_FILES})`)
|
|
64
|
-
|
|
65
|
-
const fileStats = await Promise.all(
|
|
66
|
-
logFiles.map(async f => {
|
|
67
|
-
try {
|
|
68
|
-
const stats = await fs.stat(path.join(LOG_DIR, f))
|
|
69
|
-
return { name: f, mtime: stats.mtime }
|
|
70
|
-
} catch { return null }
|
|
71
|
-
})
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
const validFiles = fileStats.filter(f => f !== null)
|
|
75
|
-
validFiles.sort((a, b) => a.mtime - b.mtime)
|
|
76
|
-
|
|
77
|
-
const toDelete = validFiles.slice(0, validFiles.length - MAX_LOG_FILES)
|
|
78
|
-
for (const file of toDelete) {
|
|
79
|
-
try {
|
|
80
|
-
await fs.unlink(path.join(LOG_DIR, file.name))
|
|
81
|
-
console.log(`[LogManager] Deleted old log: ${file.name}`)
|
|
82
|
-
} catch (err) {
|
|
83
|
-
console.error(`[LogManager] Failed to delete ${file.name}: ${err.message}`)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
console.log(`[LogManager] Pruned ${toDelete.length} old log files`)
|
|
87
|
-
} catch (err) {
|
|
88
|
-
console.error(`[LogManager] Failed to prune logs: ${err.message}`)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function getLogStats() {
|
|
93
|
-
try {
|
|
94
|
-
try { await fs.access(LOG_DIR) } catch { return { count: 0, totalSize: 0 } }
|
|
95
|
-
const files = await fs.readdir(LOG_DIR)
|
|
96
|
-
const logFiles = files.filter(f => f.endsWith('.log'))
|
|
97
|
-
let totalSize = 0
|
|
98
|
-
for (const f of logFiles) {
|
|
99
|
-
try {
|
|
100
|
-
const stats = await fs.stat(path.join(LOG_DIR, f))
|
|
101
|
-
totalSize += stats.size
|
|
102
|
-
} catch { /* skip */ }
|
|
103
|
-
}
|
|
104
|
-
return { count: logFiles.length, totalSize }
|
|
105
|
-
} catch {
|
|
106
|
-
return { count: 0, totalSize: 0 }
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
module.exports = {
|
|
111
|
-
LOG_DIR,
|
|
112
|
-
MAX_LOG_FILES,
|
|
113
|
-
ensureLogDir,
|
|
114
|
-
getLogPath,
|
|
115
|
-
logExists,
|
|
116
|
-
readLog,
|
|
117
|
-
pruneOldLogs,
|
|
118
|
-
getLogStats,
|
|
119
|
-
}
|