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.
Files changed (64) hide show
  1. package/README.md +274 -0
  2. package/dist/app.d.ts +12 -0
  3. package/dist/app.js +127 -0
  4. package/dist/cli.d.ts +13 -0
  5. package/dist/cli.js +177 -0
  6. package/dist/clients/api.d.ts +115 -0
  7. package/dist/clients/api.js +123 -0
  8. package/dist/clients/qdrant.d.ts +39 -0
  9. package/dist/clients/qdrant.js +34 -0
  10. package/dist/clients/temporal.d.ts +37 -0
  11. package/dist/clients/temporal.js +32 -0
  12. package/dist/commands/agent.d.ts +4 -0
  13. package/dist/commands/agent.js +103 -0
  14. package/dist/commands/artifact.d.ts +4 -0
  15. package/dist/commands/artifact.js +42 -0
  16. package/dist/commands/config.d.ts +4 -0
  17. package/dist/commands/config.js +80 -0
  18. package/dist/commands/core.d.ts +4 -0
  19. package/dist/commands/core.js +79 -0
  20. package/dist/commands/index.d.ts +6 -0
  21. package/dist/commands/index.js +20 -0
  22. package/dist/commands/memory.d.ts +4 -0
  23. package/dist/commands/memory.js +24 -0
  24. package/dist/commands/registry.d.ts +23 -0
  25. package/dist/commands/registry.js +23 -0
  26. package/dist/commands/session.d.ts +4 -0
  27. package/dist/commands/session.js +15 -0
  28. package/dist/commands/workflow.d.ts +5 -0
  29. package/dist/commands/workflow.js +301 -0
  30. package/dist/config.d.ts +152 -0
  31. package/dist/config.js +91 -0
  32. package/dist/screens/help.d.ts +5 -0
  33. package/dist/screens/help.js +14 -0
  34. package/dist/screens/main.d.ts +5 -0
  35. package/dist/screens/main.js +5 -0
  36. package/dist/screens/workflow-detail.d.ts +9 -0
  37. package/dist/screens/workflow-detail.js +11 -0
  38. package/dist/screens/workflow-list.d.ts +5 -0
  39. package/dist/screens/workflow-list.js +10 -0
  40. package/dist/sse.d.ts +16 -0
  41. package/dist/sse.js +197 -0
  42. package/dist/store.d.ts +100 -0
  43. package/dist/store.js +64 -0
  44. package/dist/widgets/agent-panel.d.ts +15 -0
  45. package/dist/widgets/agent-panel.js +23 -0
  46. package/dist/widgets/approval-modal.d.ts +16 -0
  47. package/dist/widgets/approval-modal.js +24 -0
  48. package/dist/widgets/artifact-tree.d.ts +14 -0
  49. package/dist/widgets/artifact-tree.js +9 -0
  50. package/dist/widgets/chat-panel.d.ts +10 -0
  51. package/dist/widgets/chat-panel.js +29 -0
  52. package/dist/widgets/header.d.ts +11 -0
  53. package/dist/widgets/header.js +14 -0
  54. package/dist/widgets/health-check.d.ts +15 -0
  55. package/dist/widgets/health-check.js +19 -0
  56. package/dist/widgets/input-bar.d.ts +9 -0
  57. package/dist/widgets/input-bar.js +37 -0
  58. package/dist/widgets/memory-panel.d.ts +11 -0
  59. package/dist/widgets/memory-panel.js +9 -0
  60. package/dist/widgets/status-bar.d.ts +13 -0
  61. package/dist/widgets/status-bar.js +24 -0
  62. package/dist/widgets/timeline.d.ts +26 -0
  63. package/dist/widgets/timeline.js +19 -0
  64. 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();