hydra-os-cli 0.1.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/README.md +274 -0
- package/dist/app.d.ts +12 -0
- package/dist/app.js +127 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +177 -0
- package/dist/clients/api.d.ts +115 -0
- package/dist/clients/api.js +123 -0
- package/dist/clients/qdrant.d.ts +39 -0
- package/dist/clients/qdrant.js +34 -0
- package/dist/clients/temporal.d.ts +37 -0
- package/dist/clients/temporal.js +32 -0
- package/dist/commands/agent.d.ts +4 -0
- package/dist/commands/agent.js +103 -0
- package/dist/commands/artifact.d.ts +4 -0
- package/dist/commands/artifact.js +42 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.js +80 -0
- package/dist/commands/core.d.ts +4 -0
- package/dist/commands/core.js +79 -0
- package/dist/commands/index.d.ts +6 -0
- package/dist/commands/index.js +20 -0
- package/dist/commands/memory.d.ts +4 -0
- package/dist/commands/memory.js +24 -0
- package/dist/commands/registry.d.ts +23 -0
- package/dist/commands/registry.js +23 -0
- package/dist/commands/session.d.ts +4 -0
- package/dist/commands/session.js +15 -0
- package/dist/commands/workflow.d.ts +5 -0
- package/dist/commands/workflow.js +301 -0
- package/dist/config.d.ts +152 -0
- package/dist/config.js +91 -0
- package/dist/screens/help.d.ts +5 -0
- package/dist/screens/help.js +14 -0
- package/dist/screens/main.d.ts +5 -0
- package/dist/screens/main.js +5 -0
- package/dist/screens/workflow-detail.d.ts +9 -0
- package/dist/screens/workflow-detail.js +11 -0
- package/dist/screens/workflow-list.d.ts +5 -0
- package/dist/screens/workflow-list.js +10 -0
- package/dist/sse.d.ts +16 -0
- package/dist/sse.js +197 -0
- package/dist/store.d.ts +100 -0
- package/dist/store.js +64 -0
- package/dist/widgets/agent-panel.d.ts +15 -0
- package/dist/widgets/agent-panel.js +23 -0
- package/dist/widgets/approval-modal.d.ts +16 -0
- package/dist/widgets/approval-modal.js +24 -0
- package/dist/widgets/artifact-tree.d.ts +14 -0
- package/dist/widgets/artifact-tree.js +9 -0
- package/dist/widgets/chat-panel.d.ts +10 -0
- package/dist/widgets/chat-panel.js +29 -0
- package/dist/widgets/header.d.ts +11 -0
- package/dist/widgets/header.js +14 -0
- package/dist/widgets/health-check.d.ts +15 -0
- package/dist/widgets/health-check.js +19 -0
- package/dist/widgets/input-bar.d.ts +9 -0
- package/dist/widgets/input-bar.js +37 -0
- package/dist/widgets/memory-panel.d.ts +11 -0
- package/dist/widgets/memory-panel.js +9 -0
- package/dist/widgets/status-bar.d.ts +13 -0
- package/dist/widgets/status-bar.js +24 -0
- package/dist/widgets/timeline.d.ts +26 -0
- package/dist/widgets/timeline.js +19 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# hydra-os-cli
|
|
2
|
+
|
|
3
|
+
Terminal user interface for the Hydra multi-agent workflow orchestrator. Monitor workflows, approve decisions, and manage agents from your terminal.
|
|
4
|
+
|
|
5
|
+
Built with [Ink](https://github.com/vadimdemedes/ink) (React for CLI), TypeScript, and Zustand.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g hydra-os-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or run without installing:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx hydra-os-cli doctor --json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- Node.js 18+
|
|
22
|
+
- Hydra API running on `http://localhost:7070` (or configured via `~/.hydra/config.yaml`)
|
|
23
|
+
- Temporal server running on `localhost:7233`
|
|
24
|
+
|
|
25
|
+
## CLI Usage
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
hydra-os Launch interactive TUI (default)
|
|
29
|
+
hydra-os --attach <workflow-id> Launch and attach to a running workflow
|
|
30
|
+
hydra-os --theme <name> Launch with a specific theme
|
|
31
|
+
hydra-os status <workflow-id> Check workflow status (non-interactive)
|
|
32
|
+
hydra-os status <workflow-id> --json Output status as JSON
|
|
33
|
+
hydra-os list List all workflows (non-interactive)
|
|
34
|
+
hydra-os list --json Output workflow list as JSON
|
|
35
|
+
hydra-os doctor Run service health checks (non-interactive)
|
|
36
|
+
hydra-os doctor --json Output health checks as JSON
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### JSON Output
|
|
40
|
+
|
|
41
|
+
All non-interactive commands support `--json` for scriptable output:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Pipe workflow list to jq
|
|
45
|
+
hydra-os list --json | jq '.[].status'
|
|
46
|
+
|
|
47
|
+
# Check a specific workflow programmatically
|
|
48
|
+
hydra-os status wf-abc123 --json | jq '.status.phase'
|
|
49
|
+
|
|
50
|
+
# Health check in CI
|
|
51
|
+
hydra-os doctor --json | jq '.api.status'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Examples
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Start the interactive TUI
|
|
58
|
+
hydra-os
|
|
59
|
+
|
|
60
|
+
# Attach to a workflow on launch
|
|
61
|
+
hydra-os --attach wf-abc123
|
|
62
|
+
|
|
63
|
+
# Check a workflow from the command line without entering the TUI
|
|
64
|
+
hydra-os status wf-abc123
|
|
65
|
+
|
|
66
|
+
# List all workflows
|
|
67
|
+
hydra-os list
|
|
68
|
+
|
|
69
|
+
# Health check all services
|
|
70
|
+
hydra-os doctor
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Development Mode
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
cd apps/tui
|
|
77
|
+
npm install
|
|
78
|
+
npm run dev
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Interactive Commands
|
|
82
|
+
|
|
83
|
+
Once inside the TUI, type slash commands in the input bar. Press `Tab` to autocomplete.
|
|
84
|
+
|
|
85
|
+
### Core
|
|
86
|
+
|
|
87
|
+
| Command | Description |
|
|
88
|
+
|------------|------------------------------------------------|
|
|
89
|
+
| `/help` | Show all available commands |
|
|
90
|
+
| `/clear` | Clear chat output |
|
|
91
|
+
| `/doctor` | Health check (API, Temporal, Qdrant, workers) |
|
|
92
|
+
|
|
93
|
+
### Workflow
|
|
94
|
+
|
|
95
|
+
| Command | Description |
|
|
96
|
+
|----------------------------------|--------------------------------------|
|
|
97
|
+
| `/start <description>` | Start a new workflow from natural language |
|
|
98
|
+
| `/status [workflow-id]` | Show workflow status (defaults to active) |
|
|
99
|
+
| `/list` | List all workflows |
|
|
100
|
+
| `/attach <workflow-id>` | Attach to a workflow's live SSE stream |
|
|
101
|
+
| `/detach` | Detach from live stream |
|
|
102
|
+
| `/approve [workflow-id]` | Approve pending approval |
|
|
103
|
+
| `/deny [workflow-id] [feedback]` | Deny pending approval with optional feedback |
|
|
104
|
+
| `/pause [workflow-id]` | Pause a running workflow |
|
|
105
|
+
| `/resume [workflow-id]` | Resume a paused workflow |
|
|
106
|
+
| `/cancel confirm [workflow-id]` | Cancel a workflow (requires `confirm`) |
|
|
107
|
+
|
|
108
|
+
### Agent
|
|
109
|
+
|
|
110
|
+
| Command | Description |
|
|
111
|
+
|------------------|--------------------------------------|
|
|
112
|
+
| `/agents` | List all agent roles and their status |
|
|
113
|
+
| `/agent <role>` | Show details for a specific agent role |
|
|
114
|
+
|
|
115
|
+
### Config
|
|
116
|
+
|
|
117
|
+
| Command | Description |
|
|
118
|
+
|-----------|----------------------------------------|
|
|
119
|
+
| `/roles` | Show role catalog from config/roles.yaml |
|
|
120
|
+
|
|
121
|
+
## Keyboard Shortcuts
|
|
122
|
+
|
|
123
|
+
| Key | Action |
|
|
124
|
+
|----------|----------------------|
|
|
125
|
+
| `Ctrl+P` | Toggle side panels |
|
|
126
|
+
| `Ctrl+D` | Exit |
|
|
127
|
+
| `Tab` | Autocomplete command |
|
|
128
|
+
|
|
129
|
+
## Layout
|
|
130
|
+
|
|
131
|
+
The TUI uses a split-pane layout:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
┌──────────────────────────────────────────────────────────┐
|
|
135
|
+
│ Hydra TUI v0.1.0 ● IDLE │
|
|
136
|
+
├───────────────────────────────────┬──────────────────────┤
|
|
137
|
+
│ │ Agents │
|
|
138
|
+
│ Chat / Command Output │ ○ pm.prd pending │
|
|
139
|
+
│ │ ⏳ eng.init running │
|
|
140
|
+
│ │ ... │
|
|
141
|
+
│ ├──────────────────────┤
|
|
142
|
+
│ │ Artifacts │
|
|
143
|
+
│ │ 📄 prd.md │
|
|
144
|
+
│ ├──────────────────────┤
|
|
145
|
+
│ │ Memory │
|
|
146
|
+
│ │ 3 decisions, 7 facts│
|
|
147
|
+
├───────────────────────────────────┴──────────────────────┤
|
|
148
|
+
│ > /start Build a REST API for user auth │
|
|
149
|
+
├──────────────────────────────────────────────────────────┤
|
|
150
|
+
│ no workflow — tokens: 0 $0.00 0s │
|
|
151
|
+
└──────────────────────────────────────────────────────────┘
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Toggle the right panel with `Ctrl+P` for a full-width chat view.
|
|
155
|
+
|
|
156
|
+
## Configuration
|
|
157
|
+
|
|
158
|
+
The TUI reads configuration from `~/.hydra/config.yaml` with sensible defaults. No config file is required — everything works out of the box for local development.
|
|
159
|
+
|
|
160
|
+
### Config File
|
|
161
|
+
|
|
162
|
+
Create `~/.hydra/config.yaml`:
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
temporal:
|
|
166
|
+
address: localhost:7233
|
|
167
|
+
namespace: default
|
|
168
|
+
|
|
169
|
+
api:
|
|
170
|
+
url: http://localhost:7070
|
|
171
|
+
|
|
172
|
+
qdrant:
|
|
173
|
+
url: http://localhost:6333
|
|
174
|
+
|
|
175
|
+
tui:
|
|
176
|
+
theme: dark # dark, light, solarized, monokai, hydra
|
|
177
|
+
layout: split # split, full
|
|
178
|
+
rightPanel: true
|
|
179
|
+
vimMode: false
|
|
180
|
+
maxHistory: 1000 # max chat messages kept in memory
|
|
181
|
+
spinnerStyle: dots # dots, line, arc, bounce
|
|
182
|
+
|
|
183
|
+
workflow:
|
|
184
|
+
defaultModel: claude-sonnet-4-5-20250929
|
|
185
|
+
autoApproveReads: true
|
|
186
|
+
qualityThreshold: 28
|
|
187
|
+
costWarning: 5.0 # warn when workflow cost exceeds this ($)
|
|
188
|
+
|
|
189
|
+
notifications:
|
|
190
|
+
sound: true
|
|
191
|
+
desktop: true
|
|
192
|
+
idleAlert: 300 # seconds before idle alert
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Environment Variable Overrides
|
|
196
|
+
|
|
197
|
+
Environment variables take precedence over the config file:
|
|
198
|
+
|
|
199
|
+
| Variable | Overrides | Example |
|
|
200
|
+
|--------------------|-------------------|------------------------------|
|
|
201
|
+
| `TEMPORAL_ADDRESS` | `temporal.address` | `localhost:7233` |
|
|
202
|
+
| `HYDRA_API_URL` | `api.url` | `http://localhost:7070` |
|
|
203
|
+
| `QDRANT_URL` | `qdrant.url` | `http://localhost:6333` |
|
|
204
|
+
| `HYDRA_THEME` | `tui.theme` | `dark` |
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# Example: connect to a remote API
|
|
208
|
+
HYDRA_API_URL=http://192.168.1.50:7070 npm run dev
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Development
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# Typecheck
|
|
215
|
+
npm run typecheck
|
|
216
|
+
|
|
217
|
+
# Run tests
|
|
218
|
+
npm test
|
|
219
|
+
|
|
220
|
+
# Build to dist/
|
|
221
|
+
npm run build
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Project Structure
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
src/
|
|
228
|
+
cli.tsx CLI entry point (Commander)
|
|
229
|
+
app.tsx Main Ink application component
|
|
230
|
+
store.ts Zustand state store
|
|
231
|
+
config.ts Config loading and validation (Zod)
|
|
232
|
+
sse.ts SSE streaming with reconnection
|
|
233
|
+
clients/
|
|
234
|
+
api.ts REST API client
|
|
235
|
+
temporal.ts Temporal client (stub)
|
|
236
|
+
qdrant.ts Qdrant client (stub)
|
|
237
|
+
commands/
|
|
238
|
+
registry.ts Command registry with autocomplete
|
|
239
|
+
index.ts Command registration
|
|
240
|
+
core.ts /help, /clear, /doctor
|
|
241
|
+
workflow.ts /start, /status, /list, /approve, ...
|
|
242
|
+
agent.ts /agents, /agent
|
|
243
|
+
config.ts /roles, /config, /theme
|
|
244
|
+
artifact.ts Artifact commands (Phase 3)
|
|
245
|
+
memory.ts Memory commands (Phase 3)
|
|
246
|
+
session.ts Session commands (Phase 4)
|
|
247
|
+
widgets/
|
|
248
|
+
header.tsx Header bar
|
|
249
|
+
chat-panel.tsx Chat message display (windowed)
|
|
250
|
+
agent-panel.tsx Agent status panel
|
|
251
|
+
artifact-tree.tsx Artifact tree view
|
|
252
|
+
memory-panel.tsx Memory stats panel
|
|
253
|
+
input-bar.tsx Input with Tab autocomplete
|
|
254
|
+
status-bar.tsx Footer status bar
|
|
255
|
+
timeline.tsx Workflow timeline
|
|
256
|
+
health-check.tsx Health check display
|
|
257
|
+
approval-modal.tsx Approval modal
|
|
258
|
+
screens/
|
|
259
|
+
main.tsx Main screen (re-exports App)
|
|
260
|
+
help.tsx Help screen
|
|
261
|
+
workflow-detail.tsx Workflow detail view
|
|
262
|
+
workflow-list.tsx Workflow list view
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Typical Workflow
|
|
266
|
+
|
|
267
|
+
1. Launch the TUI: `npm run dev`
|
|
268
|
+
2. Check services are healthy: `/doctor`
|
|
269
|
+
3. Start a workflow: `/start Build a user authentication system with JWT`
|
|
270
|
+
4. Watch progress via the agent panel and chat stream
|
|
271
|
+
5. Attach to live updates if not already streaming: `/attach <workflow-id>`
|
|
272
|
+
6. Approve when prompted: `/approve`
|
|
273
|
+
7. Check final status: `/status`
|
|
274
|
+
8. List all workflows: `/list`
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main Hydra TUI application.
|
|
3
|
+
*
|
|
4
|
+
* Renders the split-pane layout with chat panel, agent panel,
|
|
5
|
+
* artifact tree, memory panel, input bar, and status bar.
|
|
6
|
+
*/
|
|
7
|
+
interface AppProps {
|
|
8
|
+
attachWorkflow?: string;
|
|
9
|
+
themeName?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function App({ attachWorkflow, themeName }: AppProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Main Hydra TUI application.
|
|
4
|
+
*
|
|
5
|
+
* Renders the split-pane layout with chat panel, agent panel,
|
|
6
|
+
* artifact tree, memory panel, input bar, and status bar.
|
|
7
|
+
*/
|
|
8
|
+
import { useState, useEffect, useRef, useCallback } from "react";
|
|
9
|
+
import { Box, useApp, useInput } from "ink";
|
|
10
|
+
import { Header } from "./widgets/header.js";
|
|
11
|
+
import { ChatPanel } from "./widgets/chat-panel.js";
|
|
12
|
+
import { AgentPanel } from "./widgets/agent-panel.js";
|
|
13
|
+
import { ArtifactTree } from "./widgets/artifact-tree.js";
|
|
14
|
+
import { MemoryPanel } from "./widgets/memory-panel.js";
|
|
15
|
+
import { InputBar } from "./widgets/input-bar.js";
|
|
16
|
+
import { StatusBar } from "./widgets/status-bar.js";
|
|
17
|
+
import { useStore } from "./store.js";
|
|
18
|
+
import { loadConfig } from "./config.js";
|
|
19
|
+
import { getApiClient } from "./clients/api.js";
|
|
20
|
+
import { registerAllCommands, getCommand } from "./commands/index.js";
|
|
21
|
+
function toDisplayStatus(apiStatus) {
|
|
22
|
+
switch (apiStatus) {
|
|
23
|
+
case "active": return "running";
|
|
24
|
+
case "idle": return "pending";
|
|
25
|
+
case "completed": return "done";
|
|
26
|
+
case "waiting": return "waiting";
|
|
27
|
+
case "error":
|
|
28
|
+
case "failed": return "failed";
|
|
29
|
+
default: return "pending";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function App({ attachWorkflow, themeName = "dark" }) {
|
|
33
|
+
const { exit } = useApp();
|
|
34
|
+
const [showRightPanel, setShowRightPanel] = useState(true);
|
|
35
|
+
const apiRef = useRef(null);
|
|
36
|
+
const commandsRegistered = useRef(false);
|
|
37
|
+
// Zustand selectors
|
|
38
|
+
const messages = useStore((s) => s.messages);
|
|
39
|
+
const activeWorkflowId = useStore((s) => s.activeWorkflowId);
|
|
40
|
+
const agents = useStore((s) => s.agents);
|
|
41
|
+
const activeWorkflow = useStore((s) => s.activeWorkflow);
|
|
42
|
+
const apiStatus = useStore((s) => s.apiStatus);
|
|
43
|
+
// Initialize on mount
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const config = loadConfig();
|
|
46
|
+
const store = useStore.getState();
|
|
47
|
+
store.setApiUrl(config.api.url);
|
|
48
|
+
apiRef.current = getApiClient(config.api.url);
|
|
49
|
+
if (!commandsRegistered.current) {
|
|
50
|
+
registerAllCommands();
|
|
51
|
+
commandsRegistered.current = true;
|
|
52
|
+
}
|
|
53
|
+
// Initial health check
|
|
54
|
+
store.setApiStatus("connecting");
|
|
55
|
+
apiRef.current.healthCheck().then(() => store.setApiStatus("connected"), () => store.setApiStatus("error"));
|
|
56
|
+
// Cleanup SSE on unmount
|
|
57
|
+
return () => {
|
|
58
|
+
useStore.getState().abortAllSse();
|
|
59
|
+
};
|
|
60
|
+
}, []);
|
|
61
|
+
// Attach to workflow when prop changes
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (attachWorkflow) {
|
|
64
|
+
useStore.getState().setActiveWorkflowId(attachWorkflow);
|
|
65
|
+
}
|
|
66
|
+
}, [attachWorkflow]);
|
|
67
|
+
useInput((input, key) => {
|
|
68
|
+
if (key.ctrl && input === "p") {
|
|
69
|
+
setShowRightPanel((prev) => !prev);
|
|
70
|
+
}
|
|
71
|
+
if (key.ctrl && input === "d") {
|
|
72
|
+
useStore.getState().abortAllSse();
|
|
73
|
+
exit();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
const handleCommand = useCallback(async (text) => {
|
|
77
|
+
const store = useStore.getState();
|
|
78
|
+
const api = apiRef.current ?? getApiClient();
|
|
79
|
+
if (text.startsWith("/")) {
|
|
80
|
+
const parts = text.slice(1).split(/\s+/);
|
|
81
|
+
const cmdName = parts[0];
|
|
82
|
+
const args = parts.slice(1);
|
|
83
|
+
const cmd = getCommand(cmdName);
|
|
84
|
+
if (!cmd) {
|
|
85
|
+
store.addMessage({ role: "error", content: `Unknown command: /${cmdName}. Type /help for available commands.` });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
store.addMessage({ role: "user", content: text });
|
|
89
|
+
try {
|
|
90
|
+
await cmd.handler({ store, api, args });
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
94
|
+
store.addMessage({ role: "error", content: `Command error: ${msg}` });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
store.addMessage({ role: "user", content: text });
|
|
99
|
+
store.addMessage({
|
|
100
|
+
role: "system",
|
|
101
|
+
content: "Use /start <description> to start a workflow, or /help for all commands.",
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}, []);
|
|
105
|
+
// Derive agent panel data with safe status mapping
|
|
106
|
+
const agentStatuses = agents.map((a) => ({
|
|
107
|
+
role: a.role_id,
|
|
108
|
+
status: toDisplayStatus(a.status),
|
|
109
|
+
duration: undefined,
|
|
110
|
+
}));
|
|
111
|
+
// Derive artifact data
|
|
112
|
+
const artifacts = (activeWorkflow?.artifacts ?? []).map((a) => ({
|
|
113
|
+
path: typeof a === "object" ? a.path ?? "" : String(a),
|
|
114
|
+
type: "file",
|
|
115
|
+
}));
|
|
116
|
+
// Derive workflow status for header
|
|
117
|
+
const workflowStatus = activeWorkflowId
|
|
118
|
+
? (apiStatus === "connected" ? "RUNNING" : "WAITING")
|
|
119
|
+
: "IDLE";
|
|
120
|
+
// Derive metrics for status bar
|
|
121
|
+
const metrics = activeWorkflow?.metrics;
|
|
122
|
+
const tokenCount = Number(metrics?.total_tokens ?? 0);
|
|
123
|
+
const cost = Number(metrics?.total_cost ?? 0);
|
|
124
|
+
const elapsed = Number(metrics?.total_duration_minutes ?? 0) * 60;
|
|
125
|
+
const currentAgent = agents.find((a) => a.status === "active")?.role_id;
|
|
126
|
+
return (_jsxs(Box, { flexDirection: "column", width: "100%", height: "100%", children: [_jsx(Header, { workflowId: activeWorkflowId ?? undefined, workflowStatus: workflowStatus, themeName: themeName }), _jsxs(Box, { flexGrow: 1, flexDirection: "row", children: [_jsx(Box, { flexGrow: 1, flexDirection: "column", borderStyle: "single", children: _jsx(ChatPanel, { messages: messages }) }), showRightPanel && (_jsxs(Box, { width: 30, flexDirection: "column", children: [_jsx(Box, { borderStyle: "single", flexGrow: 1, children: _jsx(AgentPanel, { workflowId: activeWorkflowId ?? undefined, agents: agentStatuses }) }), _jsx(Box, { borderStyle: "single", height: 8, children: _jsx(ArtifactTree, { workflowId: activeWorkflowId ?? undefined, artifacts: artifacts }) }), _jsx(Box, { borderStyle: "single", height: 6, children: _jsx(MemoryPanel, {}) })] }))] }), _jsx(InputBar, { onSubmit: handleCommand }), _jsx(StatusBar, { workflowId: activeWorkflowId ?? undefined, currentAgent: currentAgent, tokenCount: tokenCount, cost: cost, elapsed: elapsed })] }));
|
|
127
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point for the Hydra TUI.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* hydra Interactive TUI (default)
|
|
7
|
+
* hydra --attach wf-abc123 Attach to a running workflow
|
|
8
|
+
* hydra exec "Build auth system" Start workflow non-interactively
|
|
9
|
+
* hydra status [workflow-id] Check workflow status
|
|
10
|
+
* hydra list List all workflows
|
|
11
|
+
* hydra doctor Health check
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* CLI entry point for the Hydra TUI.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* hydra Interactive TUI (default)
|
|
8
|
+
* hydra --attach wf-abc123 Attach to a running workflow
|
|
9
|
+
* hydra exec "Build auth system" Start workflow non-interactively
|
|
10
|
+
* hydra status [workflow-id] Check workflow status
|
|
11
|
+
* hydra list List all workflows
|
|
12
|
+
* hydra doctor Health check
|
|
13
|
+
*/
|
|
14
|
+
import { Command } from "commander";
|
|
15
|
+
import { render } from "ink";
|
|
16
|
+
import { App } from "./app.js";
|
|
17
|
+
import { loadConfig } from "./config.js";
|
|
18
|
+
import { getApiClient } from "./clients/api.js";
|
|
19
|
+
function createApiFromConfig() {
|
|
20
|
+
const config = loadConfig();
|
|
21
|
+
return getApiClient(config.api.url);
|
|
22
|
+
}
|
|
23
|
+
const program = new Command();
|
|
24
|
+
program
|
|
25
|
+
.name("hydra")
|
|
26
|
+
.description("Hydra TUI — multi-agent workflow orchestrator")
|
|
27
|
+
.version("0.1.0")
|
|
28
|
+
.option("--attach <workflow-id>", "Attach to a running workflow")
|
|
29
|
+
.option("--theme <name>", "Theme (dark, light, solarized, monokai, hydra)")
|
|
30
|
+
.action(async (opts) => {
|
|
31
|
+
const instance = render(_jsx(App, { attachWorkflow: opts.attach, themeName: opts.theme ?? "dark" }));
|
|
32
|
+
await instance.waitUntilExit();
|
|
33
|
+
});
|
|
34
|
+
program
|
|
35
|
+
.command("exec <description>")
|
|
36
|
+
.description("Start a workflow non-interactively and print results")
|
|
37
|
+
.action((_description) => {
|
|
38
|
+
console.log("Non-interactive mode not yet implemented.");
|
|
39
|
+
process.exit(0);
|
|
40
|
+
});
|
|
41
|
+
program
|
|
42
|
+
.command("status [workflow-id]")
|
|
43
|
+
.description("Show workflow status")
|
|
44
|
+
.option("--json", "Output as JSON")
|
|
45
|
+
.action(async (workflowId, opts) => {
|
|
46
|
+
const api = createApiFromConfig();
|
|
47
|
+
if (!workflowId) {
|
|
48
|
+
console.error("Usage: hydra status <workflow-id>");
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const resp = await api.getWorkflowStatus(workflowId);
|
|
53
|
+
if (opts.json) {
|
|
54
|
+
console.log(JSON.stringify(resp, null, 2));
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
const status = resp.status;
|
|
58
|
+
console.log(`Workflow: ${workflowId}`);
|
|
59
|
+
if (typeof status === "object" && status !== null) {
|
|
60
|
+
const s = status;
|
|
61
|
+
if (s.phase)
|
|
62
|
+
console.log(`Phase: ${s.phase}`);
|
|
63
|
+
if (s.current_phase)
|
|
64
|
+
console.log(`Current phase: ${s.current_phase}`);
|
|
65
|
+
if (s.waiting_for_approval)
|
|
66
|
+
console.log("** Waiting for approval **");
|
|
67
|
+
if (s.total_cost != null)
|
|
68
|
+
console.log(`Cost: $${Number(s.total_cost).toFixed(4)}`);
|
|
69
|
+
if (Array.isArray(s.steps)) {
|
|
70
|
+
console.log("\nSteps:");
|
|
71
|
+
for (const step of s.steps) {
|
|
72
|
+
const st = step;
|
|
73
|
+
const icon = st.status === "completed" ? " ✓" : st.status === "running" ? " ⏳" : " ○";
|
|
74
|
+
console.log(`${icon} ${st.role ?? st.name ?? "step"} [${st.status}]`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.log(`Status: ${JSON.stringify(status, null, 2)}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
84
|
+
console.error(`Error: ${msg}`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
process.exit(0);
|
|
88
|
+
});
|
|
89
|
+
program
|
|
90
|
+
.command("list")
|
|
91
|
+
.description("List all workflows")
|
|
92
|
+
.option("--json", "Output as JSON")
|
|
93
|
+
.action(async (opts) => {
|
|
94
|
+
const api = createApiFromConfig();
|
|
95
|
+
try {
|
|
96
|
+
const tasks = await api.listTasks({ limit: 50 });
|
|
97
|
+
if (opts.json) {
|
|
98
|
+
console.log(JSON.stringify(tasks, null, 2));
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
if (tasks.length === 0) {
|
|
102
|
+
console.log("No workflows found.");
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
console.log("ID | Status | Type | Step");
|
|
106
|
+
console.log("-".repeat(80));
|
|
107
|
+
for (const t of tasks) {
|
|
108
|
+
const id = t.workflow_id.slice(0, 28).padEnd(28);
|
|
109
|
+
const status = (t.status ?? "unknown").padEnd(11);
|
|
110
|
+
const type = (t.workflow_type ?? "").slice(0, 16).padEnd(16);
|
|
111
|
+
const step = t.current_step ?? "";
|
|
112
|
+
console.log(`${id} | ${status} | ${type} | ${step}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
117
|
+
console.error(`Error: ${msg}`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
process.exit(0);
|
|
121
|
+
});
|
|
122
|
+
program
|
|
123
|
+
.command("doctor")
|
|
124
|
+
.description("Run health checks on all Hydra services")
|
|
125
|
+
.option("--json", "Output as JSON")
|
|
126
|
+
.action(async (opts) => {
|
|
127
|
+
const api = createApiFromConfig();
|
|
128
|
+
const checks = {};
|
|
129
|
+
let exitCode = 0;
|
|
130
|
+
try {
|
|
131
|
+
const health = await api.healthCheck();
|
|
132
|
+
checks.api = { status: health.status ?? "ok", version: health.version ?? null };
|
|
133
|
+
try {
|
|
134
|
+
const agentsResp = await api.getAgents();
|
|
135
|
+
checks.temporal = { status: "connected", roles: agentsResp.agents.length };
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
checks.temporal = { status: "unreachable" };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
143
|
+
checks.api = { status: "unreachable", error: msg };
|
|
144
|
+
exitCode = 1;
|
|
145
|
+
}
|
|
146
|
+
if (opts.json) {
|
|
147
|
+
console.log(JSON.stringify(checks, null, 2));
|
|
148
|
+
process.exit(exitCode);
|
|
149
|
+
}
|
|
150
|
+
const apiCheck = checks.api;
|
|
151
|
+
if (apiCheck.status === "unreachable") {
|
|
152
|
+
console.error(`API: unreachable (${apiCheck.error})`);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
console.log(`API: ${apiCheck.status}`);
|
|
156
|
+
if (apiCheck.version)
|
|
157
|
+
console.log(`Version: ${apiCheck.version}`);
|
|
158
|
+
}
|
|
159
|
+
const temporalCheck = checks.temporal;
|
|
160
|
+
if (temporalCheck) {
|
|
161
|
+
if (temporalCheck.status === "connected") {
|
|
162
|
+
console.log(`Temporal: connected (${temporalCheck.roles} roles)`);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
console.log("Temporal: unreachable");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
process.exit(exitCode);
|
|
169
|
+
});
|
|
170
|
+
program
|
|
171
|
+
.command("resume [session-name]")
|
|
172
|
+
.description("Resume a saved TUI session")
|
|
173
|
+
.action((_sessionName) => {
|
|
174
|
+
console.log("Resume command not yet implemented.");
|
|
175
|
+
process.exit(0);
|
|
176
|
+
});
|
|
177
|
+
program.parse();
|