dyui 0.1.0__py3-none-any.whl
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.
- dyui/__init__.py +61 -0
- dyui/__version__.py +1 -0
- dyui/cli.py +347 -0
- dyui/emit.py +290 -0
- dyui/events.py +86 -0
- dyui/scaffold.py +275 -0
- dyui/server.py +198 -0
- dyui/static/index.html +375 -0
- dyui/stream.py +106 -0
- dyui/templates.py +105 -0
- dyui-0.1.0.dist-info/METADATA +143 -0
- dyui-0.1.0.dist-info/RECORD +15 -0
- dyui-0.1.0.dist-info/WHEEL +4 -0
- dyui-0.1.0.dist-info/entry_points.txt +2 -0
- dyui-0.1.0.dist-info/licenses/LICENSE +21 -0
dyui/__init__.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""DyUI -- a dynamic-UI layer for any LangGraph agent.
|
|
2
|
+
|
|
3
|
+
Emit rich, live UI "cards" from inside any LangGraph node or tool, with any LLM
|
|
4
|
+
(or none). The companion ``@dyui/react`` package renders those cards on the
|
|
5
|
+
frontend via a pluggable registry that also supports raw custom HTML.
|
|
6
|
+
|
|
7
|
+
Quick start (agent side)::
|
|
8
|
+
|
|
9
|
+
from dyui import emit, ui_tool
|
|
10
|
+
|
|
11
|
+
@ui_tool("stat", title="Weather", icon="cloud", accent="cyan")
|
|
12
|
+
def get_weather(city: str) -> dict:
|
|
13
|
+
return {"label": city, "value": 21, "unit": "C"}
|
|
14
|
+
|
|
15
|
+
# or imperatively, anywhere inside a node:
|
|
16
|
+
emit("table", {"columns": ["a", "b"], "rows": [[1, 2]]}, title="Results")
|
|
17
|
+
|
|
18
|
+
Quick start (server side)::
|
|
19
|
+
|
|
20
|
+
from dyui.server import create_dyui_app
|
|
21
|
+
app = create_dyui_app(my_compiled_graph) # uvicorn dyui_app:app
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from .__version__ import __version__
|
|
25
|
+
from .emit import Card, emit, ui_tool, update
|
|
26
|
+
from .events import DYUI_KEY, Status, UIEvent, is_dyui_payload, parse_envelope
|
|
27
|
+
from .stream import astream_ui, collect, collect_events
|
|
28
|
+
from .templates import HtmlTemplates, render_template
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"__version__",
|
|
32
|
+
"emit",
|
|
33
|
+
"update",
|
|
34
|
+
"ui_tool",
|
|
35
|
+
"Card",
|
|
36
|
+
"UIEvent",
|
|
37
|
+
"Status",
|
|
38
|
+
"DYUI_KEY",
|
|
39
|
+
"is_dyui_payload",
|
|
40
|
+
"parse_envelope",
|
|
41
|
+
"astream_ui",
|
|
42
|
+
"collect",
|
|
43
|
+
"collect_events",
|
|
44
|
+
"HtmlTemplates",
|
|
45
|
+
"render_template",
|
|
46
|
+
# lazily provided (need FastAPI):
|
|
47
|
+
"create_dyui_app",
|
|
48
|
+
"add_dyui_routes",
|
|
49
|
+
"mount_ui",
|
|
50
|
+
"render_ui_page",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def __getattr__(name: str):
|
|
55
|
+
# Lazily expose the FastAPI helpers so importing ``dyui`` never hard-requires
|
|
56
|
+
# FastAPI (it's an optional ``[server]`` extra).
|
|
57
|
+
if name in {"create_dyui_app", "add_dyui_routes", "mount_ui", "render_ui_page"}:
|
|
58
|
+
from . import server
|
|
59
|
+
|
|
60
|
+
return getattr(server, name)
|
|
61
|
+
raise AttributeError(f"module 'dyui' has no attribute {name!r}")
|
dyui/__version__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
dyui/cli.py
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""The ``dyui`` command-line interface.
|
|
2
|
+
|
|
3
|
+
Commands
|
|
4
|
+
--------
|
|
5
|
+
* ``dyui claude init <file.py>`` / ``dyui gemini init <file.py>`` /
|
|
6
|
+
``dyui codex init <file.py>`` -- hand your LangGraph agent file to a coding
|
|
7
|
+
agent (Claude Code / Gemini CLI / Codex CLI) together with a full brief on how
|
|
8
|
+
to use DyUI, so it designs + builds custom cards for your specific agent,
|
|
9
|
+
confirms the plan with you, and wires them into the served UI.
|
|
10
|
+
* ``dyui serve [target]`` -- run the agent's served dynamic-UI in one command
|
|
11
|
+
(uvicorn under the hood). ``target`` is a ``file.py`` or ``module:attr``;
|
|
12
|
+
auto-discovered when omitted.
|
|
13
|
+
* ``dyui export <name>`` -- scaffold the whole dynamic UI as an editable React
|
|
14
|
+
project (``@dyui/react``) in ``./<name>``.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import importlib.util
|
|
21
|
+
import os
|
|
22
|
+
import shutil
|
|
23
|
+
import subprocess
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any, Optional
|
|
27
|
+
|
|
28
|
+
from .__version__ import __version__
|
|
29
|
+
|
|
30
|
+
# --------------------------------------------------------------------------- #
|
|
31
|
+
# The brief handed to coding agents on `dyui <agent> init`.
|
|
32
|
+
# --------------------------------------------------------------------------- #
|
|
33
|
+
AGENT_BRIEF = r'''# DyUI integration brief (for a coding agent)
|
|
34
|
+
|
|
35
|
+
You are helping a developer add a **dynamic UI** to their existing LangGraph
|
|
36
|
+
agent using the **DyUI** Python package (already `pip install`ed). Your job:
|
|
37
|
+
read their agent code, design a set of UI **cards** tailored to what the agent
|
|
38
|
+
does, get the developer's confirmation, then implement and wire it up.
|
|
39
|
+
|
|
40
|
+
## How DyUI works (the essentials)
|
|
41
|
+
|
|
42
|
+
DyUI streams "cards" from inside a LangGraph graph to a frontend over LangGraph's
|
|
43
|
+
native `custom` stream channel. You emit cards from any node or tool:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from dyui import emit, ui_tool, Card, HtmlTemplates
|
|
47
|
+
|
|
48
|
+
# 1) Imperative emit (call anywhere inside a node/tool):
|
|
49
|
+
emit("table", {"columns": ["city","temp"], "rows": [["Berlin",21]]},
|
|
50
|
+
title="Weather", icon="cloud", accent="cyan")
|
|
51
|
+
|
|
52
|
+
# 2) Decorator: auto pending -> done/error around a tool:
|
|
53
|
+
@ui_tool("stat", title="Sum", icon="calculator", accent="emerald")
|
|
54
|
+
def add(a: float, b: float) -> dict:
|
|
55
|
+
return {"label": f"{a}+{b}", "value": a + b} # dict becomes the card props
|
|
56
|
+
|
|
57
|
+
# 3) Context manager: long work, one card updated in place:
|
|
58
|
+
with Card("progress", title="Indexing") as card:
|
|
59
|
+
for i in range(1, 4):
|
|
60
|
+
card.progress({"value": i, "max": 3})
|
|
61
|
+
card.done({"value": 3, "max": 3, "label": "Done"})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`emit(component, props, *, id=None, status="done", surface="default", title=None,
|
|
65
|
+
icon=None, accent=None, ttl_ms=None)`. Re-emit with the same `id` to update a card
|
|
66
|
+
(lifecycle: `pending` -> `active` -> `done`/`error`).
|
|
67
|
+
|
|
68
|
+
## Built-in card components (no frontend code needed)
|
|
69
|
+
|
|
70
|
+
| component | props |
|
|
71
|
+
|------------|-------|
|
|
72
|
+
| `text` | `{text}` |
|
|
73
|
+
| `markdown` | `{text}` (supports #/##/### , **bold**, `code`, - lists) |
|
|
74
|
+
| `table` | `{columns: string[], rows: any[][]}` |
|
|
75
|
+
| `stat` | `{label, value, unit?, delta?}` |
|
|
76
|
+
| `progress` | `{value, max?, label?}` |
|
|
77
|
+
| `list` | `{items: (string | {title, subtitle?, badge?})[]}` |
|
|
78
|
+
| `keyvalue` | `{data: {k: v}}` |
|
|
79
|
+
| `json` | any |
|
|
80
|
+
| `alert` | `{text, level?: info|success|warning|error, title?}` |
|
|
81
|
+
| `image` | `{src, alt?, caption?}` |
|
|
82
|
+
| `html` | `{html}` (raw, sanitized) -- for fully custom markup |
|
|
83
|
+
|
|
84
|
+
`icon` hints (served UI): `search, calculator, cloud, check, list, sparkles, plus`.
|
|
85
|
+
`accent` is any CSS color or one of `cyan, violet, emerald, amber, rose`.
|
|
86
|
+
|
|
87
|
+
## Custom cards via HTML templates (preferred for bespoke visuals)
|
|
88
|
+
|
|
89
|
+
Put `.html` files in a `./cards/` folder; `{{ key }}` is HTML-escaped, `{{{ key }}}`
|
|
90
|
+
is raw. Then:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
cards = HtmlTemplates("cards")
|
|
94
|
+
cards.emit("invoice", {"number": 42, "total": "$1,200"}, title="Invoice", accent="emerald")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Reserved kwargs (`id,status,surface,title,icon,accent,ttl_ms`) are card config —
|
|
98
|
+
put template values with those names inside the dict.
|
|
99
|
+
|
|
100
|
+
## Serving (already handled by the package)
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from dyui.server import create_dyui_app
|
|
104
|
+
app = create_dyui_app(graph, input_adapter=lambda b: {"messages": [("user", b["input"])]})
|
|
105
|
+
```
|
|
106
|
+
The developer runs `dyui serve` (or `uvicorn module:app`) and opens the browser —
|
|
107
|
+
the package ships an animated chat UI that renders all the cards above.
|
|
108
|
+
|
|
109
|
+
## YOUR TASK (do these in order)
|
|
110
|
+
|
|
111
|
+
1. Read the developer's agent file (shown below / at the given path). Identify the
|
|
112
|
+
nodes, tools, and the data each produces.
|
|
113
|
+
2. **Propose** a concrete set of cards: for each meaningful tool/node output, pick
|
|
114
|
+
a built-in component or design an HTML template, with titles/icons/accents.
|
|
115
|
+
Present this plan as a short table.
|
|
116
|
+
3. **ASK the developer to confirm** (or adjust) the plan before writing code. Do
|
|
117
|
+
not skip this confirmation step.
|
|
118
|
+
4. On approval, implement:
|
|
119
|
+
- Add `emit(...)` / `@ui_tool(...)` calls (or `HtmlTemplates`) at the right
|
|
120
|
+
points so cards stream as the agent runs.
|
|
121
|
+
- Create any `./cards/*.html` templates you designed.
|
|
122
|
+
- Ensure the file exposes `app = create_dyui_app(graph, ...)` with an
|
|
123
|
+
`input_adapter` matching the agent's expected input.
|
|
124
|
+
5. Tell the developer to run `dyui serve <file>` and open the browser.
|
|
125
|
+
|
|
126
|
+
Keep changes minimal and faithful to the existing agent logic — you are adding a
|
|
127
|
+
UI layer, not rewriting the agent.
|
|
128
|
+
'''
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _c(text: str, code: str = "1") -> str:
|
|
132
|
+
if not sys.stdout.isatty():
|
|
133
|
+
return text
|
|
134
|
+
return f"\033[{code}m{text}\033[0m"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# --------------------------------------------------------------------------- #
|
|
138
|
+
# `dyui <agent> init <file>`
|
|
139
|
+
# --------------------------------------------------------------------------- #
|
|
140
|
+
AGENT_LAUNCHERS = {
|
|
141
|
+
# agent -> function(prompt_path) -> argv list
|
|
142
|
+
"claude": lambda p: ["claude", f"Read {p} and follow it exactly."],
|
|
143
|
+
"gemini": lambda p: ["gemini", "-i", f"Read {p} and follow it exactly."],
|
|
144
|
+
"codex": lambda p: ["codex", f"Read {p} and follow it exactly."],
|
|
145
|
+
}
|
|
146
|
+
AGENT_INSTALL_HINT = {
|
|
147
|
+
"claude": "npm install -g @anthropic-ai/claude-code (or see claude.com/claude-code)",
|
|
148
|
+
"gemini": "npm install -g @google/gemini-cli",
|
|
149
|
+
"codex": "npm install -g @openai/codex",
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def cmd_init(agent: str, file: str) -> int:
|
|
154
|
+
src = Path(file)
|
|
155
|
+
if not src.exists():
|
|
156
|
+
print(_c(f"error: file not found: {file}", "31"))
|
|
157
|
+
return 1
|
|
158
|
+
|
|
159
|
+
code = src.read_text(encoding="utf-8")
|
|
160
|
+
prompt = (
|
|
161
|
+
AGENT_BRIEF
|
|
162
|
+
+ f"\n\n## The developer's LangGraph agent: `{src.name}`\n\n"
|
|
163
|
+
+ f"```python\n{code}\n```\n"
|
|
164
|
+
)
|
|
165
|
+
out_dir = Path(".dyui")
|
|
166
|
+
out_dir.mkdir(exist_ok=True)
|
|
167
|
+
prompt_path = (out_dir / "INIT_PROMPT.md").resolve()
|
|
168
|
+
prompt_path.write_text(prompt, encoding="utf-8")
|
|
169
|
+
|
|
170
|
+
print(_c(f"\n DyUI :: {agent} init", "1"))
|
|
171
|
+
print(f" Brief + your agent written to {_c(str(prompt_path), '36')}")
|
|
172
|
+
|
|
173
|
+
launcher = AGENT_LAUNCHERS[agent]
|
|
174
|
+
argv = launcher(str(prompt_path))
|
|
175
|
+
exe = shutil.which(argv[0])
|
|
176
|
+
|
|
177
|
+
manual = f'{argv[0]} "Read {prompt_path} and follow it."'
|
|
178
|
+
if exe is None:
|
|
179
|
+
print(_c(f"\n '{argv[0]}' CLI not found.", "33"))
|
|
180
|
+
print(f" Install it: {AGENT_INSTALL_HINT[agent]}")
|
|
181
|
+
print(f" Then run: {manual}\n")
|
|
182
|
+
return 2
|
|
183
|
+
|
|
184
|
+
# Let users (or tests) skip auto-launch and just get the command to run.
|
|
185
|
+
if os.environ.get("DYUI_NO_LAUNCH"):
|
|
186
|
+
print(f" Run this to start the session:\n {manual}\n")
|
|
187
|
+
return 0
|
|
188
|
+
|
|
189
|
+
argv[0] = exe
|
|
190
|
+
print(f" Launching {_c(argv[0], '36')} - it will design cards and confirm with you.\n")
|
|
191
|
+
try:
|
|
192
|
+
# On Windows the resolved CLI is a .CMD shim and must go through the shell.
|
|
193
|
+
if os.name == "nt":
|
|
194
|
+
return subprocess.run(subprocess.list2cmdline(argv), shell=True).returncode
|
|
195
|
+
return subprocess.run(argv).returncode
|
|
196
|
+
except (FileNotFoundError, OSError) as exc:
|
|
197
|
+
print(_c(f"\n could not launch {agent}: {exc}", "33"))
|
|
198
|
+
print(f" Run it manually: {manual}\n")
|
|
199
|
+
return 2
|
|
200
|
+
except KeyboardInterrupt:
|
|
201
|
+
return 130
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# --------------------------------------------------------------------------- #
|
|
205
|
+
# `dyui serve`
|
|
206
|
+
# --------------------------------------------------------------------------- #
|
|
207
|
+
def _load_module_from_path(path: Path) -> Any:
|
|
208
|
+
# Make sibling imports (e.g. `from calculator import ...`) resolve.
|
|
209
|
+
sys.path.insert(0, str(path.resolve().parent))
|
|
210
|
+
spec = importlib.util.spec_from_file_location(path.stem, path)
|
|
211
|
+
if spec is None or spec.loader is None:
|
|
212
|
+
raise ImportError(f"cannot import {path}")
|
|
213
|
+
module = importlib.util.module_from_spec(spec)
|
|
214
|
+
# Register before exec so typing.get_type_hints (used by LangGraph on
|
|
215
|
+
# TypedDict state schemas) can resolve the module's own annotations.
|
|
216
|
+
sys.modules[spec.name] = module
|
|
217
|
+
spec.loader.exec_module(module)
|
|
218
|
+
return module
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _discover_target() -> Optional[str]:
|
|
222
|
+
for name in ("app.py", "agent.py", "main.py", "graph.py"):
|
|
223
|
+
if Path(name).exists():
|
|
224
|
+
return name
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _resolve_app(target: Optional[str], *, serve_ui: bool, title: str) -> Any:
|
|
229
|
+
"""Return a FastAPI app from a target ``file.py`` or ``module:attr``."""
|
|
230
|
+
from .server import create_dyui_app
|
|
231
|
+
|
|
232
|
+
if target is None:
|
|
233
|
+
target = _discover_target()
|
|
234
|
+
if target is None:
|
|
235
|
+
raise SystemExit(
|
|
236
|
+
"error: no target given and none of app.py/agent.py/main.py found.\n"
|
|
237
|
+
" usage: dyui serve <file.py | module:attr>"
|
|
238
|
+
)
|
|
239
|
+
print(_c(f" discovered {target}", "36"))
|
|
240
|
+
|
|
241
|
+
attr = None
|
|
242
|
+
if ":" in target and not target.endswith(".py"):
|
|
243
|
+
mod_name, attr = target.split(":", 1)
|
|
244
|
+
module = __import__(mod_name, fromlist=["*"])
|
|
245
|
+
else:
|
|
246
|
+
path = Path(target)
|
|
247
|
+
if not path.exists():
|
|
248
|
+
raise SystemExit(f"error: file not found: {target}")
|
|
249
|
+
module = _load_module_from_path(path)
|
|
250
|
+
|
|
251
|
+
# Prefer an explicit attr, else `app`, else wrap a `graph`.
|
|
252
|
+
obj = getattr(module, attr) if attr else getattr(module, "app", None)
|
|
253
|
+
if obj is not None:
|
|
254
|
+
return obj
|
|
255
|
+
graph = getattr(module, "graph", None)
|
|
256
|
+
if graph is not None:
|
|
257
|
+
print(_c(" no `app` found — wrapping `graph` with create_dyui_app()", "36"))
|
|
258
|
+
return create_dyui_app(graph, serve_ui=serve_ui, ui_title=title)
|
|
259
|
+
raise SystemExit(
|
|
260
|
+
f"error: {target} exposes neither `app` nor `graph`.\n"
|
|
261
|
+
" Define `app = create_dyui_app(graph)` or expose a compiled `graph`."
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def cmd_serve(target: Optional[str], host: str, port: int, no_ui: bool, title: str) -> int:
|
|
266
|
+
try:
|
|
267
|
+
import uvicorn
|
|
268
|
+
except ImportError:
|
|
269
|
+
print(_c("error: uvicorn not installed. Run: pip install 'dyui[server]'", "31"))
|
|
270
|
+
return 1
|
|
271
|
+
|
|
272
|
+
app = _resolve_app(target, serve_ui=not no_ui, title=title)
|
|
273
|
+
print(_c(f"\n DyUI serving on http://{host}:{port}", "1")
|
|
274
|
+
+ (f" (UI at /)" if not no_ui else " (UI disabled)"))
|
|
275
|
+
print(f" stream endpoint: POST http://{host}:{port}/dyui/stream\n")
|
|
276
|
+
uvicorn.run(app, host=host, port=port)
|
|
277
|
+
return 0
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
# --------------------------------------------------------------------------- #
|
|
281
|
+
# `dyui export <name>`
|
|
282
|
+
# --------------------------------------------------------------------------- #
|
|
283
|
+
def cmd_export(name: str, stream_url: str, force: bool, local: Optional[str]) -> int:
|
|
284
|
+
from .scaffold import write_react_project
|
|
285
|
+
|
|
286
|
+
dest = Path(name)
|
|
287
|
+
if dest.exists() and any(dest.iterdir()) and not force:
|
|
288
|
+
print(_c(f"error: ./{name} exists and is not empty (use --force to overwrite)", "31"))
|
|
289
|
+
return 1
|
|
290
|
+
files = write_react_project(
|
|
291
|
+
dest, project_name=name, stream_url=stream_url, local_react=local
|
|
292
|
+
)
|
|
293
|
+
print(_c(f"\n Exported React project to ./{name}/", "1"))
|
|
294
|
+
for f in files:
|
|
295
|
+
print(f" {f}")
|
|
296
|
+
print(
|
|
297
|
+
"\n Next:\n"
|
|
298
|
+
f" cd {name}\n"
|
|
299
|
+
" npm install\n"
|
|
300
|
+
" npm run dev\n\n"
|
|
301
|
+
" Edit src/cards/ to add custom card components; they render the same\n"
|
|
302
|
+
" events your `dyui serve` UI shows.\n"
|
|
303
|
+
)
|
|
304
|
+
return 0
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# --------------------------------------------------------------------------- #
|
|
308
|
+
# arg parsing
|
|
309
|
+
# --------------------------------------------------------------------------- #
|
|
310
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
311
|
+
p = argparse.ArgumentParser(prog="dyui", description="Dynamic UI for LangGraph agents.")
|
|
312
|
+
p.add_argument("--version", action="version", version=f"dyui {__version__}")
|
|
313
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
314
|
+
|
|
315
|
+
for agent in ("claude", "gemini", "codex"):
|
|
316
|
+
ap = sub.add_parser(agent, help=f"use {agent} to design + build cards for your agent")
|
|
317
|
+
ap.add_argument("action", choices=["init"], help="init: analyse a file and build cards")
|
|
318
|
+
ap.add_argument("file", help="your LangGraph agent .py file")
|
|
319
|
+
|
|
320
|
+
sp = sub.add_parser("serve", help="run the agent's served dynamic UI")
|
|
321
|
+
sp.add_argument("target", nargs="?", help="file.py or module:attr (auto-discovered if omitted)")
|
|
322
|
+
sp.add_argument("--host", default="127.0.0.1")
|
|
323
|
+
sp.add_argument("--port", type=int, default=8000)
|
|
324
|
+
sp.add_argument("--no-ui", action="store_true", help="serve only the stream endpoint")
|
|
325
|
+
sp.add_argument("--title", default="DyUI")
|
|
326
|
+
|
|
327
|
+
ep = sub.add_parser("export", help="export the dynamic UI as a React project")
|
|
328
|
+
ep.add_argument("name", help="project directory name")
|
|
329
|
+
ep.add_argument("--stream-url", default="http://localhost:8000/dyui/stream")
|
|
330
|
+
ep.add_argument("--local", help="use a local @dyui/react build path instead of the npm package")
|
|
331
|
+
ep.add_argument("--force", action="store_true")
|
|
332
|
+
return p
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
|
336
|
+
args = build_parser().parse_args(argv)
|
|
337
|
+
if args.cmd in ("claude", "gemini", "codex"):
|
|
338
|
+
return cmd_init(args.cmd, args.file)
|
|
339
|
+
if args.cmd == "serve":
|
|
340
|
+
return cmd_serve(args.target, args.host, args.port, args.no_ui, args.title)
|
|
341
|
+
if args.cmd == "export":
|
|
342
|
+
return cmd_export(args.name, args.stream_url, args.force, args.local)
|
|
343
|
+
return 1
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
if __name__ == "__main__":
|
|
347
|
+
raise SystemExit(main())
|