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
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Chat panel widget.
|
|
4
|
+
* Displays streaming output from agents, approval prompts, and user messages.
|
|
5
|
+
*/
|
|
6
|
+
import { Box, Text } from "ink";
|
|
7
|
+
const ROLE_COLORS = {
|
|
8
|
+
user: "blue",
|
|
9
|
+
agent: "green",
|
|
10
|
+
system: "yellow",
|
|
11
|
+
error: "red",
|
|
12
|
+
};
|
|
13
|
+
const ROLE_PREFIXES = {
|
|
14
|
+
user: () => "> ",
|
|
15
|
+
agent: (msg) => `[${msg.agent ?? "agent"}] `,
|
|
16
|
+
system: () => "! ",
|
|
17
|
+
error: () => "!! ",
|
|
18
|
+
};
|
|
19
|
+
const MAX_VISIBLE_MESSAGES = 100;
|
|
20
|
+
export function ChatPanel({ messages = [] }) {
|
|
21
|
+
if (messages.length === 0) {
|
|
22
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { dimColor: true, children: "Welcome to Hydra TUI. Type a command or describe a workflow to begin." }), _jsx(Text, { dimColor: true, children: "Use /help for available commands. Ctrl+P to toggle side panels." })] }));
|
|
23
|
+
}
|
|
24
|
+
// Only render the last N messages to avoid full terminal rewrite on each update
|
|
25
|
+
const visible = messages.length > MAX_VISIBLE_MESSAGES
|
|
26
|
+
? messages.slice(-MAX_VISIBLE_MESSAGES)
|
|
27
|
+
: messages;
|
|
28
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [messages.length > MAX_VISIBLE_MESSAGES && (_jsxs(Text, { dimColor: true, children: ["(", messages.length - MAX_VISIBLE_MESSAGES, " older messages hidden)"] })), visible.map((msg) => (_jsxs(Box, { marginBottom: 0, children: [_jsx(Text, { color: ROLE_COLORS[msg.role] ?? "white", children: (ROLE_PREFIXES[msg.role] ?? (() => ""))(msg) }), _jsx(Text, { wrap: "wrap", children: msg.content })] }, msg.id)))] }));
|
|
29
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header bar widget.
|
|
3
|
+
* Shows app title, version, active workflow name, and workflow state.
|
|
4
|
+
*/
|
|
5
|
+
interface HeaderProps {
|
|
6
|
+
workflowId?: string;
|
|
7
|
+
workflowStatus: string;
|
|
8
|
+
themeName: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function Header({ workflowId, workflowStatus, themeName: _themeName }: HeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Header bar widget.
|
|
4
|
+
* Shows app title, version, active workflow name, and workflow state.
|
|
5
|
+
*/
|
|
6
|
+
import { Box, Text } from "ink";
|
|
7
|
+
export function Header({ workflowId, workflowStatus, themeName: _themeName }) {
|
|
8
|
+
const statusColor = workflowStatus === "RUNNING"
|
|
9
|
+
? "green"
|
|
10
|
+
: workflowStatus === "WAITING"
|
|
11
|
+
? "yellow"
|
|
12
|
+
: "gray";
|
|
13
|
+
return (_jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: "cyan", children: "Hydra TUI v0.1.0" }), _jsxs(Box, { gap: 2, children: [workflowId && (_jsxs(Text, { children: ["workflow: ", _jsx(Text, { bold: true, children: workflowId })] })), _jsx(Text, { color: statusColor, children: workflowStatus === "IDLE" ? "● IDLE" : `▶ ${workflowStatus}` })] })] }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health check output widget.
|
|
3
|
+
* Displays /doctor results for Temporal, API, Qdrant, Docker, workers, etc.
|
|
4
|
+
*/
|
|
5
|
+
interface ServiceHealth {
|
|
6
|
+
name: string;
|
|
7
|
+
address: string;
|
|
8
|
+
status: "ok" | "warn" | "error";
|
|
9
|
+
detail: string;
|
|
10
|
+
}
|
|
11
|
+
interface HealthCheckProps {
|
|
12
|
+
services: ServiceHealth[];
|
|
13
|
+
}
|
|
14
|
+
export declare function HealthCheck({ services }: HealthCheckProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Health check output widget.
|
|
4
|
+
* Displays /doctor results for Temporal, API, Qdrant, Docker, workers, etc.
|
|
5
|
+
*/
|
|
6
|
+
import { Box, Text } from "ink";
|
|
7
|
+
const STATUS_ICONS = {
|
|
8
|
+
ok: "✓",
|
|
9
|
+
warn: "!",
|
|
10
|
+
error: "✗",
|
|
11
|
+
};
|
|
12
|
+
const STATUS_COLORS = {
|
|
13
|
+
ok: "green",
|
|
14
|
+
warn: "yellow",
|
|
15
|
+
error: "red",
|
|
16
|
+
};
|
|
17
|
+
export function HealthCheck({ services }) {
|
|
18
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, children: "Hydra TUI Health Check" }), _jsx(Text, { children: "─".repeat(30) }), services.map((svc) => (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: STATUS_COLORS[svc.status], children: STATUS_ICONS[svc.status] }), _jsx(Text, { bold: true, children: svc.name.padEnd(14) }), _jsx(Text, { children: svc.address.padEnd(20) }), _jsx(Text, { dimColor: true, children: svc.detail })] }, svc.name)))] }));
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input bar widget.
|
|
3
|
+
* User prompt input with slash command autocomplete.
|
|
4
|
+
*/
|
|
5
|
+
interface InputBarProps {
|
|
6
|
+
onSubmit: (text: string) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function InputBar({ onSubmit }: InputBarProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Input bar widget.
|
|
4
|
+
* User prompt input with slash command autocomplete.
|
|
5
|
+
*/
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
import { Box, Text, useInput } from "ink";
|
|
8
|
+
import TextInput from "ink-text-input";
|
|
9
|
+
import { getCompletions } from "../commands/index.js";
|
|
10
|
+
export function InputBar({ onSubmit }) {
|
|
11
|
+
const [value, setValue] = useState("");
|
|
12
|
+
const [completions, setCompletions] = useState([]);
|
|
13
|
+
const handleChange = (text) => {
|
|
14
|
+
setValue(text);
|
|
15
|
+
if (text.startsWith("/") && !text.includes(" ")) {
|
|
16
|
+
setCompletions(getCompletions(text).slice(0, 5));
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
setCompletions([]);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const handleSubmit = (text) => {
|
|
23
|
+
if (text.trim()) {
|
|
24
|
+
onSubmit(text.trim());
|
|
25
|
+
setValue("");
|
|
26
|
+
setCompletions([]);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
// Tab completion
|
|
30
|
+
useInput((input, key) => {
|
|
31
|
+
if (key.tab && completions.length > 0) {
|
|
32
|
+
setValue(completions[0] + " ");
|
|
33
|
+
setCompletions([]);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return (_jsxs(Box, { flexDirection: "column", children: [completions.length > 0 && (_jsx(Box, { paddingX: 1, gap: 1, children: completions.map((c) => (_jsx(Text, { dimColor: true, children: c }, c))) })), _jsxs(Box, { borderStyle: "single", paddingX: 1, children: [_jsxs(Text, { color: "cyan", bold: true, children: [">", " "] }), _jsx(TextInput, { value: value, onChange: handleChange, onSubmit: handleSubmit })] })] }));
|
|
37
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory panel widget.
|
|
3
|
+
* Shows recent decisions and facts from Qdrant.
|
|
4
|
+
*/
|
|
5
|
+
interface MemoryPanelProps {
|
|
6
|
+
decisionCount?: number;
|
|
7
|
+
factCount?: number;
|
|
8
|
+
lastMemory?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function MemoryPanel({ decisionCount, factCount, lastMemory, }: MemoryPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Memory panel widget.
|
|
4
|
+
* Shows recent decisions and facts from Qdrant.
|
|
5
|
+
*/
|
|
6
|
+
import { Box, Text } from "ink";
|
|
7
|
+
export function MemoryPanel({ decisionCount = 0, factCount = 0, lastMemory, }) {
|
|
8
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, underline: true, children: "Memory" }), _jsxs(Text, { children: [decisionCount, " decisions, ", factCount, " facts"] }), lastMemory && (_jsxs(Text, { dimColor: true, wrap: "truncate", children: ["Last: ", lastMemory] }))] }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status bar widget (footer).
|
|
3
|
+
* Shows active workflow ID, current agent, token usage, cost, elapsed time.
|
|
4
|
+
*/
|
|
5
|
+
interface StatusBarProps {
|
|
6
|
+
workflowId?: string;
|
|
7
|
+
currentAgent?: string;
|
|
8
|
+
tokenCount: number;
|
|
9
|
+
cost: number;
|
|
10
|
+
elapsed: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function StatusBar({ workflowId, currentAgent, tokenCount, cost, elapsed, }: StatusBarProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Status bar widget (footer).
|
|
4
|
+
* Shows active workflow ID, current agent, token usage, cost, elapsed time.
|
|
5
|
+
*/
|
|
6
|
+
import { Box, Text } from "ink";
|
|
7
|
+
function formatElapsed(seconds) {
|
|
8
|
+
if (seconds < 60)
|
|
9
|
+
return `${seconds}s`;
|
|
10
|
+
const mins = Math.floor(seconds / 60);
|
|
11
|
+
const secs = seconds % 60;
|
|
12
|
+
if (mins < 60)
|
|
13
|
+
return `${mins}m ${secs}s`;
|
|
14
|
+
const hrs = Math.floor(mins / 60);
|
|
15
|
+
return `${hrs}h ${mins % 60}m`;
|
|
16
|
+
}
|
|
17
|
+
function formatTokens(count) {
|
|
18
|
+
if (count < 1000)
|
|
19
|
+
return `${count}`;
|
|
20
|
+
return `${(count / 1000).toFixed(1)}K`;
|
|
21
|
+
}
|
|
22
|
+
export function StatusBar({ workflowId, currentAgent, tokenCount, cost, elapsed, }) {
|
|
23
|
+
return (_jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [_jsx(Text, { dimColor: true, children: workflowId ?? "no workflow" }), _jsx(Text, { dimColor: true, children: currentAgent ?? "—" }), _jsxs(Text, { dimColor: true, children: ["tokens: ", formatTokens(tokenCount)] }), _jsxs(Text, { dimColor: true, children: ["$", cost.toFixed(2)] }), _jsx(Text, { dimColor: true, children: formatElapsed(elapsed) })] }));
|
|
24
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow timeline widget.
|
|
3
|
+
* Renders workflow progress as a vertical timeline with agent steps.
|
|
4
|
+
*/
|
|
5
|
+
interface TimelineStep {
|
|
6
|
+
time: string;
|
|
7
|
+
role: string;
|
|
8
|
+
description: string;
|
|
9
|
+
status: "done" | "running" | "waiting" | "pending";
|
|
10
|
+
duration?: string;
|
|
11
|
+
cost?: string;
|
|
12
|
+
score?: string;
|
|
13
|
+
children?: {
|
|
14
|
+
label: string;
|
|
15
|
+
done: boolean;
|
|
16
|
+
}[];
|
|
17
|
+
}
|
|
18
|
+
interface TimelineProps {
|
|
19
|
+
workflowId: string;
|
|
20
|
+
workflowStatus: string;
|
|
21
|
+
startedAgo: string;
|
|
22
|
+
totalCost: string;
|
|
23
|
+
steps: TimelineStep[];
|
|
24
|
+
}
|
|
25
|
+
export declare function Timeline({ workflowId, workflowStatus, startedAgo, totalCost, steps, }: TimelineProps): import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Workflow timeline widget.
|
|
4
|
+
* Renders workflow progress as a vertical timeline with agent steps.
|
|
5
|
+
*/
|
|
6
|
+
import { Box, Text } from "ink";
|
|
7
|
+
const STATUS_ICONS = {
|
|
8
|
+
done: "✓",
|
|
9
|
+
running: "⏳",
|
|
10
|
+
waiting: "⏸",
|
|
11
|
+
pending: "○",
|
|
12
|
+
};
|
|
13
|
+
export function Timeline({ workflowId, workflowStatus, startedAgo, totalCost, steps, }) {
|
|
14
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Text, { bold: true, children: ["Workflow: ", workflowId] }), _jsxs(Text, { children: ["Status: ", _jsx(Text, { bold: true, children: workflowStatus }), " | Started: ", startedAgo, " | Cost: ", totalCost] }), _jsx(Text, {}), _jsx(Text, { bold: true, underline: true, children: "Timeline:" }), steps.map((step, i) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: step.status === "done"
|
|
15
|
+
? "green"
|
|
16
|
+
: step.status === "running"
|
|
17
|
+
? "cyan"
|
|
18
|
+
: "gray", children: STATUS_ICONS[step.status] }), _jsx(Text, { dimColor: true, children: step.time }), _jsx(Text, { bold: true, children: step.role.padEnd(14) }), _jsx(Text, { children: step.description }), step.duration && _jsx(Text, { dimColor: true, children: step.duration }), step.cost && _jsx(Text, { dimColor: true, children: step.cost })] }), step.children?.map((child, j) => (_jsxs(Box, { marginLeft: 4, children: [_jsx(Text, { color: child.done ? "green" : "gray", children: child.done ? "├── ✓ " : "├── ○ " }), _jsx(Text, { children: child.label })] }, j)))] }, i)))] }));
|
|
19
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hydra-os-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Terminal user interface for the Hydra multi-agent OS. Monitor workflows, approve decisions, and manage agents from your terminal.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/mercurialsolo/hydra.git",
|
|
10
|
+
"directory": "apps/tui"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/mercurialsolo/hydra/tree/main/apps/tui",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"cli",
|
|
15
|
+
"tui",
|
|
16
|
+
"terminal",
|
|
17
|
+
"agents",
|
|
18
|
+
"multi-agent",
|
|
19
|
+
"temporal",
|
|
20
|
+
"workflow",
|
|
21
|
+
"orchestration",
|
|
22
|
+
"hydra"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18.0.0"
|
|
26
|
+
},
|
|
27
|
+
"bin": {
|
|
28
|
+
"hydra-os": "./dist/cli.js"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"dev": "tsx src/cli.tsx",
|
|
36
|
+
"build": "tsc",
|
|
37
|
+
"prepublishOnly": "tsc",
|
|
38
|
+
"start": "node dist/cli.js",
|
|
39
|
+
"lint": "eslint .",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"test": "vitest"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"ink": "^5.1.0",
|
|
45
|
+
"ink-spinner": "^5.0.0",
|
|
46
|
+
"ink-text-input": "^6.0.0",
|
|
47
|
+
"ink-select-input": "^6.0.0",
|
|
48
|
+
"ink-table": "^3.1.0",
|
|
49
|
+
"react": "^18.3.0",
|
|
50
|
+
"commander": "^12.1.0",
|
|
51
|
+
"chalk": "^5.3.0",
|
|
52
|
+
"yaml": "^2.4.0",
|
|
53
|
+
"zod": "^3.23.0",
|
|
54
|
+
"zustand": "^5.0.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^20.10.6",
|
|
58
|
+
"@types/react": "^18.3.0",
|
|
59
|
+
"tsx": "^4.19.0",
|
|
60
|
+
"typescript": "^5.3.3",
|
|
61
|
+
"vitest": "^1.0.4",
|
|
62
|
+
"ink-testing-library": "^4.0.0"
|
|
63
|
+
}
|
|
64
|
+
}
|