@geekbeer/minion 2.23.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 +44 -4
- 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} +245 -4
- 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
|
@@ -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' })
|
|
@@ -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 workflowStore = require('
|
|
24
|
-
const logManager = require('
|
|
21
|
+
const { config } = require('../core/config')
|
|
22
|
+
const executionStore = require('../core/stores/execution-store')
|
|
23
|
+
const workflowStore = require('../core/stores/workflow-store')
|
|
24
|
+
const logManager = require('../core/lib/log-manager')
|
|
25
25
|
|
|
26
26
|
// Active cron jobs keyed by workflow ID
|
|
27
27
|
const activeJobs = new Map()
|
package/package.json
CHANGED
|
@@ -1,40 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekbeer/minion",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.32.0",
|
|
4
4
|
"description": "AI Agent runtime for Minion - manages status and skill deployment on VPS",
|
|
5
|
-
"main": "server.js",
|
|
5
|
+
"main": "linux/server.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"minion-cli": "./minion-cli.sh",
|
|
8
|
-
"hq": "./bin/hq"
|
|
7
|
+
"minion-cli": "./linux/minion-cli.sh",
|
|
8
|
+
"hq": "./linux/bin/hq",
|
|
9
|
+
"minion-cli-win": "./win/bin/minion-cli-win.js",
|
|
10
|
+
"hq-win": "./win/bin/hq-win.js"
|
|
9
11
|
},
|
|
10
12
|
"files": [
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"terminal-proxy.js",
|
|
15
|
-
"workflow-runner.js",
|
|
16
|
-
"workflow-store.js",
|
|
17
|
-
"routine-runner.js",
|
|
18
|
-
"routine-store.js",
|
|
19
|
-
"execution-store.js",
|
|
20
|
-
"chat-store.js",
|
|
21
|
-
"lib/",
|
|
22
|
-
"routes/",
|
|
13
|
+
"core/",
|
|
14
|
+
"linux/",
|
|
15
|
+
"win/",
|
|
23
16
|
"skills/",
|
|
24
17
|
"rules/",
|
|
25
18
|
"roles/",
|
|
26
19
|
"docs/",
|
|
27
20
|
"settings/",
|
|
28
|
-
"bin/",
|
|
29
|
-
"minion-cli.sh",
|
|
30
21
|
".env.example"
|
|
31
22
|
],
|
|
32
23
|
"scripts": {
|
|
33
|
-
"start": "node server.js"
|
|
24
|
+
"start": "node linux/server.js",
|
|
25
|
+
"start:win": "node win/server.js"
|
|
34
26
|
},
|
|
35
27
|
"dependencies": {
|
|
36
28
|
"croner": "^9.0.0",
|
|
37
|
-
"fastify": "^5.2.2"
|
|
29
|
+
"fastify": "^5.2.2",
|
|
30
|
+
"ws": "^8.0.0"
|
|
31
|
+
},
|
|
32
|
+
"optionalDependencies": {
|
|
33
|
+
"node-pty": "^1.0.0"
|
|
38
34
|
},
|
|
39
35
|
"engines": {
|
|
40
36
|
"node": ">=20.0.0"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Node.js wrapper to launch hq.ps1 via powershell.exe
|
|
3
|
+
// See minion-cli-win.js for rationale.
|
|
4
|
+
const { spawn } = require('child_process');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const child = spawn('powershell.exe', [
|
|
8
|
+
'-ExecutionPolicy', 'Bypass',
|
|
9
|
+
'-NoLogo',
|
|
10
|
+
'-File', path.join(__dirname, 'hq.ps1'),
|
|
11
|
+
...process.argv.slice(2)
|
|
12
|
+
], { stdio: 'inherit' });
|
|
13
|
+
|
|
14
|
+
child.on('exit', (code) => process.exit(code || 0));
|
|
15
|
+
child.on('error', (err) => {
|
|
16
|
+
console.error('Failed to start PowerShell:', err.message);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
});
|
package/win/bin/hq.ps1
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#Requires -Version 5.1
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
HQ API helper for minion chat context (Windows version)
|
|
5
|
+
|
|
6
|
+
.DESCRIPTION
|
|
7
|
+
Fetches resource details from the HQ server API.
|
|
8
|
+
Used by Claude CLI during chat to retrieve information about
|
|
9
|
+
skills, workflows, and projects that the user is viewing on the dashboard.
|
|
10
|
+
|
|
11
|
+
Environment variables (inherited from minion server):
|
|
12
|
+
HQ_URL - HQ server URL (e.g., https://minion-agent.com)
|
|
13
|
+
API_TOKEN - Minion API token for authentication
|
|
14
|
+
|
|
15
|
+
.EXAMPLE
|
|
16
|
+
.\hq.ps1 fetch skill <name>
|
|
17
|
+
.\hq.ps1 fetch workflow <name>
|
|
18
|
+
.\hq.ps1 fetch project <id>
|
|
19
|
+
.\hq.ps1 fetch project-context <id>
|
|
20
|
+
#>
|
|
21
|
+
|
|
22
|
+
param(
|
|
23
|
+
[Parameter(Position = 0)]
|
|
24
|
+
[string]$Command,
|
|
25
|
+
|
|
26
|
+
[Parameter(Position = 1)]
|
|
27
|
+
[string]$Resource,
|
|
28
|
+
|
|
29
|
+
[Parameter(Position = 2)]
|
|
30
|
+
[string]$Identifier
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
$ErrorActionPreference = 'Stop'
|
|
34
|
+
|
|
35
|
+
# Validate required environment variables
|
|
36
|
+
if (-not $env:HQ_URL) {
|
|
37
|
+
Write-Error "Error: HQ_URL is not set"
|
|
38
|
+
exit 1
|
|
39
|
+
}
|
|
40
|
+
if (-not $env:API_TOKEN) {
|
|
41
|
+
Write-Error "Error: API_TOKEN is not set"
|
|
42
|
+
exit 1
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
$BaseUrl = "$($env:HQ_URL)/api/minion"
|
|
46
|
+
$Headers = @{ 'Authorization' = "Bearer $($env:API_TOKEN)" }
|
|
47
|
+
|
|
48
|
+
function Invoke-HqApi {
|
|
49
|
+
param([string]$Url)
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
$response = Invoke-RestMethod -Uri $Url -Headers $Headers -Method Get -ErrorAction Stop
|
|
53
|
+
$response | ConvertTo-Json -Depth 10
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
$statusCode = $_.Exception.Response.StatusCode.value__
|
|
57
|
+
Write-Error "Error: HQ API returned HTTP $statusCode"
|
|
58
|
+
Write-Error $_.ErrorDetails.Message
|
|
59
|
+
exit 1
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
switch ($Command) {
|
|
64
|
+
'fetch' {
|
|
65
|
+
if (-not $Resource -or -not $Identifier) {
|
|
66
|
+
Write-Error "Usage: hq fetch {skill|workflow|project|project-context} <identifier>"
|
|
67
|
+
exit 1
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
switch ($Resource) {
|
|
71
|
+
'skill' {
|
|
72
|
+
Invoke-HqApi "$BaseUrl/skills/$Identifier"
|
|
73
|
+
}
|
|
74
|
+
'workflow' {
|
|
75
|
+
Invoke-HqApi "$BaseUrl/workflows/$Identifier"
|
|
76
|
+
}
|
|
77
|
+
'project' {
|
|
78
|
+
$response = Invoke-RestMethod -Uri "$BaseUrl/me/projects" -Headers $Headers -Method Get
|
|
79
|
+
$project = $response.projects | Where-Object { $_.id -eq $Identifier }
|
|
80
|
+
if ($project) {
|
|
81
|
+
$project | ConvertTo-Json -Depth 10
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
Write-Error "Project not found: $Identifier"
|
|
85
|
+
exit 1
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
'project-context' {
|
|
89
|
+
Invoke-HqApi "$BaseUrl/me/project/$Identifier/context"
|
|
90
|
+
}
|
|
91
|
+
default {
|
|
92
|
+
Write-Error "Unknown resource: $Resource"
|
|
93
|
+
Write-Error "Usage: hq fetch {skill|workflow|project|project-context} <identifier>"
|
|
94
|
+
exit 1
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
default {
|
|
99
|
+
Write-Host "HQ API helper for minion chat" -ForegroundColor Cyan
|
|
100
|
+
Write-Host ""
|
|
101
|
+
Write-Host "Usage:"
|
|
102
|
+
Write-Host " hq fetch skill <name> - Get skill details"
|
|
103
|
+
Write-Host " hq fetch workflow <name> - Get workflow details"
|
|
104
|
+
Write-Host " hq fetch project <id> - Get project info"
|
|
105
|
+
Write-Host " hq fetch project-context <id> - Get project context"
|
|
106
|
+
exit 1
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Node.js wrapper to launch minion-cli.ps1 via powershell.exe
|
|
3
|
+
// npm's cmd-shim cannot execute .ps1 files directly from cmd.exe because
|
|
4
|
+
// #Requires is not recognized as a shebang. This wrapper ensures the CLI
|
|
5
|
+
// works from any shell (cmd.exe, PowerShell, Git Bash).
|
|
6
|
+
const { spawn } = require('child_process');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const child = spawn('powershell.exe', [
|
|
10
|
+
'-ExecutionPolicy', 'Bypass',
|
|
11
|
+
'-NoLogo',
|
|
12
|
+
'-File', path.join(__dirname, '..', 'minion-cli.ps1'),
|
|
13
|
+
...process.argv.slice(2)
|
|
14
|
+
], { stdio: 'inherit' });
|
|
15
|
+
|
|
16
|
+
child.on('exit', (code) => process.exit(code || 0));
|
|
17
|
+
child.on('error', (err) => {
|
|
18
|
+
console.error('Failed to start PowerShell:', err.message);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
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 }
|