Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

Commit 1013e21

Browse files
committed
add subcommands
1 parent 5de1500 commit 1013e21

File tree

10 files changed

+573
-19
lines changed

10 files changed

+573
-19
lines changed

AGENTS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ flake8 --config=.linters/tox.ini taskweaver/
6969
# CLI mode
7070
python -m taskweaver -p ./project/
7171

72+
# Start CES server explicitly
73+
python -m taskweaver -p ./project/ server --port 8000
74+
75+
# In another terminal, connect to running server
76+
python -m taskweaver -p ./project/ chat --server-url http://localhost:8000
77+
7278
# As a module
7379
python -m taskweaver
7480
```

taskweaver/ces/AGENTS.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,22 @@ Connect to pre-started server. API key required.
176176

177177
## Usage
178178

179+
### CLI Integration
180+
181+
The Code Execution Service can be started as a standalone server via the TaskWeaver CLI. This is the recommended way to run CES for remote or containerized deployments. The implementation resides in `taskweaver/cli/server.py`.
182+
183+
```bash
184+
# Start CES server via CLI
185+
python -m taskweaver -p ./project server \
186+
--host 0.0.0.0 \
187+
--port 8000 \
188+
--api-key "secret" \
189+
--log-level info \
190+
--reload
191+
```
192+
193+
The server command wraps `taskweaver.ces.server.app:app` and runs it using `uvicorn`. Note that when using `--server-url` with the `chat` command, `server_auto_start` is automatically disabled to connect to the existing instance.
194+
179195
### Starting the Server Manually
180196

181197
```bash

taskweaver/chat/console/chat.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -529,15 +529,19 @@ def format_status_message(limit: int):
529529

530530

531531
class TaskWeaverChatApp(SessionEventHandlerBase):
532-
def __init__(self, app_dir: Optional[str] = None):
532+
def __init__(self, app_dir: Optional[str] = None, server_url: Optional[str] = None):
533533
from taskweaver.app.app import TaskWeaverApp, _cleanup_existing_servers
534534

535-
# Check and kill any existing server before starting
536-
cleanup_result = _cleanup_existing_servers()
537-
if cleanup_result:
538-
click.secho(f"[Startup] Killed existing server (PID: {cleanup_result}) on port 8000", fg="yellow")
535+
config = {}
536+
if server_url:
537+
config["execution_service.server.url"] = server_url
538+
config["execution_service.server.auto_start"] = False
539+
else:
540+
cleanup_result = _cleanup_existing_servers()
541+
if cleanup_result:
542+
click.secho(f"[Startup] Killed existing server (PID: {cleanup_result}) on port 8000", fg="yellow")
539543

540-
self.app = TaskWeaverApp(app_dir=app_dir)
544+
self.app = TaskWeaverApp(app_dir=app_dir, config=config)
541545
self.session = self.app.get_session()
542546
self.pending_files: List[Dict[Literal["name", "path", "content"], Any]] = []
543547
atexit.register(self.app.stop)
@@ -650,8 +654,8 @@ def _assistant_message(self, message: str) -> None:
650654
click.secho(click.style(f"▶ {message}", fg="yellow"))
651655

652656

653-
def chat_taskweaver(app_dir: Optional[str] = None):
654-
TaskWeaverChatApp(app_dir=app_dir).run()
657+
def chat_taskweaver(app_dir: Optional[str] = None, server_url: Optional[str] = None):
658+
TaskWeaverChatApp(app_dir=app_dir, server_url=server_url).run()
655659

656660

657661
if __name__ == "__main__":

taskweaver/cli/chat.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@
66
@click.command()
77
@require_workspace()
88
@click.pass_context
9-
def chat(ctx: click.Context):
10-
"""
11-
Chat with TaskWeaver in command line
12-
"""
13-
9+
@click.option(
10+
"--server-url",
11+
help="URL of the Code Execution Server (overrides --server-url from parent command)",
12+
type=str,
13+
required=False,
14+
default=None,
15+
)
16+
def chat(ctx: click.Context, server_url: str):
17+
"""Chat with TaskWeaver in command line."""
1418
ctx_obj: CliContext = ctx.obj
1519

1620
from taskweaver.chat.console import chat_taskweaver
1721

22+
effective_server_url = server_url or ctx_obj.server_url
23+
1824
click.echo(get_ascii_banner())
19-
chat_taskweaver(ctx_obj.workspace)
25+
chat_taskweaver(ctx_obj.workspace, server_url=effective_server_url)

taskweaver/cli/cli.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
from .chat import chat
44
from .init import init
5+
from .server import server
56
from .util import CliContext, get_ascii_banner
67

78

89
@click.group(
910
name="taskweaver",
1011
help=f"\b\n{get_ascii_banner(center=False)}\nTaskWeaver",
1112
invoke_without_command=True,
12-
commands=[init, chat],
13+
commands=[init, chat, server],
1314
)
1415
@click.pass_context
1516
@click.version_option(package_name="taskweaver")
@@ -25,18 +26,25 @@
2526
required=False,
2627
default=None,
2728
)
28-
def taskweaver(ctx: click.Context, project: str):
29+
@click.option(
30+
"--server-url",
31+
help="URL of the Code Execution Server (e.g., http://localhost:8000). "
32+
"If provided, disables auto-start and connects to existing server.",
33+
type=str,
34+
required=False,
35+
default=None,
36+
)
37+
def taskweaver(ctx: click.Context, project: str, server_url: str):
2938
from taskweaver.utils.app_utils import discover_app_dir
3039

3140
workspace_base, is_valid, is_empty = discover_app_dir(project)
3241

33-
# subcommand_target = ctx.invoked_subcommand if ctx.invoked_subcommand is not None else "chat"
34-
3542
ctx.obj = CliContext(
3643
workspace=workspace_base,
3744
workspace_param=project,
3845
is_workspace_valid=is_valid,
3946
is_workspace_empty=is_empty,
47+
server_url=server_url,
4048
)
4149
if not ctx.invoked_subcommand:
4250
ctx.invoke(chat)

taskweaver/cli/server.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"""Server subcommand for starting the Code Execution Server."""
2+
3+
import os
4+
5+
import click
6+
7+
from taskweaver.cli.util import CliContext, require_workspace
8+
9+
10+
@click.command()
11+
@require_workspace()
12+
@click.pass_context
13+
@click.option(
14+
"--host",
15+
type=str,
16+
default=None,
17+
help="Host to bind to (default: localhost)",
18+
)
19+
@click.option(
20+
"--port",
21+
type=int,
22+
default=None,
23+
help="Port to bind to (default: 8000)",
24+
)
25+
@click.option(
26+
"--api-key",
27+
type=str,
28+
default=None,
29+
help="API key for authentication (optional for localhost)",
30+
)
31+
@click.option(
32+
"--log-level",
33+
type=click.Choice(["debug", "info", "warning", "error", "critical"]),
34+
default="info",
35+
help="Log level (default: info)",
36+
)
37+
@click.option(
38+
"--reload",
39+
is_flag=True,
40+
default=False,
41+
help="Enable auto-reload for development",
42+
)
43+
def server(
44+
ctx: click.Context,
45+
host: str,
46+
port: int,
47+
api_key: str,
48+
log_level: str,
49+
reload: bool,
50+
):
51+
"""Start the Code Execution Server.
52+
53+
The server handles code execution requests from TaskWeaver sessions.
54+
Start this server first, then run 'taskweaver chat' in another terminal.
55+
56+
\b
57+
Example:
58+
taskweaver -p ./project server --port 8000
59+
taskweaver -p ./project chat --server-url http://localhost:8000
60+
"""
61+
ctx_obj: CliContext = ctx.obj
62+
workspace = ctx_obj.workspace
63+
assert workspace is not None
64+
65+
from taskweaver.config.config_mgt import AppConfigSource
66+
67+
app_config_file = os.path.join(workspace, "taskweaver_config.json")
68+
config_src = AppConfigSource(
69+
config_file_path=app_config_file if os.path.exists(app_config_file) else None,
70+
config={},
71+
app_base_path=workspace,
72+
)
73+
74+
def get_config(key: str, default):
75+
return config_src.json_file_store.get(key, default)
76+
77+
effective_host = host or get_config("execution_service.server.host", "localhost")
78+
effective_port = port or get_config("execution_service.server.port", 8000)
79+
effective_api_key = api_key or get_config("execution_service.server.api_key", None)
80+
work_dir = get_config(
81+
"execution_service.env_dir",
82+
os.path.join(workspace, "env"),
83+
)
84+
85+
os.makedirs(work_dir, exist_ok=True)
86+
87+
os.environ["TASKWEAVER_SERVER_HOST"] = effective_host
88+
os.environ["TASKWEAVER_SERVER_PORT"] = str(effective_port)
89+
os.environ["TASKWEAVER_SERVER_WORK_DIR"] = work_dir
90+
if effective_api_key:
91+
os.environ["TASKWEAVER_SERVER_API_KEY"] = effective_api_key
92+
93+
click.echo()
94+
click.echo("=" * 60)
95+
click.echo(" TaskWeaver Code Execution Server")
96+
click.echo("=" * 60)
97+
click.echo(f" Project: {ctx_obj.workspace}")
98+
click.echo(f" Host: {effective_host}")
99+
click.echo(f" Port: {effective_port}")
100+
click.echo(f" URL: http://{effective_host}:{effective_port}")
101+
click.echo(f" Health: http://{effective_host}:{effective_port}/api/v1/health")
102+
click.echo(f" Work Dir: {work_dir}")
103+
click.echo(f" API Key: {'configured' if effective_api_key else 'not required (localhost)'}")
104+
click.echo("=" * 60)
105+
click.echo()
106+
click.echo("To connect a chat session, run in another terminal:")
107+
click.echo(f" taskweaver -p {ctx_obj.workspace} chat --server-url http://{effective_host}:{effective_port}")
108+
click.echo()
109+
110+
try:
111+
import uvicorn
112+
except ImportError:
113+
click.secho(
114+
"Error: uvicorn is required to run the server. " "Please install it with: pip install uvicorn",
115+
fg="red",
116+
)
117+
raise SystemExit(1)
118+
119+
try:
120+
import fastapi # noqa: F401
121+
except ImportError:
122+
click.secho(
123+
"Error: fastapi is required to run the server. " "Please install it with: pip install fastapi",
124+
fg="red",
125+
)
126+
raise SystemExit(1)
127+
128+
uvicorn.run(
129+
"taskweaver.ces.server.app:app",
130+
host=effective_host,
131+
port=effective_port,
132+
reload=reload,
133+
log_level=log_level,
134+
)

taskweaver/cli/util.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class CliContext:
3333
workspace_param: Optional[str]
3434
is_workspace_valid: bool
3535
is_workspace_empty: bool
36+
server_url: Optional[str] = None
3637

3738

3839
def center_cli_str(text: str, width: Optional[int] = None):

taskweaver/module/execution_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def _configure(self) -> None:
2828
)
2929
self.server_auto_start = self._get_bool(
3030
"server.auto_start",
31-
True,
31+
False,
3232
)
3333
self.server_container = self._get_bool(
3434
"server.container",

0 commit comments

Comments
 (0)