@eidentic/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/LICENSE +201 -0
- package/README.md +54 -0
- package/dist/chunk-QGM4M3NI.js +37 -0
- package/dist/dist-DDLWLV6O.js +8044 -0
- package/dist/index.js +1127 -0
- package/package.json +70 -0
- package/templates/components/chat.tsx +226 -0
- package/templates/components/run-status.tsx +273 -0
- package/templates/components/workflow-trace.tsx +189 -0
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eidentic/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"description": "The eidentic command-line tool — dev server, project init, component scaffolding, and health diagnostics.",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/eidentic/eidentic.git",
|
|
13
|
+
"directory": "packages/cli"
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"eidentic": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"templates",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@clack/prompts": "^1.5.1",
|
|
31
|
+
"@hono/node-server": "^2.0.0",
|
|
32
|
+
"citty": "^0.2.2",
|
|
33
|
+
"consola": "^3.4.2",
|
|
34
|
+
"hono": "^4.12.0",
|
|
35
|
+
"jiti": "^2.7.0",
|
|
36
|
+
"picocolors": "^1.1.1",
|
|
37
|
+
"@eidentic/core": "0.1.0",
|
|
38
|
+
"@eidentic/eval": "0.1.0",
|
|
39
|
+
"@eidentic/server": "0.1.0",
|
|
40
|
+
"@eidentic/studio": "0.1.0",
|
|
41
|
+
"@eidentic/types": "0.1.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/react": "^19.0.0",
|
|
45
|
+
"react": "^19.0.0",
|
|
46
|
+
"@eidentic/react": "0.1.0",
|
|
47
|
+
"@eidentic/skills": "^0.1.0"
|
|
48
|
+
},
|
|
49
|
+
"keywords": [
|
|
50
|
+
"ai",
|
|
51
|
+
"agents",
|
|
52
|
+
"typescript",
|
|
53
|
+
"eidentic",
|
|
54
|
+
"cli",
|
|
55
|
+
"dev-server",
|
|
56
|
+
"scaffold"
|
|
57
|
+
],
|
|
58
|
+
"homepage": "https://github.com/eidentic/eidentic#readme",
|
|
59
|
+
"bugs": {
|
|
60
|
+
"url": "https://github.com/eidentic/eidentic/issues"
|
|
61
|
+
},
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=22"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "tsup src/index.ts --format esm --clean",
|
|
67
|
+
"typecheck": "tsc --noEmit",
|
|
68
|
+
"typecheck:templates": "tsc --noEmit -p tsconfig.templates.json"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Copied by `eidentic add component` — yours to edit.
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
import React, { useRef, useEffect, type KeyboardEvent, type FormEvent } from "react";
|
|
5
|
+
import { useAgent } from "@eidentic/react";
|
|
6
|
+
import type { TextMessage, ToolCall } from "@eidentic/react";
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Props
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
export interface EidenticChatProps {
|
|
13
|
+
/** Agent ID registered in the Eidentic server. */
|
|
14
|
+
agentId: string;
|
|
15
|
+
/** Base URL of the Eidentic server. Defaults to same-origin (""). */
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
/** Placeholder text for the input field. */
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
/** Class name applied to the outermost container. */
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Sub-components
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
function MessageBubble({ message }: { message: TextMessage }) {
|
|
28
|
+
return (
|
|
29
|
+
<div className="flex justify-end">
|
|
30
|
+
<div className="max-w-[80%] rounded-2xl rounded-br-sm bg-indigo-500 px-4 py-2 text-sm text-white">
|
|
31
|
+
{message.content}
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function AssistantBubble({ message }: { message: TextMessage }) {
|
|
38
|
+
return (
|
|
39
|
+
<div className="flex justify-start">
|
|
40
|
+
<div className="max-w-[80%] rounded-2xl rounded-bl-sm bg-white px-4 py-2 text-sm text-zinc-800 shadow-sm">
|
|
41
|
+
{message.content}
|
|
42
|
+
{message.streaming && (
|
|
43
|
+
<span className="ml-1 inline-block h-3 w-1 animate-pulse rounded-sm bg-indigo-400" aria-hidden="true" />
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function ToolChip({ call }: { call: ToolCall }) {
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
role="status"
|
|
54
|
+
aria-label={`Tool call: ${call.name}`}
|
|
55
|
+
className="flex items-center gap-1.5 rounded-full border border-indigo-200 bg-indigo-50 px-3 py-1 text-xs text-indigo-700"
|
|
56
|
+
>
|
|
57
|
+
<span className="h-1.5 w-1.5 rounded-full bg-indigo-400" aria-hidden="true" />
|
|
58
|
+
<span className="font-mono">{call.name}</span>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// EidenticChat
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Drop-in chat UI for a Eidentic agent.
|
|
69
|
+
*
|
|
70
|
+
* Usage:
|
|
71
|
+
* <EidenticChat agentId="my-agent" baseUrl="http://localhost:3000" />
|
|
72
|
+
*/
|
|
73
|
+
export function EidenticChat({
|
|
74
|
+
agentId,
|
|
75
|
+
baseUrl = "",
|
|
76
|
+
placeholder = "Type a message…",
|
|
77
|
+
className = "",
|
|
78
|
+
}: EidenticChatProps) {
|
|
79
|
+
const { messages, toolCalls, result, status, error, send, stop } = useAgent(agentId, baseUrl);
|
|
80
|
+
|
|
81
|
+
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
82
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
83
|
+
|
|
84
|
+
// Autoscroll to bottom on new messages.
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
const el = scrollRef.current;
|
|
87
|
+
if (el) {
|
|
88
|
+
el.scrollTop = el.scrollHeight;
|
|
89
|
+
}
|
|
90
|
+
}, [messages, toolCalls]);
|
|
91
|
+
|
|
92
|
+
const isStreaming = status === "streaming";
|
|
93
|
+
|
|
94
|
+
function handleSubmit(e: FormEvent) {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
const val = inputRef.current?.value.trim();
|
|
97
|
+
if (!val || isStreaming) return;
|
|
98
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
99
|
+
send(val);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function handleKeyDown(e: KeyboardEvent<HTMLTextAreaElement>) {
|
|
103
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
const val = inputRef.current?.value.trim();
|
|
106
|
+
if (!val || isStreaming) return;
|
|
107
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
108
|
+
send(val);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Format USD cost when available.
|
|
113
|
+
const costDisplay =
|
|
114
|
+
result?.cost?.usd != null ? `$${result.cost.usd.toFixed(6)}` : null;
|
|
115
|
+
const usageDisplay =
|
|
116
|
+
result?.usage != null
|
|
117
|
+
? `${result.usage.inputTokens + result.usage.outputTokens} tokens`
|
|
118
|
+
: null;
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div
|
|
122
|
+
className={`flex h-full flex-col rounded-2xl border border-zinc-200 bg-zinc-50 shadow-sm ${className}`}
|
|
123
|
+
role="region"
|
|
124
|
+
aria-label="Eidentic chat"
|
|
125
|
+
>
|
|
126
|
+
{/* Message list */}
|
|
127
|
+
<div
|
|
128
|
+
ref={scrollRef}
|
|
129
|
+
className="flex-1 overflow-y-auto space-y-3 px-4 py-4"
|
|
130
|
+
role="log"
|
|
131
|
+
aria-live="polite"
|
|
132
|
+
aria-label="Conversation"
|
|
133
|
+
>
|
|
134
|
+
{messages.length === 0 && !isStreaming && (
|
|
135
|
+
<p className="text-center text-sm text-zinc-400">
|
|
136
|
+
Send a message to start the conversation.
|
|
137
|
+
</p>
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
{messages.map((msg, i) =>
|
|
141
|
+
msg.role === "assistant" ? (
|
|
142
|
+
<AssistantBubble key={i} message={msg} />
|
|
143
|
+
) : (
|
|
144
|
+
<MessageBubble key={i} message={msg} />
|
|
145
|
+
),
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
{/* Tool-call chips */}
|
|
149
|
+
{toolCalls.length > 0 && (
|
|
150
|
+
<div className="flex flex-wrap gap-2 pt-1" aria-label="Active tool calls">
|
|
151
|
+
{toolCalls.map((tc) => (
|
|
152
|
+
<ToolChip key={tc.callId} call={tc} />
|
|
153
|
+
))}
|
|
154
|
+
</div>
|
|
155
|
+
)}
|
|
156
|
+
|
|
157
|
+
{/* Error */}
|
|
158
|
+
{error && (
|
|
159
|
+
<div
|
|
160
|
+
role="alert"
|
|
161
|
+
className="rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm text-red-700"
|
|
162
|
+
>
|
|
163
|
+
{error.message}
|
|
164
|
+
</div>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
{/* Footer: usage + cost */}
|
|
169
|
+
{(usageDisplay || costDisplay) && (
|
|
170
|
+
<div
|
|
171
|
+
className="flex items-center gap-3 border-t border-zinc-100 px-4 py-1.5 text-xs text-zinc-400"
|
|
172
|
+
aria-label="Usage summary"
|
|
173
|
+
>
|
|
174
|
+
{usageDisplay && <span>{usageDisplay}</span>}
|
|
175
|
+
{costDisplay && <span>{costDisplay}</span>}
|
|
176
|
+
</div>
|
|
177
|
+
)}
|
|
178
|
+
|
|
179
|
+
{/* Input */}
|
|
180
|
+
<form
|
|
181
|
+
onSubmit={handleSubmit}
|
|
182
|
+
className="flex items-end gap-2 border-t border-zinc-200 px-3 py-3"
|
|
183
|
+
aria-label="Message input"
|
|
184
|
+
>
|
|
185
|
+
<label htmlFor="eidentic-chat-input" className="sr-only">
|
|
186
|
+
Message
|
|
187
|
+
</label>
|
|
188
|
+
<textarea
|
|
189
|
+
id="eidentic-chat-input"
|
|
190
|
+
ref={inputRef}
|
|
191
|
+
rows={1}
|
|
192
|
+
placeholder={placeholder}
|
|
193
|
+
disabled={isStreaming}
|
|
194
|
+
onKeyDown={handleKeyDown}
|
|
195
|
+
className="flex-1 resize-none rounded-xl border border-zinc-300 bg-white px-3 py-2 text-sm text-zinc-800 placeholder-zinc-400 outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 disabled:opacity-60"
|
|
196
|
+
aria-multiline="true"
|
|
197
|
+
/>
|
|
198
|
+
{isStreaming ? (
|
|
199
|
+
<button
|
|
200
|
+
type="button"
|
|
201
|
+
onClick={stop}
|
|
202
|
+
aria-label="Stop generation"
|
|
203
|
+
className="flex h-9 w-9 items-center justify-center rounded-xl bg-zinc-200 text-zinc-600 hover:bg-zinc-300 focus-visible:outline-2 focus-visible:outline-indigo-500"
|
|
204
|
+
>
|
|
205
|
+
<span className="block h-3.5 w-3.5 rounded-sm bg-current" aria-hidden="true" />
|
|
206
|
+
</button>
|
|
207
|
+
) : (
|
|
208
|
+
<button
|
|
209
|
+
type="submit"
|
|
210
|
+
aria-label="Send message"
|
|
211
|
+
className="flex h-9 w-9 items-center justify-center rounded-xl bg-indigo-500 text-white hover:bg-indigo-600 focus-visible:outline-2 focus-visible:outline-indigo-500 disabled:opacity-50"
|
|
212
|
+
>
|
|
213
|
+
<svg
|
|
214
|
+
viewBox="0 0 16 16"
|
|
215
|
+
fill="currentColor"
|
|
216
|
+
className="h-4 w-4"
|
|
217
|
+
aria-hidden="true"
|
|
218
|
+
>
|
|
219
|
+
<path d="M8 2.5a.75.75 0 0 1 .75.75v7.19l2.47-2.47a.75.75 0 1 1 1.06 1.06l-3.75 3.75a.75.75 0 0 1-1.06 0L3.72 9.03a.75.75 0 1 1 1.06-1.06L7.25 10.44V3.25A.75.75 0 0 1 8 2.5Z" />
|
|
220
|
+
</svg>
|
|
221
|
+
</button>
|
|
222
|
+
)}
|
|
223
|
+
</form>
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// Copied by `eidentic add component` — yours to edit.
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { useAsyncRun, useRunStatus } from "@eidentic/react";
|
|
6
|
+
import type { AsyncRunStatus } from "@eidentic/react";
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Props
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
export interface RunStatusProps {
|
|
13
|
+
/** Agent ID registered in the Eidentic server. */
|
|
14
|
+
agentId: string;
|
|
15
|
+
/**
|
|
16
|
+
* Run ID to watch. When provided, the component polls that run's status.
|
|
17
|
+
* Omit (or pass null) to render a start-button that fires a new run.
|
|
18
|
+
*/
|
|
19
|
+
runId?: string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Initial input for new runs. Only used when `runId` is not provided and the
|
|
22
|
+
* start button is clicked. Required when using the start-button variant.
|
|
23
|
+
*/
|
|
24
|
+
initialInput?: unknown;
|
|
25
|
+
/** Base URL of the Eidentic server. Defaults to same-origin (""). */
|
|
26
|
+
baseUrl?: string;
|
|
27
|
+
/** Label for the start button. Defaults to "Start run". */
|
|
28
|
+
startLabel?: string;
|
|
29
|
+
/** Class name applied to the outermost container. */
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Spinner
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
function Spinner() {
|
|
38
|
+
return (
|
|
39
|
+
<span
|
|
40
|
+
className="inline-block h-4 w-4 animate-spin rounded-full border-2 border-indigo-200 border-t-indigo-500"
|
|
41
|
+
role="status"
|
|
42
|
+
aria-label="Loading"
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Status badge
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
const STATUS_STYLES: Record<AsyncRunStatus, string> = {
|
|
52
|
+
idle: "bg-zinc-100 text-zinc-500",
|
|
53
|
+
running: "bg-indigo-50 text-indigo-600",
|
|
54
|
+
completed: "bg-emerald-50 text-emerald-700",
|
|
55
|
+
failed: "bg-red-50 text-red-700",
|
|
56
|
+
aborted: "bg-amber-50 text-amber-700",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function StatusPill({ status }: { status: AsyncRunStatus }) {
|
|
60
|
+
return (
|
|
61
|
+
<span
|
|
62
|
+
className={`inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium ${STATUS_STYLES[status]}`}
|
|
63
|
+
aria-live="polite"
|
|
64
|
+
aria-atomic="true"
|
|
65
|
+
>
|
|
66
|
+
{status === "running" && <Spinner />}
|
|
67
|
+
{status}
|
|
68
|
+
</span>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Watcher variant (runId provided)
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
function RunWatcher({
|
|
77
|
+
agentId,
|
|
78
|
+
runId,
|
|
79
|
+
baseUrl,
|
|
80
|
+
className,
|
|
81
|
+
}: {
|
|
82
|
+
agentId: string;
|
|
83
|
+
runId: string;
|
|
84
|
+
baseUrl: string;
|
|
85
|
+
className: string;
|
|
86
|
+
}) {
|
|
87
|
+
const { status, output, error, isPolling } = useRunStatus(agentId, runId, { baseUrl });
|
|
88
|
+
|
|
89
|
+
const outputText =
|
|
90
|
+
output != null
|
|
91
|
+
? typeof output === "string"
|
|
92
|
+
? output
|
|
93
|
+
: JSON.stringify(output, null, 2)
|
|
94
|
+
: null;
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div
|
|
98
|
+
className={`space-y-3 rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm ${className}`}
|
|
99
|
+
role="region"
|
|
100
|
+
aria-label="Run status"
|
|
101
|
+
>
|
|
102
|
+
{/* Header */}
|
|
103
|
+
<div className="flex items-center gap-3">
|
|
104
|
+
<span className="text-sm font-medium text-zinc-700">Run</span>
|
|
105
|
+
<code className="flex-1 truncate rounded bg-zinc-100 px-2 py-0.5 font-mono text-xs text-zinc-500">
|
|
106
|
+
{runId}
|
|
107
|
+
</code>
|
|
108
|
+
<StatusPill status={status} />
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{/* Polling indicator */}
|
|
112
|
+
{isPolling && (
|
|
113
|
+
<p className="text-xs text-zinc-400">Polling for updates…</p>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
{/* Output */}
|
|
117
|
+
{outputText && (
|
|
118
|
+
<div>
|
|
119
|
+
<p className="mb-1 text-xs font-medium text-zinc-500">Output</p>
|
|
120
|
+
<pre className="overflow-x-auto rounded-xl bg-zinc-50 p-3 text-xs text-zinc-700 whitespace-pre-wrap break-all">
|
|
121
|
+
{outputText}
|
|
122
|
+
</pre>
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
|
|
126
|
+
{/* Error */}
|
|
127
|
+
{error && (
|
|
128
|
+
<div
|
|
129
|
+
role="alert"
|
|
130
|
+
className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700"
|
|
131
|
+
>
|
|
132
|
+
{error}
|
|
133
|
+
</div>
|
|
134
|
+
)}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Starter variant (no runId — shows a start button)
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
function RunStarter({
|
|
144
|
+
agentId,
|
|
145
|
+
baseUrl,
|
|
146
|
+
initialInput,
|
|
147
|
+
startLabel,
|
|
148
|
+
className,
|
|
149
|
+
}: {
|
|
150
|
+
agentId: string;
|
|
151
|
+
baseUrl: string;
|
|
152
|
+
initialInput: unknown;
|
|
153
|
+
startLabel: string;
|
|
154
|
+
className: string;
|
|
155
|
+
}) {
|
|
156
|
+
const { start, runId, status, output, error, isPolling } = useAsyncRun(agentId, { baseUrl });
|
|
157
|
+
|
|
158
|
+
async function handleStart() {
|
|
159
|
+
try {
|
|
160
|
+
await start(initialInput ?? "");
|
|
161
|
+
} catch (e) {
|
|
162
|
+
// error is surfaced via the hook state
|
|
163
|
+
void e;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const outputText =
|
|
168
|
+
output != null
|
|
169
|
+
? typeof output === "string"
|
|
170
|
+
? output
|
|
171
|
+
: JSON.stringify(output, null, 2)
|
|
172
|
+
: null;
|
|
173
|
+
|
|
174
|
+
const isRunning = status === "running";
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<div
|
|
178
|
+
className={`space-y-3 rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm ${className}`}
|
|
179
|
+
role="region"
|
|
180
|
+
aria-label="Run status"
|
|
181
|
+
>
|
|
182
|
+
{/* Header */}
|
|
183
|
+
<div className="flex items-center gap-3">
|
|
184
|
+
<span className="text-sm font-medium text-zinc-700">Agent run</span>
|
|
185
|
+
{status !== "idle" && <StatusPill status={status} />}
|
|
186
|
+
<button
|
|
187
|
+
type="button"
|
|
188
|
+
onClick={handleStart}
|
|
189
|
+
disabled={isRunning}
|
|
190
|
+
className="ml-auto rounded-xl bg-indigo-500 px-3 py-1.5 text-sm font-medium text-white hover:bg-indigo-600 focus-visible:outline-2 focus-visible:outline-indigo-500 disabled:opacity-50"
|
|
191
|
+
aria-label={startLabel}
|
|
192
|
+
>
|
|
193
|
+
{isRunning ? "Running…" : startLabel}
|
|
194
|
+
</button>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
{/* Run ID */}
|
|
198
|
+
{runId && (
|
|
199
|
+
<p className="font-mono text-xs text-zinc-400">
|
|
200
|
+
run: <code>{runId}</code>
|
|
201
|
+
</p>
|
|
202
|
+
)}
|
|
203
|
+
|
|
204
|
+
{/* Polling indicator */}
|
|
205
|
+
{isPolling && (
|
|
206
|
+
<p className="text-xs text-zinc-400">Polling for updates…</p>
|
|
207
|
+
)}
|
|
208
|
+
|
|
209
|
+
{/* Output */}
|
|
210
|
+
{outputText && (
|
|
211
|
+
<div>
|
|
212
|
+
<p className="mb-1 text-xs font-medium text-zinc-500">Output</p>
|
|
213
|
+
<pre className="overflow-x-auto rounded-xl bg-zinc-50 p-3 text-xs text-zinc-700 whitespace-pre-wrap break-all">
|
|
214
|
+
{outputText}
|
|
215
|
+
</pre>
|
|
216
|
+
</div>
|
|
217
|
+
)}
|
|
218
|
+
|
|
219
|
+
{/* Error */}
|
|
220
|
+
{error && (
|
|
221
|
+
<div
|
|
222
|
+
role="alert"
|
|
223
|
+
className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700"
|
|
224
|
+
>
|
|
225
|
+
{error}
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// RunStatus — top-level component
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Displays the status of an async agent run, or a start button that fires one.
|
|
238
|
+
*
|
|
239
|
+
* Usage (watch an existing run):
|
|
240
|
+
* <RunStatus agentId="my-agent" runId={runId} baseUrl="http://localhost:3000" />
|
|
241
|
+
*
|
|
242
|
+
* Usage (start a new run):
|
|
243
|
+
* <RunStatus agentId="my-agent" initialInput="Summarise the logs" baseUrl="http://localhost:3000" />
|
|
244
|
+
*/
|
|
245
|
+
export function RunStatus({
|
|
246
|
+
agentId,
|
|
247
|
+
runId = null,
|
|
248
|
+
initialInput,
|
|
249
|
+
baseUrl = "",
|
|
250
|
+
startLabel = "Start run",
|
|
251
|
+
className = "",
|
|
252
|
+
}: RunStatusProps) {
|
|
253
|
+
if (runId) {
|
|
254
|
+
return (
|
|
255
|
+
<RunWatcher
|
|
256
|
+
agentId={agentId}
|
|
257
|
+
runId={runId}
|
|
258
|
+
baseUrl={baseUrl}
|
|
259
|
+
className={className}
|
|
260
|
+
/>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<RunStarter
|
|
266
|
+
agentId={agentId}
|
|
267
|
+
baseUrl={baseUrl}
|
|
268
|
+
initialInput={initialInput}
|
|
269
|
+
startLabel={startLabel}
|
|
270
|
+
className={className}
|
|
271
|
+
/>
|
|
272
|
+
);
|
|
273
|
+
}
|