Files
zk-data-agent/src/main.py
T

1529 lines
69 KiB
Python

from __future__ import annotations
import argparse
import os
import sys
from pathlib import Path
from dataclasses import replace
import json
from typing import Callable
from .background_runtime import BackgroundSessionRuntime, build_background_worker_command
from .account_runtime import AccountRuntime
from .ask_user_runtime import AskUserRuntime
from .agent_runtime import LocalCodingAgent
from .agent_types import (
AgentPermissions,
AgentRuntimeConfig,
BudgetConfig,
ModelConfig,
ModelPricing,
OutputSchemaConfig,
)
from .bootstrap_graph import build_bootstrap_graph
from .command_graph import build_command_graph
from .commands import execute_command, get_command, get_commands, render_command_index
from .config_runtime import ConfigRuntime
from .lsp_runtime import LSPRuntime
from .mcp_runtime import MCPRuntime
from .parity_audit import run_parity_audit
from .permissions import ToolPermissionContext
from .port_manifest import build_port_manifest
from .query_engine import QueryEnginePort
from .remote_runtime import (
RemoteRuntime,
run_deep_link_mode,
run_direct_connect_mode,
run_remote_mode,
run_ssh_mode,
run_teleport_mode,
)
from .remote_trigger_runtime import RemoteTriggerRuntime
from .search_runtime import SearchRuntime
from .team_runtime import TeamRuntime
from .task_runtime import TaskRuntime
from .workflow_runtime import WorkflowRuntime
from .worktree_runtime import WorktreeRuntime
from .runtime import PortRuntime
from .session_store import (
StoredAgentSession,
deserialize_model_config,
deserialize_runtime_config,
load_agent_session,
load_session,
)
from .setup import run_setup
from .tool_pool import assemble_tool_pool
from .tools import execute_tool, get_tool, get_tools, render_tool_index
def _add_agent_common_args(parser: argparse.ArgumentParser, *, include_backend: bool) -> None:
parser.add_argument('--model', default=os.environ.get('OPENAI_MODEL', 'Qwen/Qwen3-Coder-30B-A3B-Instruct'))
if include_backend:
parser.add_argument('--base-url', default=os.environ.get('OPENAI_BASE_URL', 'http://127.0.0.1:8000/v1'))
parser.add_argument('--api-key', default=os.environ.get('OPENAI_API_KEY', 'local-token'))
parser.add_argument('--temperature', type=float, default=0.0)
parser.add_argument('--timeout-seconds', type=float, default=120.0)
parser.add_argument('--input-cost-per-million', type=float, default=0.0)
parser.add_argument('--output-cost-per-million', type=float, default=0.0)
parser.add_argument('--cwd', default='.')
parser.add_argument('--add-dir', action='append', default=[])
parser.add_argument('--disable-claude-md', action='store_true')
parser.add_argument('--allow-write', action='store_true')
parser.add_argument('--allow-shell', action='store_true')
parser.add_argument('--unsafe', action='store_true')
parser.add_argument('--stream', action='store_true')
parser.add_argument('--auto-snip-threshold', type=int)
parser.add_argument('--auto-compact-threshold', type=int)
parser.add_argument('--compact-preserve-messages', type=int, default=4)
parser.add_argument('--max-total-tokens', type=int)
parser.add_argument('--max-input-tokens', type=int)
parser.add_argument('--max-output-tokens', type=int)
parser.add_argument('--max-reasoning-tokens', type=int)
parser.add_argument('--max-budget-usd', type=float)
parser.add_argument('--max-tool-calls', type=int)
parser.add_argument('--max-delegated-tasks', type=int)
parser.add_argument('--max-model-calls', type=int)
parser.add_argument('--max-session-turns', type=int)
parser.add_argument('--response-schema-file')
parser.add_argument('--response-schema-name')
parser.add_argument('--response-schema-strict', action='store_true')
parser.add_argument('--scratchpad-root')
parser.add_argument('--system-prompt')
parser.add_argument('--append-system-prompt')
parser.add_argument('--override-system-prompt')
def _build_runtime_config(args: argparse.Namespace) -> AgentRuntimeConfig:
return AgentRuntimeConfig(
cwd=Path(args.cwd).resolve(),
max_turns=getattr(args, 'max_turns', 12),
permissions=AgentPermissions(
allow_file_write=args.allow_write,
allow_shell_commands=args.allow_shell,
allow_destructive_shell_commands=args.unsafe,
),
stream_model_responses=bool(getattr(args, 'stream', False)),
auto_snip_threshold_tokens=getattr(args, 'auto_snip_threshold', None),
auto_compact_threshold_tokens=getattr(args, 'auto_compact_threshold', None),
compact_preserve_messages=max(0, int(getattr(args, 'compact_preserve_messages', 4))),
additional_working_directories=tuple(Path(path).resolve() for path in args.add_dir),
disable_claude_md_discovery=args.disable_claude_md,
budget_config=BudgetConfig(
max_total_tokens=getattr(args, 'max_total_tokens', None),
max_input_tokens=getattr(args, 'max_input_tokens', None),
max_output_tokens=getattr(args, 'max_output_tokens', None),
max_reasoning_tokens=getattr(args, 'max_reasoning_tokens', None),
max_total_cost_usd=getattr(args, 'max_budget_usd', None),
max_tool_calls=getattr(args, 'max_tool_calls', None),
max_delegated_tasks=getattr(args, 'max_delegated_tasks', None),
max_model_calls=getattr(args, 'max_model_calls', None),
max_session_turns=getattr(args, 'max_session_turns', None),
),
output_schema=_load_output_schema_config(args),
session_directory=(Path('.port_sessions') / 'agent').resolve(),
scratchpad_root=(
Path(getattr(args, 'scratchpad_root')).resolve()
if getattr(args, 'scratchpad_root', None)
else (Path('.port_sessions') / 'scratchpad').resolve()
),
)
def _build_model_config(args: argparse.Namespace) -> ModelConfig:
return ModelConfig(
model=args.model,
base_url=getattr(args, 'base_url', os.environ.get('OPENAI_BASE_URL', 'http://127.0.0.1:8000/v1')),
api_key=getattr(args, 'api_key', os.environ.get('OPENAI_API_KEY', 'local-token')),
temperature=getattr(args, 'temperature', 0.0),
timeout_seconds=getattr(args, 'timeout_seconds', 120.0),
pricing=ModelPricing(
input_cost_per_million_tokens_usd=float(
getattr(args, 'input_cost_per_million', 0.0) or 0.0
),
output_cost_per_million_tokens_usd=float(
getattr(args, 'output_cost_per_million', 0.0) or 0.0
),
),
)
def _load_output_schema_config(args: argparse.Namespace) -> OutputSchemaConfig | None:
schema_file = getattr(args, 'response_schema_file', None)
if not schema_file:
return None
payload = json.loads(Path(schema_file).read_text(encoding='utf-8'))
if not isinstance(payload, dict):
raise ValueError('response schema file must contain a top-level JSON object')
name = getattr(args, 'response_schema_name', None) or Path(schema_file).stem
return OutputSchemaConfig(
name=name,
schema=payload,
strict=bool(getattr(args, 'response_schema_strict', False)),
)
def _build_agent(args: argparse.Namespace) -> LocalCodingAgent:
return LocalCodingAgent(
model_config=_build_model_config(args),
runtime_config=_build_runtime_config(args),
custom_system_prompt=args.system_prompt,
append_system_prompt=args.append_system_prompt,
override_system_prompt=args.override_system_prompt,
)
def _append_agent_forwarded_args(
command: list[str],
args: argparse.Namespace,
*,
include_backend: bool,
) -> None:
command.extend(['--cwd', str(args.cwd)])
command.extend(['--max-turns', str(getattr(args, 'max_turns', 12))])
if include_backend:
command.extend(['--model', str(args.model)])
command.extend(['--base-url', str(args.base_url)])
command.extend(['--api-key', str(args.api_key)])
command.extend(['--temperature', str(args.temperature)])
command.extend(['--timeout-seconds', str(args.timeout_seconds)])
command.extend(['--input-cost-per-million', str(args.input_cost_per_million)])
command.extend(['--output-cost-per-million', str(args.output_cost_per_million)])
else:
command.extend(['--model', str(args.model)])
for path in getattr(args, 'add_dir', []):
command.extend(['--add-dir', str(path)])
for flag in (
('--disable-claude-md', getattr(args, 'disable_claude_md', False)),
('--allow-write', getattr(args, 'allow_write', False)),
('--allow-shell', getattr(args, 'allow_shell', False)),
('--unsafe', getattr(args, 'unsafe', False)),
('--stream', getattr(args, 'stream', False)),
('--show-transcript', getattr(args, 'show_transcript', False)),
(
'--response-schema-strict',
getattr(args, 'response_schema_strict', False),
),
):
if flag[1]:
command.append(flag[0])
for name, value in (
('--auto-snip-threshold', getattr(args, 'auto_snip_threshold', None)),
('--auto-compact-threshold', getattr(args, 'auto_compact_threshold', None)),
('--compact-preserve-messages', getattr(args, 'compact_preserve_messages', None)),
('--max-total-tokens', getattr(args, 'max_total_tokens', None)),
('--max-input-tokens', getattr(args, 'max_input_tokens', None)),
('--max-output-tokens', getattr(args, 'max_output_tokens', None)),
('--max-reasoning-tokens', getattr(args, 'max_reasoning_tokens', None)),
('--max-budget-usd', getattr(args, 'max_budget_usd', None)),
('--max-tool-calls', getattr(args, 'max_tool_calls', None)),
('--max-delegated-tasks', getattr(args, 'max_delegated_tasks', None)),
('--max-model-calls', getattr(args, 'max_model_calls', None)),
('--max-session-turns', getattr(args, 'max_session_turns', None)),
('--response-schema-file', getattr(args, 'response_schema_file', None)),
('--response-schema-name', getattr(args, 'response_schema_name', None)),
('--scratchpad-root', getattr(args, 'scratchpad_root', None)),
('--system-prompt', getattr(args, 'system_prompt', None)),
('--append-system-prompt', getattr(args, 'append_system_prompt', None)),
('--override-system-prompt', getattr(args, 'override_system_prompt', None)),
):
if value is not None:
command.extend([name, str(value)])
def _add_agent_resume_args(parser: argparse.ArgumentParser) -> None:
parser.add_argument('session_id')
parser.add_argument('prompt')
parser.add_argument('--max-turns', type=int)
parser.add_argument('--show-transcript', action='store_true')
parser.add_argument('--model')
parser.add_argument('--base-url')
parser.add_argument('--api-key')
parser.add_argument('--temperature', type=float)
parser.add_argument('--timeout-seconds', type=float)
parser.add_argument('--input-cost-per-million', type=float)
parser.add_argument('--output-cost-per-million', type=float)
parser.add_argument('--allow-write', action='store_true')
parser.add_argument('--allow-shell', action='store_true')
parser.add_argument('--unsafe', action='store_true')
parser.add_argument('--stream', action='store_true')
parser.add_argument('--auto-snip-threshold', type=int)
parser.add_argument('--auto-compact-threshold', type=int)
parser.add_argument('--compact-preserve-messages', type=int)
parser.add_argument('--max-total-tokens', type=int)
parser.add_argument('--max-input-tokens', type=int)
parser.add_argument('--max-output-tokens', type=int)
parser.add_argument('--max-reasoning-tokens', type=int)
parser.add_argument('--max-budget-usd', type=float)
parser.add_argument('--max-tool-calls', type=int)
parser.add_argument('--max-delegated-tasks', type=int)
parser.add_argument('--max-model-calls', type=int)
parser.add_argument('--max-session-turns', type=int)
parser.add_argument('--response-schema-file')
parser.add_argument('--response-schema-name')
parser.add_argument('--response-schema-strict', action='store_true')
parser.add_argument('--scratchpad-root')
def _launch_background_agent(args: argparse.Namespace) -> int:
background_runtime = BackgroundSessionRuntime()
background_id = background_runtime.create_id()
forwarded_args: list[str] = []
_append_agent_forwarded_args(forwarded_args, args, include_backend=True)
forwarded_args.extend(['--background-root', str(background_runtime.root)])
command = build_background_worker_command(
background_id=background_id,
prompt=args.prompt,
forwarded_args=forwarded_args,
)
record = background_runtime.launch(
command,
prompt=args.prompt,
workspace_cwd=Path(args.cwd).resolve(),
model=args.model,
background_id=background_id,
process_cwd=Path(__file__).resolve().parent.parent,
)
print('# Background Session')
print(f'background_id={record.background_id}')
print(f'pid={record.pid}')
print(f'log_path={record.log_path}')
print(f'record_path={record.record_path}')
return 0
def _run_background_worker(args: argparse.Namespace) -> int:
background_runtime = BackgroundSessionRuntime(Path(args.background_root))
exit_code = 1
stop_reason = 'worker_failed'
session_id = None
session_path = None
try:
agent = _build_agent(args)
result = agent.run(args.prompt)
_print_agent_result(result, show_transcript=args.show_transcript)
exit_code = 0
stop_reason = result.stop_reason or 'completed'
session_id = result.session_id
session_path = result.session_path
return 0
finally:
background_runtime.mark_finished(
args.background_id,
exit_code=exit_code,
stop_reason=stop_reason,
session_id=session_id,
session_path=session_path,
)
def _build_resumed_agent(args: argparse.Namespace) -> tuple[LocalCodingAgent, StoredAgentSession]:
stored_session = load_agent_session(args.session_id)
model_config = deserialize_model_config(stored_session.model_config)
runtime_config = deserialize_runtime_config(stored_session.runtime_config)
if args.model:
model_config = replace(model_config, model=args.model)
if args.base_url:
model_config = replace(model_config, base_url=args.base_url)
if args.api_key:
model_config = replace(model_config, api_key=args.api_key)
if args.temperature is not None:
model_config = replace(model_config, temperature=args.temperature)
if args.timeout_seconds is not None:
model_config = replace(model_config, timeout_seconds=args.timeout_seconds)
if args.input_cost_per_million is not None or args.output_cost_per_million is not None:
model_config = replace(
model_config,
pricing=replace(
model_config.pricing,
input_cost_per_million_tokens_usd=(
args.input_cost_per_million
if args.input_cost_per_million is not None
else model_config.pricing.input_cost_per_million_tokens_usd
),
output_cost_per_million_tokens_usd=(
args.output_cost_per_million
if args.output_cost_per_million is not None
else model_config.pricing.output_cost_per_million_tokens_usd
),
),
)
if args.max_turns is not None:
runtime_config = replace(runtime_config, max_turns=args.max_turns)
if args.allow_write or args.allow_shell or args.unsafe:
runtime_config = replace(
runtime_config,
permissions=AgentPermissions(
allow_file_write=runtime_config.permissions.allow_file_write or args.allow_write,
allow_shell_commands=runtime_config.permissions.allow_shell_commands or args.allow_shell,
allow_destructive_shell_commands=runtime_config.permissions.allow_destructive_shell_commands or args.unsafe,
),
)
if args.stream:
runtime_config = replace(runtime_config, stream_model_responses=True)
if (
args.auto_snip_threshold is not None
or args.auto_compact_threshold is not None
or args.compact_preserve_messages is not None
):
runtime_config = replace(
runtime_config,
auto_snip_threshold_tokens=(
args.auto_snip_threshold
if args.auto_snip_threshold is not None
else runtime_config.auto_snip_threshold_tokens
),
auto_compact_threshold_tokens=(
args.auto_compact_threshold
if args.auto_compact_threshold is not None
else runtime_config.auto_compact_threshold_tokens
),
compact_preserve_messages=(
max(0, args.compact_preserve_messages)
if args.compact_preserve_messages is not None
else runtime_config.compact_preserve_messages
),
)
if (
args.max_total_tokens is not None
or args.max_input_tokens is not None
or args.max_output_tokens is not None
or args.max_reasoning_tokens is not None
or args.max_budget_usd is not None
or args.max_tool_calls is not None
or args.max_delegated_tasks is not None
or args.max_model_calls is not None
or args.max_session_turns is not None
):
runtime_config = replace(
runtime_config,
budget_config=BudgetConfig(
max_total_tokens=(
args.max_total_tokens
if args.max_total_tokens is not None
else runtime_config.budget_config.max_total_tokens
),
max_input_tokens=(
args.max_input_tokens
if args.max_input_tokens is not None
else runtime_config.budget_config.max_input_tokens
),
max_output_tokens=(
args.max_output_tokens
if args.max_output_tokens is not None
else runtime_config.budget_config.max_output_tokens
),
max_reasoning_tokens=(
args.max_reasoning_tokens
if args.max_reasoning_tokens is not None
else runtime_config.budget_config.max_reasoning_tokens
),
max_total_cost_usd=(
args.max_budget_usd
if args.max_budget_usd is not None
else runtime_config.budget_config.max_total_cost_usd
),
max_tool_calls=(
args.max_tool_calls
if args.max_tool_calls is not None
else runtime_config.budget_config.max_tool_calls
),
max_delegated_tasks=(
args.max_delegated_tasks
if args.max_delegated_tasks is not None
else runtime_config.budget_config.max_delegated_tasks
),
max_model_calls=(
args.max_model_calls
if args.max_model_calls is not None
else runtime_config.budget_config.max_model_calls
),
max_session_turns=(
args.max_session_turns
if args.max_session_turns is not None
else runtime_config.budget_config.max_session_turns
),
),
)
output_schema = _load_output_schema_config(args)
if output_schema is not None:
runtime_config = replace(runtime_config, output_schema=output_schema)
if args.scratchpad_root:
runtime_config = replace(
runtime_config,
scratchpad_root=Path(args.scratchpad_root).resolve(),
)
agent = LocalCodingAgent(
model_config=model_config,
runtime_config=runtime_config,
)
return agent, stored_session
def _print_agent_result(result, *, show_transcript: bool) -> None:
print(result.final_output)
print('\n# Usage')
print(f'total_tokens={result.usage.total_tokens}')
print(f'input_tokens={result.usage.input_tokens}')
print(f'output_tokens={result.usage.output_tokens}')
print(f'total_cost_usd={result.total_cost_usd:.6f}')
if result.stop_reason:
print(f'stop_reason={result.stop_reason}')
if result.session_id:
print('\n# Session')
print(f'session_id={result.session_id}')
if result.session_path:
print(f'session_path={result.session_path}')
if result.scratchpad_directory:
print(f'scratchpad_directory={result.scratchpad_directory}')
if show_transcript:
print('\n# Transcript')
for message in result.transcript:
role = message.get('role', 'unknown')
print(f'[{role}]')
print(message.get('content', ''))
def _run_agent_chat_loop(
agent: LocalCodingAgent,
*,
initial_prompt: str | None,
resume_session_id: str | None,
show_transcript: bool,
input_func: Callable[[str], str] = input,
output_func: Callable[[str], None] = print,
result_printer: Callable[..., None] = _print_agent_result,
) -> int:
active_session_id = resume_session_id
first_prompt = initial_prompt
output_func('# Agent Chat')
output_func("Enter a prompt. Use '/exit' or '/quit' to stop.")
if active_session_id:
output_func(f'resuming_session_id={active_session_id}')
while True:
if first_prompt is not None:
prompt = first_prompt
first_prompt = None
else:
try:
prompt = input_func('user> ')
except EOFError:
output_func('chat_ended=eof')
return 0
except KeyboardInterrupt:
output_func('\nchat_ended=interrupt')
return 130
normalized = prompt.strip()
if not normalized:
continue
if normalized in {'/exit', '/quit'}:
output_func('chat_ended=user_exit')
return 0
if active_session_id:
stored_session = load_agent_session(
active_session_id,
directory=agent.runtime_config.session_directory,
)
result = agent.resume(prompt, stored_session)
else:
result = agent.run(prompt)
result_printer(result, show_transcript=show_transcript)
active_session_id = result.session_id
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description='Python porting workspace for the Claude Code rewrite effort')
subparsers = parser.add_subparsers(dest='command', required=True)
subparsers.add_parser('summary', help='render a Markdown summary of the Python porting workspace')
subparsers.add_parser('manifest', help='print the current Python workspace manifest')
subparsers.add_parser('parity-audit', help='compare the Python workspace against the local ignored TypeScript archive when available')
subparsers.add_parser('setup-report', help='render the startup/prefetch setup report')
subparsers.add_parser('command-graph', help='show command graph segmentation')
subparsers.add_parser('tool-pool', help='show assembled tool pool with default settings')
subparsers.add_parser('bootstrap-graph', help='show the mirrored bootstrap/runtime graph stages')
list_parser = subparsers.add_parser('subsystems', help='list the current Python modules in the workspace')
list_parser.add_argument('--limit', type=int, default=32)
commands_parser = subparsers.add_parser('commands', help='list mirrored command entries from the archived snapshot')
commands_parser.add_argument('--limit', type=int, default=20)
commands_parser.add_argument('--query')
commands_parser.add_argument('--no-plugin-commands', action='store_true')
commands_parser.add_argument('--no-skill-commands', action='store_true')
tools_parser = subparsers.add_parser('tools', help='list mirrored tool entries from the archived snapshot')
tools_parser.add_argument('--limit', type=int, default=20)
tools_parser.add_argument('--query')
tools_parser.add_argument('--simple-mode', action='store_true')
tools_parser.add_argument('--no-mcp', action='store_true')
tools_parser.add_argument('--deny-tool', action='append', default=[])
tools_parser.add_argument('--deny-prefix', action='append', default=[])
route_parser = subparsers.add_parser('route', help='route a prompt across mirrored command/tool inventories')
route_parser.add_argument('prompt')
route_parser.add_argument('--limit', type=int, default=5)
bootstrap_parser = subparsers.add_parser('bootstrap', help='build a runtime-style session report from the mirrored inventories')
bootstrap_parser.add_argument('prompt')
bootstrap_parser.add_argument('--limit', type=int, default=5)
loop_parser = subparsers.add_parser('turn-loop', help='run a small stateful turn loop for the mirrored runtime')
loop_parser.add_argument('prompt')
loop_parser.add_argument('--limit', type=int, default=5)
loop_parser.add_argument('--max-turns', type=int, default=3)
loop_parser.add_argument('--structured-output', action='store_true')
flush_parser = subparsers.add_parser('flush-transcript', help='persist and flush a temporary session transcript')
flush_parser.add_argument('prompt')
load_session_parser = subparsers.add_parser('load-session', help='load a previously persisted session')
load_session_parser.add_argument('session_id')
remote_parser = subparsers.add_parser('remote-mode', help='simulate remote-control runtime branching')
remote_parser.add_argument('target')
remote_parser.add_argument('--cwd', default='.')
ssh_parser = subparsers.add_parser('ssh-mode', help='simulate SSH runtime branching')
ssh_parser.add_argument('target')
ssh_parser.add_argument('--cwd', default='.')
teleport_parser = subparsers.add_parser('teleport-mode', help='simulate teleport runtime branching')
teleport_parser.add_argument('target')
teleport_parser.add_argument('--cwd', default='.')
direct_parser = subparsers.add_parser('direct-connect-mode', help='simulate direct-connect runtime branching')
direct_parser.add_argument('target')
direct_parser.add_argument('--cwd', default='.')
deep_link_parser = subparsers.add_parser('deep-link-mode', help='simulate deep-link runtime branching')
deep_link_parser.add_argument('target')
deep_link_parser.add_argument('--cwd', default='.')
remote_status_parser = subparsers.add_parser('remote-status', help='show local remote runtime status')
remote_status_parser.add_argument('--cwd', default='.')
remote_profiles_parser = subparsers.add_parser('remote-profiles', help='list configured local remote profiles')
remote_profiles_parser.add_argument('--cwd', default='.')
remote_profiles_parser.add_argument('--query')
remote_disconnect_parser = subparsers.add_parser('remote-disconnect', help='disconnect the active local remote target')
remote_disconnect_parser.add_argument('--cwd', default='.')
worktree_status_parser = subparsers.add_parser('worktree-status', help='show local managed git worktree status')
worktree_status_parser.add_argument('--cwd', default='.')
worktree_enter_parser = subparsers.add_parser('worktree-enter', help='create and enter a managed git worktree')
worktree_enter_parser.add_argument('name', nargs='?')
worktree_enter_parser.add_argument('--cwd', default='.')
worktree_exit_parser = subparsers.add_parser('worktree-exit', help='exit the active managed git worktree')
worktree_exit_parser.add_argument('--action', default='keep')
worktree_exit_parser.add_argument('--discard-changes', action='store_true')
worktree_exit_parser.add_argument('--cwd', default='.')
account_status_parser = subparsers.add_parser('account-status', help='show local account runtime status')
account_status_parser.add_argument('--cwd', default='.')
account_profiles_parser = subparsers.add_parser('account-profiles', help='list configured local account profiles')
account_profiles_parser.add_argument('--cwd', default='.')
account_profiles_parser.add_argument('--query')
account_login_parser = subparsers.add_parser('account-login', help='activate a local account profile or ephemeral identity')
account_login_parser.add_argument('target')
account_login_parser.add_argument('--provider')
account_login_parser.add_argument('--auth-mode')
account_login_parser.add_argument('--cwd', default='.')
account_logout_parser = subparsers.add_parser('account-logout', help='clear the active local account session')
account_logout_parser.add_argument('--cwd', default='.')
ask_status_parser = subparsers.add_parser('ask-status', help='show local ask-user runtime status')
ask_status_parser.add_argument('--cwd', default='.')
ask_history_parser = subparsers.add_parser('ask-history', help='show local ask-user interaction history')
ask_history_parser.add_argument('--cwd', default='.')
search_status_parser = subparsers.add_parser('search-status', help='show local search runtime status')
search_status_parser.add_argument('--cwd', default='.')
search_status_parser.add_argument('--provider')
search_providers_parser = subparsers.add_parser('search-providers', help='list configured local search providers')
search_providers_parser.add_argument('--cwd', default='.')
search_providers_parser.add_argument('--query')
search_activate_parser = subparsers.add_parser('search-activate', help='set the active local search provider')
search_activate_parser.add_argument('provider')
search_activate_parser.add_argument('--cwd', default='.')
search_parser = subparsers.add_parser('search', help='run a real web search against the configured local search runtime')
search_parser.add_argument('query')
search_parser.add_argument('--cwd', default='.')
search_parser.add_argument('--provider')
search_parser.add_argument('--max-results', type=int, default=5)
search_parser.add_argument('--domain', action='append', default=[])
mcp_status_parser = subparsers.add_parser('mcp-status', help='show local MCP runtime status')
mcp_status_parser.add_argument('--cwd', default='.')
mcp_resources_parser = subparsers.add_parser('mcp-resources', help='list MCP resources discovered through local manifests and transport-backed servers')
mcp_resources_parser.add_argument('--cwd', default='.')
mcp_resources_parser.add_argument('--query')
mcp_resource_parser = subparsers.add_parser('mcp-resource', help='read an MCP resource by URI')
mcp_resource_parser.add_argument('uri')
mcp_resource_parser.add_argument('--cwd', default='.')
mcp_tools_parser = subparsers.add_parser('mcp-tools', help='list MCP tools exposed by configured MCP servers')
mcp_tools_parser.add_argument('--cwd', default='.')
mcp_tools_parser.add_argument('--query')
mcp_tools_parser.add_argument('--server')
mcp_call_tool_parser = subparsers.add_parser('mcp-call-tool', help='call an MCP tool exposed by a configured MCP server')
mcp_call_tool_parser.add_argument('tool_name')
mcp_call_tool_parser.add_argument('--arguments-json', default='{}')
mcp_call_tool_parser.add_argument('--server')
mcp_call_tool_parser.add_argument('--cwd', default='.')
config_status_parser = subparsers.add_parser('config-status', help='show local workspace config runtime summary')
config_status_parser.add_argument('--cwd', default='.')
config_effective_parser = subparsers.add_parser('config-effective', help='render the merged effective local workspace config')
config_effective_parser.add_argument('--cwd', default='.')
config_source_parser = subparsers.add_parser('config-source', help='render a specific local config source')
config_source_parser.add_argument('source')
config_source_parser.add_argument('--cwd', default='.')
config_get_parser = subparsers.add_parser('config-get', help='read a local config value by dotted key path')
config_get_parser.add_argument('key_path')
config_get_parser.add_argument('--source')
config_get_parser.add_argument('--cwd', default='.')
config_set_parser = subparsers.add_parser('config-set', help='write a local config value by dotted key path')
config_set_parser.add_argument('key_path')
config_set_parser.add_argument('value_json')
config_set_parser.add_argument('--source', default='local')
config_set_parser.add_argument('--cwd', default='.')
lsp_status_parser = subparsers.add_parser('lsp-status', help='show local LSP runtime summary')
lsp_status_parser.add_argument('--cwd', default='.')
lsp_symbols_parser = subparsers.add_parser('lsp-symbols', help='show local LSP document symbols for one file')
lsp_symbols_parser.add_argument('file_path')
lsp_symbols_parser.add_argument('--cwd', default='.')
lsp_workspace_parser = subparsers.add_parser('lsp-workspace-symbols', help='search workspace symbols through the local LSP runtime')
lsp_workspace_parser.add_argument('query')
lsp_workspace_parser.add_argument('--max-results', type=int, default=50)
lsp_workspace_parser.add_argument('--cwd', default='.')
lsp_definition_parser = subparsers.add_parser('lsp-definition', help='run a local LSP definition query')
lsp_definition_parser.add_argument('file_path')
lsp_definition_parser.add_argument('line', type=int)
lsp_definition_parser.add_argument('character', type=int)
lsp_definition_parser.add_argument('--max-results', type=int, default=20)
lsp_definition_parser.add_argument('--cwd', default='.')
lsp_references_parser = subparsers.add_parser('lsp-references', help='run a local LSP references query')
lsp_references_parser.add_argument('file_path')
lsp_references_parser.add_argument('line', type=int)
lsp_references_parser.add_argument('character', type=int)
lsp_references_parser.add_argument('--max-results', type=int, default=50)
lsp_references_parser.add_argument('--cwd', default='.')
lsp_hover_parser = subparsers.add_parser('lsp-hover', help='run a local LSP hover query')
lsp_hover_parser.add_argument('file_path')
lsp_hover_parser.add_argument('line', type=int)
lsp_hover_parser.add_argument('character', type=int)
lsp_hover_parser.add_argument('--cwd', default='.')
lsp_diagnostics_parser = subparsers.add_parser('lsp-diagnostics', help='show local LSP diagnostics')
lsp_diagnostics_parser.add_argument('--file-path')
lsp_diagnostics_parser.add_argument('--cwd', default='.')
lsp_hierarchy_parser = subparsers.add_parser('lsp-call-hierarchy', help='show local LSP call hierarchy at a position')
lsp_hierarchy_parser.add_argument('file_path')
lsp_hierarchy_parser.add_argument('line', type=int)
lsp_hierarchy_parser.add_argument('character', type=int)
lsp_hierarchy_parser.add_argument('--cwd', default='.')
lsp_incoming_parser = subparsers.add_parser('lsp-incoming-calls', help='show local LSP incoming calls at a position')
lsp_incoming_parser.add_argument('file_path')
lsp_incoming_parser.add_argument('line', type=int)
lsp_incoming_parser.add_argument('character', type=int)
lsp_incoming_parser.add_argument('--max-results', type=int, default=50)
lsp_incoming_parser.add_argument('--cwd', default='.')
lsp_outgoing_parser = subparsers.add_parser('lsp-outgoing-calls', help='show local LSP outgoing calls at a position')
lsp_outgoing_parser.add_argument('file_path')
lsp_outgoing_parser.add_argument('line', type=int)
lsp_outgoing_parser.add_argument('character', type=int)
lsp_outgoing_parser.add_argument('--max-results', type=int, default=50)
lsp_outgoing_parser.add_argument('--cwd', default='.')
workflow_list_parser = subparsers.add_parser('workflow-list', help='list local workflow definitions')
workflow_list_parser.add_argument('--cwd', default='.')
workflow_list_parser.add_argument('--query')
workflow_get_parser = subparsers.add_parser('workflow-get', help='show one local workflow definition')
workflow_get_parser.add_argument('workflow_name')
workflow_get_parser.add_argument('--cwd', default='.')
workflow_run_parser = subparsers.add_parser('workflow-run', help='record and render a local workflow run')
workflow_run_parser.add_argument('workflow_name')
workflow_run_parser.add_argument('--arguments-json', default='{}')
workflow_run_parser.add_argument('--cwd', default='.')
trigger_list_parser = subparsers.add_parser('trigger-list', help='list local remote triggers')
trigger_list_parser.add_argument('--cwd', default='.')
trigger_list_parser.add_argument('--query')
trigger_get_parser = subparsers.add_parser('trigger-get', help='show one local remote trigger')
trigger_get_parser.add_argument('trigger_id')
trigger_get_parser.add_argument('--cwd', default='.')
trigger_create_parser = subparsers.add_parser('trigger-create', help='create a local remote trigger')
trigger_create_parser.add_argument('--body-json', required=True)
trigger_create_parser.add_argument('--cwd', default='.')
trigger_update_parser = subparsers.add_parser('trigger-update', help='update a local remote trigger')
trigger_update_parser.add_argument('trigger_id')
trigger_update_parser.add_argument('--body-json', required=True)
trigger_update_parser.add_argument('--cwd', default='.')
trigger_run_parser = subparsers.add_parser('trigger-run', help='run a local remote trigger')
trigger_run_parser.add_argument('trigger_id')
trigger_run_parser.add_argument('--body-json', default='{}')
trigger_run_parser.add_argument('--cwd', default='.')
teams_status_parser = subparsers.add_parser('team-status', help='show local collaboration team runtime summary')
teams_status_parser.add_argument('--cwd', default='.')
teams_list_parser = subparsers.add_parser('team-list', help='list local collaboration teams')
teams_list_parser.add_argument('--cwd', default='.')
teams_list_parser.add_argument('--query')
team_get_parser = subparsers.add_parser('team-get', help='show one local collaboration team')
team_get_parser.add_argument('team_name')
team_get_parser.add_argument('--cwd', default='.')
team_create_parser = subparsers.add_parser('team-create', help='create a local collaboration team')
team_create_parser.add_argument('team_name')
team_create_parser.add_argument('--description')
team_create_parser.add_argument('--member', action='append', default=[])
team_create_parser.add_argument('--cwd', default='.')
team_delete_parser = subparsers.add_parser('team-delete', help='delete a local collaboration team')
team_delete_parser.add_argument('team_name')
team_delete_parser.add_argument('--cwd', default='.')
team_messages_parser = subparsers.add_parser('team-messages', help='show local team messages')
team_messages_parser.add_argument('--team-name')
team_messages_parser.add_argument('--cwd', default='.')
show_command = subparsers.add_parser('show-command', help='show one mirrored command entry by exact name')
show_command.add_argument('name')
show_tool = subparsers.add_parser('show-tool', help='show one mirrored tool entry by exact name')
show_tool.add_argument('name')
exec_command_parser = subparsers.add_parser('exec-command', help='execute a mirrored command shim by exact name')
exec_command_parser.add_argument('name')
exec_command_parser.add_argument('prompt')
exec_tool_parser = subparsers.add_parser('exec-tool', help='execute a mirrored tool shim by exact name')
exec_tool_parser.add_argument('name')
exec_tool_parser.add_argument('payload')
agent_parser = subparsers.add_parser('agent', help='run the real Python local-model agent')
agent_parser.add_argument('prompt')
agent_parser.add_argument('--max-turns', type=int, default=12)
agent_parser.add_argument('--show-transcript', action='store_true')
_add_agent_common_args(agent_parser, include_backend=True)
background_parser = subparsers.add_parser('agent-bg', help='run the Python local-model agent as a local background session')
background_parser.add_argument('prompt')
background_parser.add_argument('--max-turns', type=int, default=12)
background_parser.add_argument('--show-transcript', action='store_true')
_add_agent_common_args(background_parser, include_backend=True)
background_worker_parser = subparsers.add_parser('agent-bg-worker', help=argparse.SUPPRESS)
background_worker_parser.add_argument('background_id')
background_worker_parser.add_argument('prompt')
background_worker_parser.add_argument('--background-root', required=True)
background_worker_parser.add_argument('--max-turns', type=int, default=12)
background_worker_parser.add_argument('--show-transcript', action='store_true')
_add_agent_common_args(background_worker_parser, include_backend=True)
ps_parser = subparsers.add_parser('agent-ps', help='list local background agent sessions')
ps_parser.add_argument('--tail', type=int, default=None)
logs_parser = subparsers.add_parser('agent-logs', help='show logs for a local background agent session')
logs_parser.add_argument('background_id')
logs_parser.add_argument('--tail', type=int, default=None)
attach_parser = subparsers.add_parser('agent-attach', help='show the current output snapshot for a local background agent session')
attach_parser.add_argument('background_id')
attach_parser.add_argument('--tail', type=int, default=None)
kill_parser = subparsers.add_parser('agent-kill', help='stop a local background agent session')
kill_parser.add_argument('background_id')
daemon_parser = subparsers.add_parser('daemon', help='manage local daemon-style background agent sessions')
daemon_subparsers = daemon_parser.add_subparsers(dest='daemon_command')
daemon_subparsers.required = True
daemon_start_parser = daemon_subparsers.add_parser('start', help='launch a local daemon-style background agent session')
daemon_start_parser.add_argument('prompt')
daemon_start_parser.add_argument('--max-turns', type=int, default=12)
daemon_start_parser.add_argument('--show-transcript', action='store_true')
_add_agent_common_args(daemon_start_parser, include_backend=True)
daemon_worker_parser = daemon_subparsers.add_parser('worker', help=argparse.SUPPRESS)
daemon_worker_parser.add_argument('background_id')
daemon_worker_parser.add_argument('prompt')
daemon_worker_parser.add_argument('--background-root', required=True)
daemon_worker_parser.add_argument('--max-turns', type=int, default=12)
daemon_worker_parser.add_argument('--show-transcript', action='store_true')
_add_agent_common_args(daemon_worker_parser, include_backend=True)
daemon_ps_parser = daemon_subparsers.add_parser('ps', help='list local daemon-style background sessions')
daemon_ps_parser.add_argument('--tail', type=int, default=None)
daemon_logs_parser = daemon_subparsers.add_parser('logs', help='show logs for a local daemon-style background session')
daemon_logs_parser.add_argument('background_id')
daemon_logs_parser.add_argument('--tail', type=int, default=None)
daemon_attach_parser = daemon_subparsers.add_parser('attach', help='show the current output snapshot for a local daemon-style background session')
daemon_attach_parser.add_argument('background_id')
daemon_attach_parser.add_argument('--tail', type=int, default=None)
daemon_kill_parser = daemon_subparsers.add_parser('kill', help='stop a local daemon-style background session')
daemon_kill_parser.add_argument('background_id')
chat_parser = subparsers.add_parser('agent-chat', help='run an interactive Python local-model chat loop')
chat_parser.add_argument('prompt', nargs='?')
chat_parser.add_argument('--resume-session-id')
chat_parser.add_argument('--max-turns', type=int, default=12)
chat_parser.add_argument('--show-transcript', action='store_true')
_add_agent_common_args(chat_parser, include_backend=True)
resume_parser = subparsers.add_parser('agent-resume', help='resume a saved Python local-model agent session')
_add_agent_resume_args(resume_parser)
prompt_parser = subparsers.add_parser('agent-prompt', help='render the Python agent system prompt')
_add_agent_common_args(prompt_parser, include_backend=False)
context_parser = subparsers.add_parser('agent-context', help='render Python /context-style usage accounting')
_add_agent_common_args(context_parser, include_backend=False)
context_raw_parser = subparsers.add_parser('agent-context-raw', help='render the raw Python agent context snapshot')
_add_agent_common_args(context_raw_parser, include_backend=False)
token_budget_parser = subparsers.add_parser('token-budget', help='render the current token budget and prompt-length limits')
_add_agent_common_args(token_budget_parser, include_backend=False)
agents_parser = subparsers.add_parser('agents', help='list active local agent configurations or show one agent definition')
agents_parser.add_argument('agent_type', nargs='?')
agents_parser.add_argument('--all', action='store_true')
_add_agent_common_args(agents_parser, include_backend=False)
return parser
def main(argv: list[str] | None = None) -> int:
parser = build_parser()
args = parser.parse_args(argv)
manifest = build_port_manifest()
if args.command == 'summary':
print(QueryEnginePort(manifest).render_summary())
return 0
if args.command == 'manifest':
print(manifest.to_markdown())
return 0
if args.command == 'parity-audit':
print(run_parity_audit().to_markdown())
return 0
if args.command == 'setup-report':
print(run_setup().as_markdown())
return 0
if args.command == 'command-graph':
print(build_command_graph().as_markdown())
return 0
if args.command == 'tool-pool':
print(assemble_tool_pool().as_markdown())
return 0
if args.command == 'bootstrap-graph':
print(build_bootstrap_graph().as_markdown())
return 0
if args.command == 'subsystems':
for subsystem in manifest.top_level_modules[: args.limit]:
print(f'{subsystem.name}\t{subsystem.file_count}\t{subsystem.notes}')
return 0
if args.command == 'commands':
if args.query:
print(render_command_index(limit=args.limit, query=args.query))
else:
commands = get_commands(
include_plugin_commands=not args.no_plugin_commands,
include_skill_commands=not args.no_skill_commands,
)
output_lines = [f'Command entries: {len(commands)}', '']
output_lines.extend(f'- {module.name}{module.source_hint}' for module in commands[: args.limit])
print('\n'.join(output_lines))
return 0
if args.command == 'tools':
if args.query:
print(render_tool_index(limit=args.limit, query=args.query))
else:
permission_context = ToolPermissionContext.from_iterables(args.deny_tool, args.deny_prefix)
tools = get_tools(
simple_mode=args.simple_mode,
include_mcp=not args.no_mcp,
permission_context=permission_context,
)
output_lines = [f'Tool entries: {len(tools)}', '']
output_lines.extend(f'- {module.name}{module.source_hint}' for module in tools[: args.limit])
print('\n'.join(output_lines))
return 0
if args.command == 'route':
matches = PortRuntime().route_prompt(args.prompt, limit=args.limit)
if not matches:
print('No mirrored command/tool matches found.')
return 0
for match in matches:
print(f'{match.kind}\t{match.name}\t{match.score}\t{match.source_hint}')
return 0
if args.command == 'bootstrap':
print(PortRuntime().bootstrap_session(args.prompt, limit=args.limit).as_markdown())
return 0
if args.command == 'turn-loop':
results = PortRuntime().run_turn_loop(
args.prompt,
limit=args.limit,
max_turns=args.max_turns,
structured_output=args.structured_output,
)
for idx, result in enumerate(results, start=1):
print(f'## Turn {idx}')
print(result.output)
print(f'stop_reason={result.stop_reason}')
return 0
if args.command == 'flush-transcript':
engine = QueryEnginePort.from_workspace()
engine.submit_message(args.prompt)
path = engine.persist_session()
print(path)
print(f'flushed={engine.transcript_store.flushed}')
return 0
if args.command == 'load-session':
session = load_session(args.session_id)
print(f'{session.session_id}\n{len(session.messages)} messages\nin={session.input_tokens} out={session.output_tokens}')
return 0
if args.command == 'remote-mode':
print(run_remote_mode(args.target, cwd=Path(args.cwd).resolve()).as_text())
return 0
if args.command == 'ssh-mode':
print(run_ssh_mode(args.target, cwd=Path(args.cwd).resolve()).as_text())
return 0
if args.command == 'teleport-mode':
print(run_teleport_mode(args.target, cwd=Path(args.cwd).resolve()).as_text())
return 0
if args.command == 'direct-connect-mode':
print(run_direct_connect_mode(args.target, cwd=Path(args.cwd).resolve()).as_text())
return 0
if args.command == 'deep-link-mode':
print(run_deep_link_mode(args.target, cwd=Path(args.cwd).resolve()).as_text())
return 0
if args.command == 'remote-status':
runtime = RemoteRuntime.from_workspace(Path(args.cwd).resolve())
print('# Remote')
print()
print(runtime.render_summary())
return 0
if args.command == 'remote-profiles':
runtime = RemoteRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_profiles_index(query=args.query))
return 0
if args.command == 'remote-disconnect':
runtime = RemoteRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.disconnect().as_text())
return 0
if args.command == 'worktree-status':
runtime = WorktreeRuntime.from_workspace(Path(args.cwd).resolve())
print('# Worktree')
print()
print(runtime.render_summary())
return 0
if args.command == 'worktree-enter':
runtime = WorktreeRuntime.from_workspace(Path(args.cwd).resolve())
try:
print(runtime.enter(name=args.name).as_text())
except (RuntimeError, ValueError) as exc:
print(exc)
return 1
return 0
if args.command == 'worktree-exit':
runtime = WorktreeRuntime.from_workspace(Path(args.cwd).resolve())
try:
print(
runtime.exit(
action=args.action,
discard_changes=args.discard_changes,
).as_text()
)
except (RuntimeError, ValueError) as exc:
print(exc)
return 1
return 0
if args.command == 'account-status':
runtime = AccountRuntime.from_workspace(Path(args.cwd).resolve())
print('# Account')
print()
print(runtime.render_summary())
return 0
if args.command == 'account-profiles':
runtime = AccountRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_profiles_index(query=args.query))
return 0
if args.command == 'account-login':
runtime = AccountRuntime.from_workspace(Path(args.cwd).resolve())
print(
runtime.login(
args.target,
provider=args.provider,
auth_mode=args.auth_mode,
).as_text()
)
return 0
if args.command == 'account-logout':
runtime = AccountRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.logout().as_text())
return 0
if args.command == 'ask-status':
runtime = AskUserRuntime.from_workspace(Path(args.cwd).resolve())
print('# Ask User')
print()
print(runtime.render_summary())
return 0
if args.command == 'ask-history':
runtime = AskUserRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_history())
return 0
if args.command == 'search-status':
runtime = SearchRuntime.from_workspace(Path(args.cwd).resolve())
if args.provider:
print(runtime.render_provider(args.provider))
else:
print('# Search')
print()
print(runtime.render_summary())
return 0
if args.command == 'search-providers':
runtime = SearchRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_providers_index(query=args.query))
return 0
if args.command == 'search-activate':
runtime = SearchRuntime.from_workspace(Path(args.cwd).resolve())
try:
report = runtime.activate_provider(args.provider)
except KeyError:
print(f'Unknown search provider: {args.provider}')
return 1
print(report.as_text())
return 0
if args.command == 'search':
runtime = SearchRuntime.from_workspace(Path(args.cwd).resolve())
try:
output = runtime.render_search_results(
args.query,
provider_name=args.provider,
max_results=args.max_results,
domains=tuple(args.domain),
)
except (KeyError, LookupError, OSError, ValueError) as exc:
print(f'Search failed: {exc}')
return 1
print(output)
return 0
if args.command == 'mcp-status':
runtime = MCPRuntime.from_workspace(Path(args.cwd).resolve())
print('# MCP')
print()
print(runtime.render_summary())
return 0
if args.command == 'mcp-resources':
runtime = MCPRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_resource_index(query=args.query))
return 0
if args.command == 'mcp-resource':
runtime = MCPRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_resource(args.uri))
return 0
if args.command == 'mcp-tools':
runtime = MCPRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_tool_index(query=args.query, server_name=args.server))
return 0
if args.command == 'mcp-call-tool':
runtime = MCPRuntime.from_workspace(Path(args.cwd).resolve())
arguments = json.loads(args.arguments_json)
if not isinstance(arguments, dict):
print('arguments-json must decode to a JSON object')
return 1
print(
runtime.render_tool_call(
args.tool_name,
arguments=arguments,
server_name=args.server,
)
)
return 0
if args.command == 'config-status':
runtime = ConfigRuntime.from_workspace(Path(args.cwd).resolve())
print('# Config')
print()
print(runtime.render_summary())
return 0
if args.command == 'config-effective':
runtime = ConfigRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_effective_config())
return 0
if args.command == 'config-source':
runtime = ConfigRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_source(args.source))
return 0
if args.command == 'config-get':
runtime = ConfigRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_value(args.key_path, source=args.source))
return 0
if args.command == 'config-set':
runtime = ConfigRuntime.from_workspace(Path(args.cwd).resolve())
value = json.loads(args.value_json)
mutation = runtime.set_value(args.key_path, value, source=args.source)
print('# Config')
print()
print(f'source={mutation.source_name}')
print(f'key_path={mutation.key_path}')
print(f'store_path={mutation.store_path}')
print(f'effective_key_count={mutation.effective_key_count}')
print(runtime.render_value(args.key_path))
return 0
if args.command == 'lsp-status':
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
print('# LSP')
print()
print(runtime.render_summary())
return 0
if args.command == 'lsp-symbols':
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
try:
print(runtime.render_document_symbols(args.file_path))
except KeyError as exc:
print(exc)
return 1
return 0
if args.command == 'lsp-workspace-symbols':
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_workspace_symbols(args.query, max_results=args.max_results))
return 0
if args.command == 'lsp-definition':
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
try:
print(
runtime.render_definition(
args.file_path,
args.line,
args.character,
max_results=args.max_results,
)
)
except KeyError as exc:
print(exc)
return 1
return 0
if args.command == 'lsp-references':
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
try:
print(
runtime.render_references(
args.file_path,
args.line,
args.character,
max_results=args.max_results,
)
)
except KeyError as exc:
print(exc)
return 1
return 0
if args.command == 'lsp-hover':
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
try:
print(runtime.render_hover(args.file_path, args.line, args.character))
except KeyError as exc:
print(exc)
return 1
return 0
if args.command == 'lsp-diagnostics':
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
try:
print(runtime.render_diagnostics(args.file_path))
except KeyError as exc:
print(exc)
return 1
return 0
if args.command == 'lsp-call-hierarchy':
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
try:
print(
runtime.render_prepare_call_hierarchy(
args.file_path,
args.line,
args.character,
)
)
except KeyError as exc:
print(exc)
return 1
return 0
if args.command == 'lsp-incoming-calls':
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
try:
print(
runtime.render_incoming_calls(
args.file_path,
args.line,
args.character,
max_results=args.max_results,
)
)
except KeyError as exc:
print(exc)
return 1
return 0
if args.command == 'lsp-outgoing-calls':
runtime = LSPRuntime.from_workspace(Path(args.cwd).resolve())
try:
print(
runtime.render_outgoing_calls(
args.file_path,
args.line,
args.character,
max_results=args.max_results,
)
)
except KeyError as exc:
print(exc)
return 1
return 0
if args.command == 'workflow-list':
runtime = WorkflowRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_workflows_index(query=args.query))
return 0
if args.command == 'workflow-get':
runtime = WorkflowRuntime.from_workspace(Path(args.cwd).resolve())
try:
print(runtime.render_workflow(args.workflow_name))
except KeyError:
print(f'Unknown workflow: {args.workflow_name}')
return 1
return 0
if args.command == 'workflow-run':
runtime = WorkflowRuntime.from_workspace(Path(args.cwd).resolve())
arguments = json.loads(args.arguments_json)
if not isinstance(arguments, dict):
print('arguments-json must decode to a JSON object')
return 1
try:
print(runtime.render_run_report(args.workflow_name, arguments=arguments))
except KeyError:
print(f'Unknown workflow: {args.workflow_name}')
return 1
return 0
if args.command == 'trigger-list':
runtime = RemoteTriggerRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_trigger_index(query=args.query))
return 0
if args.command == 'trigger-get':
runtime = RemoteTriggerRuntime.from_workspace(Path(args.cwd).resolve())
try:
print(runtime.render_trigger(args.trigger_id))
except KeyError:
print(f'Unknown remote trigger: {args.trigger_id}')
return 1
return 0
if args.command == 'trigger-create':
runtime = RemoteTriggerRuntime.from_workspace(Path(args.cwd).resolve())
body = json.loads(args.body_json)
if not isinstance(body, dict):
print('body-json must decode to a JSON object')
return 1
try:
trigger = runtime.create_trigger(body)
except (KeyError, TypeError, ValueError) as exc:
print(exc)
return 1
print(runtime.render_trigger(trigger.trigger_id))
return 0
if args.command == 'trigger-update':
runtime = RemoteTriggerRuntime.from_workspace(Path(args.cwd).resolve())
body = json.loads(args.body_json)
if not isinstance(body, dict):
print('body-json must decode to a JSON object')
return 1
try:
trigger = runtime.update_trigger(args.trigger_id, body)
except (KeyError, TypeError, ValueError) as exc:
print(exc)
return 1
print(runtime.render_trigger(trigger.trigger_id))
return 0
if args.command == 'trigger-run':
runtime = RemoteTriggerRuntime.from_workspace(Path(args.cwd).resolve())
body = json.loads(args.body_json)
if not isinstance(body, dict):
print('body-json must decode to a JSON object')
return 1
try:
print(runtime.render_run_report(args.trigger_id, body=body))
except KeyError:
print(f'Unknown remote trigger: {args.trigger_id}')
return 1
return 0
if args.command == 'team-status':
runtime = TeamRuntime.from_workspace(Path(args.cwd).resolve())
print('# Teams')
print()
print(runtime.render_summary())
return 0
if args.command == 'team-list':
runtime = TeamRuntime.from_workspace(Path(args.cwd).resolve())
print(runtime.render_teams_index(query=args.query))
return 0
if args.command == 'team-get':
runtime = TeamRuntime.from_workspace(Path(args.cwd).resolve())
try:
print(runtime.render_team(args.team_name))
except KeyError:
print(f'Unknown team: {args.team_name}')
return 1
return 0
if args.command == 'team-create':
runtime = TeamRuntime.from_workspace(Path(args.cwd).resolve())
try:
team = runtime.create_team(
args.team_name,
description=args.description,
members=args.member,
)
except KeyError:
print(f'Team already exists: {args.team_name}')
return 1
print(f'created team {team.name}')
return 0
if args.command == 'team-delete':
runtime = TeamRuntime.from_workspace(Path(args.cwd).resolve())
try:
team = runtime.delete_team(args.team_name)
except KeyError:
print(f'Unknown team: {args.team_name}')
return 1
print(f'deleted team {team.name}')
return 0
if args.command == 'team-messages':
runtime = TeamRuntime.from_workspace(Path(args.cwd).resolve())
try:
print(runtime.render_messages(team_name=args.team_name))
except KeyError:
print(f'Unknown team: {args.team_name}')
return 1
return 0
if args.command == 'show-command':
module = get_command(args.name)
if module is None:
print(f'Command not found: {args.name}')
return 1
print('\n'.join([module.name, module.source_hint, module.responsibility]))
return 0
if args.command == 'show-tool':
module = get_tool(args.name)
if module is None:
print(f'Tool not found: {args.name}')
return 1
print('\n'.join([module.name, module.source_hint, module.responsibility]))
return 0
if args.command == 'exec-command':
result = execute_command(args.name, args.prompt)
print(result.message)
return 0 if result.handled else 1
if args.command == 'exec-tool':
result = execute_tool(args.name, args.payload)
print(result.message)
return 0 if result.handled else 1
if args.command == 'agent':
agent = _build_agent(args)
result = agent.run(args.prompt)
_print_agent_result(result, show_transcript=args.show_transcript)
return 0
if args.command == 'agent-bg':
return _launch_background_agent(args)
if args.command == 'agent-bg-worker':
return _run_background_worker(args)
if args.command == 'agent-ps':
print(BackgroundSessionRuntime().render_ps())
return 0
if args.command == 'agent-logs':
print(
BackgroundSessionRuntime().render_logs(
args.background_id,
tail=args.tail,
)
)
return 0
if args.command == 'agent-attach':
print(
BackgroundSessionRuntime().render_attach(
args.background_id,
tail=args.tail,
)
)
return 0
if args.command == 'agent-kill':
record = BackgroundSessionRuntime().kill(args.background_id)
print('# Background Session')
print(f'background_id={record.background_id}')
print(f'status={record.status}')
print(f'pid={record.pid}')
if record.exit_code is not None:
print(f'exit_code={record.exit_code}')
return 0
if args.command == 'daemon':
if args.daemon_command == 'start':
return _launch_background_agent(args)
if args.daemon_command == 'worker':
return _run_background_worker(args)
if args.daemon_command == 'ps':
print(BackgroundSessionRuntime().render_ps())
return 0
if args.daemon_command == 'logs':
print(
BackgroundSessionRuntime().render_logs(
args.background_id,
tail=args.tail,
)
)
return 0
if args.daemon_command == 'attach':
print(
BackgroundSessionRuntime().render_attach(
args.background_id,
tail=args.tail,
)
)
return 0
if args.daemon_command == 'kill':
record = BackgroundSessionRuntime().kill(args.background_id)
print('# Background Session')
print(f'background_id={record.background_id}')
print(f'status={record.status}')
print(f'pid={record.pid}')
if record.exit_code is not None:
print(f'exit_code={record.exit_code}')
return 0
if args.command == 'agent-chat':
agent = _build_agent(args)
return _run_agent_chat_loop(
agent,
initial_prompt=args.prompt,
resume_session_id=args.resume_session_id,
show_transcript=args.show_transcript,
)
if args.command == 'agent-resume':
agent, stored_session = _build_resumed_agent(args)
result = agent.resume(args.prompt, stored_session)
_print_agent_result(result, show_transcript=args.show_transcript)
return 0
if args.command == 'agent-prompt':
agent = _build_agent(args)
print(agent.render_system_prompt())
return 0
if args.command == 'agent-context':
agent = _build_agent(args)
print(agent.render_context_report())
return 0
if args.command == 'agent-context-raw':
agent = _build_agent(args)
print(agent.render_context_snapshot_report())
return 0
if args.command == 'token-budget':
agent = _build_agent(args)
agent.last_session = agent.build_session()
print(agent.render_token_budget_report())
return 0
if args.command == 'agents':
agent = _build_agent(args)
if args.agent_type:
print(agent.render_agent_detail_report(args.agent_type))
else:
print(agent.render_agents_report(show_all=bool(args.all)))
return 0
parser.error(f'unknown command: {args.command}')
return 2
if __name__ == '__main__':
raise SystemExit(main())