hexdag 0.5.0.dev1__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.
- hexdag/__init__.py +116 -0
- hexdag/__main__.py +30 -0
- hexdag/adapters/executors/__init__.py +5 -0
- hexdag/adapters/executors/local_executor.py +316 -0
- hexdag/builtin/__init__.py +6 -0
- hexdag/builtin/adapters/__init__.py +51 -0
- hexdag/builtin/adapters/anthropic/__init__.py +5 -0
- hexdag/builtin/adapters/anthropic/anthropic_adapter.py +151 -0
- hexdag/builtin/adapters/database/__init__.py +6 -0
- hexdag/builtin/adapters/database/csv/csv_adapter.py +249 -0
- hexdag/builtin/adapters/database/pgvector/__init__.py +5 -0
- hexdag/builtin/adapters/database/pgvector/pgvector_adapter.py +478 -0
- hexdag/builtin/adapters/database/sqlalchemy/sqlalchemy_adapter.py +252 -0
- hexdag/builtin/adapters/database/sqlite/__init__.py +5 -0
- hexdag/builtin/adapters/database/sqlite/sqlite_adapter.py +410 -0
- hexdag/builtin/adapters/local/README.md +59 -0
- hexdag/builtin/adapters/local/__init__.py +7 -0
- hexdag/builtin/adapters/local/local_observer_manager.py +696 -0
- hexdag/builtin/adapters/memory/__init__.py +47 -0
- hexdag/builtin/adapters/memory/file_memory_adapter.py +297 -0
- hexdag/builtin/adapters/memory/in_memory_memory.py +216 -0
- hexdag/builtin/adapters/memory/schemas.py +57 -0
- hexdag/builtin/adapters/memory/session_memory.py +178 -0
- hexdag/builtin/adapters/memory/sqlite_memory_adapter.py +215 -0
- hexdag/builtin/adapters/memory/state_memory.py +280 -0
- hexdag/builtin/adapters/mock/README.md +89 -0
- hexdag/builtin/adapters/mock/__init__.py +15 -0
- hexdag/builtin/adapters/mock/hexdag.toml +50 -0
- hexdag/builtin/adapters/mock/mock_database.py +225 -0
- hexdag/builtin/adapters/mock/mock_embedding.py +223 -0
- hexdag/builtin/adapters/mock/mock_llm.py +177 -0
- hexdag/builtin/adapters/mock/mock_tool_adapter.py +192 -0
- hexdag/builtin/adapters/mock/mock_tool_router.py +232 -0
- hexdag/builtin/adapters/openai/__init__.py +5 -0
- hexdag/builtin/adapters/openai/openai_adapter.py +634 -0
- hexdag/builtin/adapters/secret/__init__.py +7 -0
- hexdag/builtin/adapters/secret/local_secret_adapter.py +248 -0
- hexdag/builtin/adapters/unified_tool_router.py +280 -0
- hexdag/builtin/macros/__init__.py +17 -0
- hexdag/builtin/macros/conversation_agent.py +390 -0
- hexdag/builtin/macros/llm_macro.py +151 -0
- hexdag/builtin/macros/reasoning_agent.py +423 -0
- hexdag/builtin/macros/tool_macro.py +380 -0
- hexdag/builtin/nodes/__init__.py +38 -0
- hexdag/builtin/nodes/_discovery.py +123 -0
- hexdag/builtin/nodes/agent_node.py +696 -0
- hexdag/builtin/nodes/base_node_factory.py +242 -0
- hexdag/builtin/nodes/composite_node.py +926 -0
- hexdag/builtin/nodes/data_node.py +201 -0
- hexdag/builtin/nodes/expression_node.py +487 -0
- hexdag/builtin/nodes/function_node.py +454 -0
- hexdag/builtin/nodes/llm_node.py +491 -0
- hexdag/builtin/nodes/loop_node.py +920 -0
- hexdag/builtin/nodes/mapped_input.py +518 -0
- hexdag/builtin/nodes/port_call_node.py +269 -0
- hexdag/builtin/nodes/tool_call_node.py +195 -0
- hexdag/builtin/nodes/tool_utils.py +390 -0
- hexdag/builtin/prompts/__init__.py +68 -0
- hexdag/builtin/prompts/base.py +422 -0
- hexdag/builtin/prompts/chat_prompts.py +303 -0
- hexdag/builtin/prompts/error_correction_prompts.py +320 -0
- hexdag/builtin/prompts/tool_prompts.py +160 -0
- hexdag/builtin/tools/builtin_tools.py +84 -0
- hexdag/builtin/tools/database_tools.py +164 -0
- hexdag/cli/__init__.py +17 -0
- hexdag/cli/__main__.py +7 -0
- hexdag/cli/commands/__init__.py +27 -0
- hexdag/cli/commands/build_cmd.py +812 -0
- hexdag/cli/commands/create_cmd.py +208 -0
- hexdag/cli/commands/docs_cmd.py +293 -0
- hexdag/cli/commands/generate_types_cmd.py +252 -0
- hexdag/cli/commands/init_cmd.py +188 -0
- hexdag/cli/commands/pipeline_cmd.py +494 -0
- hexdag/cli/commands/plugin_dev_cmd.py +529 -0
- hexdag/cli/commands/plugins_cmd.py +441 -0
- hexdag/cli/commands/studio_cmd.py +101 -0
- hexdag/cli/commands/validate_cmd.py +221 -0
- hexdag/cli/main.py +84 -0
- hexdag/core/__init__.py +83 -0
- hexdag/core/config/__init__.py +20 -0
- hexdag/core/config/loader.py +479 -0
- hexdag/core/config/models.py +150 -0
- hexdag/core/configurable.py +294 -0
- hexdag/core/context/__init__.py +37 -0
- hexdag/core/context/execution_context.py +378 -0
- hexdag/core/docs/__init__.py +26 -0
- hexdag/core/docs/extractors.py +678 -0
- hexdag/core/docs/generators.py +890 -0
- hexdag/core/docs/models.py +120 -0
- hexdag/core/domain/__init__.py +10 -0
- hexdag/core/domain/dag.py +1225 -0
- hexdag/core/exceptions.py +234 -0
- hexdag/core/expression_parser.py +569 -0
- hexdag/core/logging.py +449 -0
- hexdag/core/models/__init__.py +17 -0
- hexdag/core/models/base.py +138 -0
- hexdag/core/orchestration/__init__.py +46 -0
- hexdag/core/orchestration/body_executor.py +481 -0
- hexdag/core/orchestration/components/__init__.py +97 -0
- hexdag/core/orchestration/components/adapter_lifecycle_manager.py +113 -0
- hexdag/core/orchestration/components/checkpoint_manager.py +134 -0
- hexdag/core/orchestration/components/execution_coordinator.py +360 -0
- hexdag/core/orchestration/components/health_check_manager.py +176 -0
- hexdag/core/orchestration/components/input_mapper.py +143 -0
- hexdag/core/orchestration/components/lifecycle_manager.py +583 -0
- hexdag/core/orchestration/components/node_executor.py +377 -0
- hexdag/core/orchestration/components/secret_manager.py +202 -0
- hexdag/core/orchestration/components/wave_executor.py +158 -0
- hexdag/core/orchestration/constants.py +17 -0
- hexdag/core/orchestration/events/README.md +312 -0
- hexdag/core/orchestration/events/__init__.py +104 -0
- hexdag/core/orchestration/events/batching.py +330 -0
- hexdag/core/orchestration/events/decorators.py +139 -0
- hexdag/core/orchestration/events/events.py +573 -0
- hexdag/core/orchestration/events/observers/__init__.py +30 -0
- hexdag/core/orchestration/events/observers/core_observers.py +690 -0
- hexdag/core/orchestration/events/observers/models.py +111 -0
- hexdag/core/orchestration/events/taxonomy.py +269 -0
- hexdag/core/orchestration/hook_context.py +237 -0
- hexdag/core/orchestration/hooks.py +437 -0
- hexdag/core/orchestration/models.py +418 -0
- hexdag/core/orchestration/orchestrator.py +910 -0
- hexdag/core/orchestration/orchestrator_factory.py +275 -0
- hexdag/core/orchestration/port_wrappers.py +327 -0
- hexdag/core/orchestration/prompt/__init__.py +32 -0
- hexdag/core/orchestration/prompt/template.py +332 -0
- hexdag/core/pipeline_builder/__init__.py +21 -0
- hexdag/core/pipeline_builder/component_instantiator.py +386 -0
- hexdag/core/pipeline_builder/include_tag.py +265 -0
- hexdag/core/pipeline_builder/pipeline_config.py +133 -0
- hexdag/core/pipeline_builder/py_tag.py +223 -0
- hexdag/core/pipeline_builder/tag_discovery.py +268 -0
- hexdag/core/pipeline_builder/yaml_builder.py +1196 -0
- hexdag/core/pipeline_builder/yaml_validator.py +569 -0
- hexdag/core/ports/__init__.py +65 -0
- hexdag/core/ports/api_call.py +133 -0
- hexdag/core/ports/database.py +489 -0
- hexdag/core/ports/embedding.py +215 -0
- hexdag/core/ports/executor.py +237 -0
- hexdag/core/ports/file_storage.py +117 -0
- hexdag/core/ports/healthcheck.py +87 -0
- hexdag/core/ports/llm.py +551 -0
- hexdag/core/ports/memory.py +70 -0
- hexdag/core/ports/observer_manager.py +130 -0
- hexdag/core/ports/secret.py +145 -0
- hexdag/core/ports/tool_router.py +94 -0
- hexdag/core/ports_builder.py +623 -0
- hexdag/core/protocols.py +273 -0
- hexdag/core/resolver.py +304 -0
- hexdag/core/schema/__init__.py +9 -0
- hexdag/core/schema/generator.py +742 -0
- hexdag/core/secrets.py +242 -0
- hexdag/core/types.py +413 -0
- hexdag/core/utils/async_warnings.py +206 -0
- hexdag/core/utils/schema_conversion.py +78 -0
- hexdag/core/utils/sql_validation.py +86 -0
- hexdag/core/validation/secure_json.py +148 -0
- hexdag/core/yaml_macro.py +517 -0
- hexdag/mcp_server.py +3120 -0
- hexdag/studio/__init__.py +10 -0
- hexdag/studio/build_ui.py +92 -0
- hexdag/studio/server/__init__.py +1 -0
- hexdag/studio/server/main.py +100 -0
- hexdag/studio/server/routes/__init__.py +9 -0
- hexdag/studio/server/routes/execute.py +208 -0
- hexdag/studio/server/routes/export.py +558 -0
- hexdag/studio/server/routes/files.py +207 -0
- hexdag/studio/server/routes/plugins.py +419 -0
- hexdag/studio/server/routes/validate.py +220 -0
- hexdag/studio/ui/index.html +13 -0
- hexdag/studio/ui/package-lock.json +2992 -0
- hexdag/studio/ui/package.json +31 -0
- hexdag/studio/ui/postcss.config.js +6 -0
- hexdag/studio/ui/public/hexdag.svg +5 -0
- hexdag/studio/ui/src/App.tsx +251 -0
- hexdag/studio/ui/src/components/Canvas.tsx +408 -0
- hexdag/studio/ui/src/components/ContextMenu.tsx +187 -0
- hexdag/studio/ui/src/components/FileBrowser.tsx +123 -0
- hexdag/studio/ui/src/components/Header.tsx +181 -0
- hexdag/studio/ui/src/components/HexdagNode.tsx +193 -0
- hexdag/studio/ui/src/components/NodeInspector.tsx +512 -0
- hexdag/studio/ui/src/components/NodePalette.tsx +262 -0
- hexdag/studio/ui/src/components/NodePortsSection.tsx +403 -0
- hexdag/studio/ui/src/components/PluginManager.tsx +347 -0
- hexdag/studio/ui/src/components/PortsEditor.tsx +481 -0
- hexdag/studio/ui/src/components/PythonEditor.tsx +195 -0
- hexdag/studio/ui/src/components/ValidationPanel.tsx +105 -0
- hexdag/studio/ui/src/components/YamlEditor.tsx +196 -0
- hexdag/studio/ui/src/components/index.ts +8 -0
- hexdag/studio/ui/src/index.css +92 -0
- hexdag/studio/ui/src/main.tsx +10 -0
- hexdag/studio/ui/src/types/index.ts +123 -0
- hexdag/studio/ui/src/vite-env.d.ts +1 -0
- hexdag/studio/ui/tailwind.config.js +29 -0
- hexdag/studio/ui/tsconfig.json +37 -0
- hexdag/studio/ui/tsconfig.node.json +13 -0
- hexdag/studio/ui/vite.config.ts +35 -0
- hexdag/visualization/__init__.py +69 -0
- hexdag/visualization/dag_visualizer.py +1020 -0
- hexdag-0.5.0.dev1.dist-info/METADATA +369 -0
- hexdag-0.5.0.dev1.dist-info/RECORD +261 -0
- hexdag-0.5.0.dev1.dist-info/WHEEL +4 -0
- hexdag-0.5.0.dev1.dist-info/entry_points.txt +4 -0
- hexdag-0.5.0.dev1.dist-info/licenses/LICENSE +190 -0
- hexdag_plugins/.gitignore +43 -0
- hexdag_plugins/README.md +73 -0
- hexdag_plugins/__init__.py +1 -0
- hexdag_plugins/azure/LICENSE +21 -0
- hexdag_plugins/azure/README.md +414 -0
- hexdag_plugins/azure/__init__.py +21 -0
- hexdag_plugins/azure/azure_blob_adapter.py +450 -0
- hexdag_plugins/azure/azure_cosmos_adapter.py +383 -0
- hexdag_plugins/azure/azure_keyvault_adapter.py +314 -0
- hexdag_plugins/azure/azure_openai_adapter.py +415 -0
- hexdag_plugins/azure/pyproject.toml +107 -0
- hexdag_plugins/azure/tests/__init__.py +1 -0
- hexdag_plugins/azure/tests/test_azure_blob_adapter.py +350 -0
- hexdag_plugins/azure/tests/test_azure_cosmos_adapter.py +323 -0
- hexdag_plugins/azure/tests/test_azure_keyvault_adapter.py +330 -0
- hexdag_plugins/azure/tests/test_azure_openai_adapter.py +329 -0
- hexdag_plugins/hexdag_etl/README.md +168 -0
- hexdag_plugins/hexdag_etl/__init__.py +53 -0
- hexdag_plugins/hexdag_etl/examples/01_simple_pandas_transform.py +270 -0
- hexdag_plugins/hexdag_etl/examples/02_simple_pandas_only.py +149 -0
- hexdag_plugins/hexdag_etl/examples/03_file_io_pipeline.py +109 -0
- hexdag_plugins/hexdag_etl/examples/test_pandas_transform.py +84 -0
- hexdag_plugins/hexdag_etl/hexdag.toml +25 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/__init__.py +48 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/__init__.py +13 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/api_extract.py +230 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/base_node_factory.py +181 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/file_io.py +415 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/outlook.py +492 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/pandas_transform.py +563 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/sql_extract_load.py +112 -0
- hexdag_plugins/hexdag_etl/pyproject.toml +82 -0
- hexdag_plugins/hexdag_etl/test_transform.py +54 -0
- hexdag_plugins/hexdag_etl/tests/test_plugin_integration.py +62 -0
- hexdag_plugins/mysql_adapter/LICENSE +21 -0
- hexdag_plugins/mysql_adapter/README.md +224 -0
- hexdag_plugins/mysql_adapter/__init__.py +6 -0
- hexdag_plugins/mysql_adapter/mysql_adapter.py +408 -0
- hexdag_plugins/mysql_adapter/pyproject.toml +93 -0
- hexdag_plugins/mysql_adapter/tests/test_mysql_adapter.py +259 -0
- hexdag_plugins/storage/README.md +184 -0
- hexdag_plugins/storage/__init__.py +19 -0
- hexdag_plugins/storage/file/__init__.py +5 -0
- hexdag_plugins/storage/file/local.py +325 -0
- hexdag_plugins/storage/ports/__init__.py +5 -0
- hexdag_plugins/storage/ports/vector_store.py +236 -0
- hexdag_plugins/storage/sql/__init__.py +7 -0
- hexdag_plugins/storage/sql/base.py +187 -0
- hexdag_plugins/storage/sql/mysql.py +27 -0
- hexdag_plugins/storage/sql/postgresql.py +27 -0
- hexdag_plugins/storage/tests/__init__.py +1 -0
- hexdag_plugins/storage/tests/test_local_file_storage.py +161 -0
- hexdag_plugins/storage/tests/test_sql_adapters.py +212 -0
- hexdag_plugins/storage/vector/__init__.py +7 -0
- hexdag_plugins/storage/vector/chromadb.py +223 -0
- hexdag_plugins/storage/vector/in_memory.py +285 -0
- hexdag_plugins/storage/vector/pgvector.py +502 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
"""Plugins management command for HexDAG CLI."""
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
from typing import Annotated, Any
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
app = typer.Typer()
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class OutputFormat(StrEnum):
|
|
16
|
+
"""Output format options."""
|
|
17
|
+
|
|
18
|
+
TABLE = "table"
|
|
19
|
+
JSON = "json"
|
|
20
|
+
YAML = "yaml"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.command("list")
|
|
24
|
+
def list_plugins(
|
|
25
|
+
format: Annotated[
|
|
26
|
+
OutputFormat | None,
|
|
27
|
+
typer.Option(
|
|
28
|
+
"--format",
|
|
29
|
+
"-f",
|
|
30
|
+
help="Output format",
|
|
31
|
+
),
|
|
32
|
+
] = None,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""List all available plugins and adapters."""
|
|
35
|
+
if format is None:
|
|
36
|
+
format = OutputFormat.TABLE
|
|
37
|
+
|
|
38
|
+
# Check available extras
|
|
39
|
+
available_plugins = _get_available_plugins()
|
|
40
|
+
|
|
41
|
+
if format == OutputFormat.JSON:
|
|
42
|
+
console.print_json(data=available_plugins)
|
|
43
|
+
elif format == OutputFormat.YAML:
|
|
44
|
+
import yaml
|
|
45
|
+
|
|
46
|
+
console.print(yaml.dump(available_plugins, default_flow_style=False))
|
|
47
|
+
else:
|
|
48
|
+
# Table format
|
|
49
|
+
table = Table(title="Available Plugins", show_header=True, header_style="bold magenta")
|
|
50
|
+
table.add_column("Plugin", style="cyan", no_wrap=True)
|
|
51
|
+
table.add_column("Namespace", style="green")
|
|
52
|
+
table.add_column("Status", style="yellow")
|
|
53
|
+
table.add_column("Capabilities", style="white")
|
|
54
|
+
|
|
55
|
+
for plugin in available_plugins:
|
|
56
|
+
status = "✓ Installed" if plugin["installed"] else "✗ Not installed"
|
|
57
|
+
table.add_row(
|
|
58
|
+
str(plugin["name"]),
|
|
59
|
+
str(plugin["namespace"]),
|
|
60
|
+
status,
|
|
61
|
+
", ".join(str(c) for c in plugin["capabilities"]),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
console.print(table)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@app.command("check")
|
|
68
|
+
def check_plugins() -> None:
|
|
69
|
+
"""Check plugin dependencies and suggest installation commands."""
|
|
70
|
+
console.print("[bold]Checking plugin dependencies...[/bold]\n")
|
|
71
|
+
|
|
72
|
+
checks = _check_dependencies()
|
|
73
|
+
has_missing = False
|
|
74
|
+
|
|
75
|
+
for check in checks:
|
|
76
|
+
if check["status"] == "ok":
|
|
77
|
+
console.print(f"✓ [green]{check['name']}[/green] - OK")
|
|
78
|
+
elif check["status"] == "missing":
|
|
79
|
+
has_missing = True
|
|
80
|
+
console.print(f"✗ [red]{check['name']}[/red] - Missing")
|
|
81
|
+
if check.get("install_hint"):
|
|
82
|
+
console.print(f" → Install with: [yellow]{check['install_hint']}[/yellow]")
|
|
83
|
+
elif check["status"] == "optional":
|
|
84
|
+
console.print(f"○ [yellow]{check['name']}[/yellow] - Optional")
|
|
85
|
+
if check.get("install_hint"):
|
|
86
|
+
console.print(f" → Install with: [dim]{check['install_hint']}[/dim]")
|
|
87
|
+
|
|
88
|
+
if not has_missing:
|
|
89
|
+
console.print("\n[green]All required dependencies are installed![/green]")
|
|
90
|
+
else:
|
|
91
|
+
console.print(
|
|
92
|
+
"\n[yellow]Some dependencies are missing. See installation hints above.[/yellow]"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@app.command("install")
|
|
97
|
+
def install_plugin(
|
|
98
|
+
plugin_name: str = typer.Argument(
|
|
99
|
+
...,
|
|
100
|
+
help="Plugin name or extra to install (e.g., 'openai', 'viz')",
|
|
101
|
+
),
|
|
102
|
+
use_uv: bool = typer.Option(
|
|
103
|
+
True,
|
|
104
|
+
"--uv/--pip",
|
|
105
|
+
help="Prefer uv when available; use --pip to force pip",
|
|
106
|
+
),
|
|
107
|
+
dry_run: bool = typer.Option(
|
|
108
|
+
False,
|
|
109
|
+
"--dry-run",
|
|
110
|
+
help="Show what would be installed without actually installing",
|
|
111
|
+
),
|
|
112
|
+
editable: bool = typer.Option(
|
|
113
|
+
False,
|
|
114
|
+
"--editable",
|
|
115
|
+
"-e",
|
|
116
|
+
help="Install in editable/development mode",
|
|
117
|
+
),
|
|
118
|
+
) -> None:
|
|
119
|
+
"""Install a plugin or adapter (wrapper around package manager).
|
|
120
|
+
|
|
121
|
+
Raises
|
|
122
|
+
------
|
|
123
|
+
typer.Exit
|
|
124
|
+
If installation fails or plugin not found
|
|
125
|
+
"""
|
|
126
|
+
# Map plugin names to extras
|
|
127
|
+
plugin_map = {
|
|
128
|
+
"openai": "adapters-openai",
|
|
129
|
+
"anthropic": "adapters-anthropic",
|
|
130
|
+
"viz": "viz",
|
|
131
|
+
"visualization": "viz",
|
|
132
|
+
"cli": "cli",
|
|
133
|
+
"all": "all",
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
extra = plugin_map.get(plugin_name, plugin_name)
|
|
137
|
+
|
|
138
|
+
# Detect package manager
|
|
139
|
+
import shutil
|
|
140
|
+
import subprocess # nosec B404 - Required for package installation
|
|
141
|
+
from pathlib import Path
|
|
142
|
+
|
|
143
|
+
# Determine which package manager to use
|
|
144
|
+
has_uv = shutil.which("uv") is not None
|
|
145
|
+
use_uv_final = use_uv and has_uv
|
|
146
|
+
|
|
147
|
+
if use_uv and not has_uv:
|
|
148
|
+
console.print("[yellow]Warning: uv requested but not found. Using pip instead.[/yellow]")
|
|
149
|
+
use_uv_final = False
|
|
150
|
+
|
|
151
|
+
if use_uv_final:
|
|
152
|
+
if editable:
|
|
153
|
+
# For editable install with uv, need to install from current directory
|
|
154
|
+
if Path("pyproject.toml").exists():
|
|
155
|
+
cmd_list = ["uv", "pip", "install", "-e", f".[{extra}]"]
|
|
156
|
+
cmd_str = f"uv pip install -e .[{extra}]"
|
|
157
|
+
else:
|
|
158
|
+
console.print(
|
|
159
|
+
"[red]Error: Editable install requires pyproject.toml "
|
|
160
|
+
"in current directory[/red]"
|
|
161
|
+
)
|
|
162
|
+
raise typer.Exit(1)
|
|
163
|
+
else:
|
|
164
|
+
cmd_list = ["uv", "pip", "install", f"hexdag[{extra}]"]
|
|
165
|
+
cmd_str = f"uv pip install hexdag[{extra}]"
|
|
166
|
+
else:
|
|
167
|
+
if editable:
|
|
168
|
+
if Path("pyproject.toml").exists():
|
|
169
|
+
cmd_list = ["pip", "install", "-e", f".[{extra}]"]
|
|
170
|
+
cmd_str = f"pip install -e .[{extra}]"
|
|
171
|
+
else:
|
|
172
|
+
console.print(
|
|
173
|
+
"[red]Error: Editable install requires pyproject.toml "
|
|
174
|
+
"in current directory[/red]"
|
|
175
|
+
)
|
|
176
|
+
raise typer.Exit(1)
|
|
177
|
+
else:
|
|
178
|
+
cmd_list = ["pip", "install", f"hexdag[{extra}]"]
|
|
179
|
+
cmd_str = f"pip install hexdag[{extra}]"
|
|
180
|
+
|
|
181
|
+
if dry_run:
|
|
182
|
+
# Use markup=False to avoid bracket interpretation
|
|
183
|
+
console.print("[yellow]Would run:[/yellow] ", end="")
|
|
184
|
+
console.print(cmd_str, markup=False)
|
|
185
|
+
if use_uv_final:
|
|
186
|
+
console.print("[dim]Using: uv package manager[/dim]")
|
|
187
|
+
else:
|
|
188
|
+
console.print("[dim]Using: pip package manager[/dim]")
|
|
189
|
+
else:
|
|
190
|
+
console.print(f"[cyan]Installing {plugin_name}...[/cyan]")
|
|
191
|
+
# Use markup=False for the command
|
|
192
|
+
console.print("Running: [bold]", end="")
|
|
193
|
+
console.print(cmd_str, markup=False, style="bold")
|
|
194
|
+
console.print("") # Empty line
|
|
195
|
+
|
|
196
|
+
# Run the installation - using list format without shell=True for security
|
|
197
|
+
result = subprocess.run(cmd_list, capture_output=True, text=True) # nosec B603
|
|
198
|
+
|
|
199
|
+
if result.returncode == 0:
|
|
200
|
+
console.print(f"[green]✓ Successfully installed {plugin_name}[/green]")
|
|
201
|
+
|
|
202
|
+
# Show what was installed
|
|
203
|
+
if "Successfully installed" in result.stdout:
|
|
204
|
+
console.print("\n[dim]Installed packages:[/dim]")
|
|
205
|
+
for line in result.stdout.split("\n"):
|
|
206
|
+
if "Successfully installed" in line:
|
|
207
|
+
packages = line.split("Successfully installed")[1].strip()
|
|
208
|
+
for pkg in packages.split():
|
|
209
|
+
console.print(f" • {pkg}")
|
|
210
|
+
else:
|
|
211
|
+
console.print(f"[red]✗ Failed to install {plugin_name}[/red]")
|
|
212
|
+
if result.stderr:
|
|
213
|
+
console.print(f"[dim]{result.stderr}[/dim]")
|
|
214
|
+
if result.stdout and "error" in result.stdout.lower():
|
|
215
|
+
console.print(f"[dim]{result.stdout}[/dim]")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _get_available_plugins() -> list[dict[str, Any]]:
|
|
219
|
+
"""Get list of available plugins by scanning known module paths."""
|
|
220
|
+
import importlib
|
|
221
|
+
|
|
222
|
+
plugins = []
|
|
223
|
+
|
|
224
|
+
# Built-in adapters (always available)
|
|
225
|
+
builtin_adapters = {
|
|
226
|
+
"mock": {
|
|
227
|
+
"module": "hexdag.builtin.adapters.mock",
|
|
228
|
+
"capabilities": ["LLM", "Database", "ToolRouter"],
|
|
229
|
+
"namespace": "core",
|
|
230
|
+
},
|
|
231
|
+
"memory": {
|
|
232
|
+
"module": "hexdag.builtin.adapters.memory",
|
|
233
|
+
"capabilities": ["Memory"],
|
|
234
|
+
"namespace": "core",
|
|
235
|
+
},
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
for name, info in builtin_adapters.items():
|
|
239
|
+
installed = False
|
|
240
|
+
with contextlib.suppress(ImportError):
|
|
241
|
+
importlib.import_module(info["module"])
|
|
242
|
+
installed = True
|
|
243
|
+
|
|
244
|
+
plugins.append({
|
|
245
|
+
"name": name,
|
|
246
|
+
"namespace": info["namespace"],
|
|
247
|
+
"installed": installed,
|
|
248
|
+
"capabilities": info["capabilities"],
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
# Optional adapters (extras)
|
|
252
|
+
optional_adapters = {
|
|
253
|
+
"openai": {
|
|
254
|
+
"module": "hexdag.adapters.openai",
|
|
255
|
+
"capabilities": ["LLM", "Embeddings"],
|
|
256
|
+
"namespace": "plugin",
|
|
257
|
+
},
|
|
258
|
+
"anthropic": {
|
|
259
|
+
"module": "hexdag.adapters.anthropic",
|
|
260
|
+
"capabilities": ["LLM"],
|
|
261
|
+
"namespace": "plugin",
|
|
262
|
+
},
|
|
263
|
+
"visualization": {
|
|
264
|
+
"module": "hexdag.visualization",
|
|
265
|
+
"capabilities": ["DAG Visualization"],
|
|
266
|
+
"namespace": "core",
|
|
267
|
+
},
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for name, info in optional_adapters.items():
|
|
271
|
+
installed = False
|
|
272
|
+
with contextlib.suppress(ImportError):
|
|
273
|
+
importlib.import_module(info["module"])
|
|
274
|
+
installed = True
|
|
275
|
+
|
|
276
|
+
plugins.append({
|
|
277
|
+
"name": name,
|
|
278
|
+
"namespace": info["namespace"],
|
|
279
|
+
"installed": installed,
|
|
280
|
+
"capabilities": info["capabilities"],
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
# Check for plugins in hexdag_plugins directory
|
|
284
|
+
from pathlib import Path
|
|
285
|
+
|
|
286
|
+
# Try to find hexdag_plugins
|
|
287
|
+
current = Path.cwd()
|
|
288
|
+
plugin_dir = None
|
|
289
|
+
while current != current.parent:
|
|
290
|
+
if (current / "hexdag_plugins").exists():
|
|
291
|
+
plugin_dir = current / "hexdag_plugins"
|
|
292
|
+
break
|
|
293
|
+
current = current.parent
|
|
294
|
+
|
|
295
|
+
if plugin_dir:
|
|
296
|
+
for plugin_path in plugin_dir.iterdir():
|
|
297
|
+
if plugin_path.is_dir() and not plugin_path.name.startswith((".", "_")):
|
|
298
|
+
plugin_name = plugin_path.name
|
|
299
|
+
# Skip if already in list
|
|
300
|
+
if plugin_name in [p["name"] for p in plugins]:
|
|
301
|
+
continue
|
|
302
|
+
|
|
303
|
+
# Try to determine capabilities from pyproject.toml
|
|
304
|
+
capabilities = ["Adapter"]
|
|
305
|
+
pyproject = plugin_path / "pyproject.toml"
|
|
306
|
+
if pyproject.exists():
|
|
307
|
+
import tomllib
|
|
308
|
+
|
|
309
|
+
with pyproject.open("rb") as f:
|
|
310
|
+
data = tomllib.load(f)
|
|
311
|
+
# Check for port hints in keywords
|
|
312
|
+
keywords = data.get("project", {}).get("keywords", [])
|
|
313
|
+
for kw in keywords:
|
|
314
|
+
if kw in ("llm", "database", "memory", "api"):
|
|
315
|
+
capabilities = [kw.upper() if kw != "llm" else "LLM"]
|
|
316
|
+
|
|
317
|
+
# Check if installable
|
|
318
|
+
installed = False
|
|
319
|
+
try:
|
|
320
|
+
importlib.import_module(f"hexdag_plugins.{plugin_name}")
|
|
321
|
+
installed = True
|
|
322
|
+
except ImportError:
|
|
323
|
+
pass
|
|
324
|
+
|
|
325
|
+
plugins.append({
|
|
326
|
+
"name": plugin_name,
|
|
327
|
+
"namespace": "plugin",
|
|
328
|
+
"installed": installed,
|
|
329
|
+
"capabilities": capabilities,
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
# Special check for visualization graphviz support
|
|
333
|
+
try:
|
|
334
|
+
from hexdag.visualization import GRAPHVIZ_AVAILABLE
|
|
335
|
+
|
|
336
|
+
for plugin in plugins:
|
|
337
|
+
if plugin["name"] == "visualization":
|
|
338
|
+
plugin["installed"] = GRAPHVIZ_AVAILABLE
|
|
339
|
+
break
|
|
340
|
+
except ImportError:
|
|
341
|
+
pass
|
|
342
|
+
|
|
343
|
+
return plugins
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _check_dependencies() -> list[dict]:
|
|
347
|
+
"""Check plugin dependencies dynamically."""
|
|
348
|
+
import importlib
|
|
349
|
+
import shutil
|
|
350
|
+
|
|
351
|
+
# Detect if uv is available for better hints
|
|
352
|
+
has_uv = shutil.which("uv") is not None
|
|
353
|
+
prefix = "uv pip install" if has_uv else "pip install"
|
|
354
|
+
|
|
355
|
+
checks = []
|
|
356
|
+
|
|
357
|
+
# Define dependency checks
|
|
358
|
+
dependency_checks = {
|
|
359
|
+
"pydantic": {
|
|
360
|
+
"name": "pydantic (core)",
|
|
361
|
+
"required": True,
|
|
362
|
+
"extra": None,
|
|
363
|
+
},
|
|
364
|
+
"yaml": {
|
|
365
|
+
"name": "PyYAML (CLI)",
|
|
366
|
+
"required": False,
|
|
367
|
+
"extra": "cli",
|
|
368
|
+
},
|
|
369
|
+
"graphviz": {
|
|
370
|
+
"name": "graphviz (visualization)",
|
|
371
|
+
"required": False,
|
|
372
|
+
"extra": "viz",
|
|
373
|
+
},
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
# Check each dependency
|
|
377
|
+
for module_name, info in dependency_checks.items():
|
|
378
|
+
try:
|
|
379
|
+
importlib.import_module(module_name)
|
|
380
|
+
checks.append({"name": info["name"], "status": "ok"})
|
|
381
|
+
except ImportError:
|
|
382
|
+
status = "missing" if info["required"] else "optional"
|
|
383
|
+
hint = f"{prefix} hexdag"
|
|
384
|
+
if info["extra"]:
|
|
385
|
+
hint = f"{prefix} hexdag[{info['extra']}]"
|
|
386
|
+
|
|
387
|
+
checks.append({
|
|
388
|
+
"name": info["name"],
|
|
389
|
+
"status": status,
|
|
390
|
+
"install_hint": hint,
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
# Check optional adapter packages dynamically
|
|
394
|
+
adapter_packages: dict[str, Any] = {
|
|
395
|
+
"openai": {
|
|
396
|
+
"display": "OpenAI",
|
|
397
|
+
"adapter_module": "hexdag.adapters.openai",
|
|
398
|
+
"extra": "adapters-openai",
|
|
399
|
+
},
|
|
400
|
+
"anthropic": {
|
|
401
|
+
"display": "Anthropic",
|
|
402
|
+
"adapter_module": "hexdag.adapters.anthropic",
|
|
403
|
+
"extra": "adapters-anthropic",
|
|
404
|
+
},
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
for sdk_name, info in adapter_packages.items():
|
|
408
|
+
sdk_ok = False
|
|
409
|
+
adapter_ok = False
|
|
410
|
+
|
|
411
|
+
# Check SDK
|
|
412
|
+
try:
|
|
413
|
+
importlib.import_module(sdk_name)
|
|
414
|
+
sdk_ok = True
|
|
415
|
+
except ImportError:
|
|
416
|
+
pass
|
|
417
|
+
|
|
418
|
+
# Check adapter if SDK exists
|
|
419
|
+
if sdk_ok:
|
|
420
|
+
try:
|
|
421
|
+
importlib.import_module(info["adapter_module"])
|
|
422
|
+
adapter_ok = True
|
|
423
|
+
except ImportError:
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
if sdk_ok and adapter_ok:
|
|
427
|
+
checks.append({"name": f"{info['display']} (SDK + Adapter)", "status": "ok"})
|
|
428
|
+
elif sdk_ok:
|
|
429
|
+
checks.append({
|
|
430
|
+
"name": f"{info['display']} (SDK only, adapter missing)",
|
|
431
|
+
"status": "optional",
|
|
432
|
+
"install_hint": f"{prefix} hexdag[{info['extra']}]",
|
|
433
|
+
})
|
|
434
|
+
else:
|
|
435
|
+
checks.append({
|
|
436
|
+
"name": f"{info['display']} (not installed)",
|
|
437
|
+
"status": "optional",
|
|
438
|
+
"install_hint": f"{prefix} hexdag[{info['extra']}]",
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
return checks
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""hexdag studio - Local-first visual editor for pipelines.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
hexdag studio ./pipelines/
|
|
5
|
+
hexdag studio --port 8080
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(name="studio", help="Local-first visual editor for pipelines")
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.callback(invoke_without_command=True)
|
|
18
|
+
def studio(
|
|
19
|
+
ctx: typer.Context,
|
|
20
|
+
path: Path = typer.Argument(
|
|
21
|
+
Path(),
|
|
22
|
+
help="Directory containing pipeline YAML files",
|
|
23
|
+
exists=True,
|
|
24
|
+
file_okay=False,
|
|
25
|
+
dir_okay=True,
|
|
26
|
+
resolve_path=True,
|
|
27
|
+
),
|
|
28
|
+
host: str = typer.Option(
|
|
29
|
+
"127.0.0.1",
|
|
30
|
+
"--host",
|
|
31
|
+
"-h",
|
|
32
|
+
help="Host to bind to",
|
|
33
|
+
),
|
|
34
|
+
port: int = typer.Option(
|
|
35
|
+
3141,
|
|
36
|
+
"--port",
|
|
37
|
+
"-p",
|
|
38
|
+
help="Port to bind to",
|
|
39
|
+
),
|
|
40
|
+
no_browser: bool = typer.Option(
|
|
41
|
+
False,
|
|
42
|
+
"--no-browser",
|
|
43
|
+
help="Don't open browser automatically",
|
|
44
|
+
),
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Start the hexdag studio visual editor.
|
|
47
|
+
|
|
48
|
+
Opens a browser-based editor that reads/writes YAML files directly.
|
|
49
|
+
No cloud, no accounts - just local files that work with git.
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
hexdag studio ./pipelines/
|
|
53
|
+
hexdag studio --port 8080
|
|
54
|
+
hexdag studio . --no-browser
|
|
55
|
+
"""
|
|
56
|
+
# Check for studio dependencies
|
|
57
|
+
try:
|
|
58
|
+
import fastapi # noqa: F401
|
|
59
|
+
import uvicorn # noqa: F401
|
|
60
|
+
except ImportError:
|
|
61
|
+
console.print(
|
|
62
|
+
"[red]Error:[/red] Studio dependencies not installed.\n"
|
|
63
|
+
"Please install with:\n"
|
|
64
|
+
" [cyan]pip install hexdag[studio][/cyan]\n"
|
|
65
|
+
" or\n"
|
|
66
|
+
" [cyan]uv pip install hexdag[studio][/cyan]"
|
|
67
|
+
)
|
|
68
|
+
raise typer.Exit(code=1)
|
|
69
|
+
|
|
70
|
+
# Resolve path
|
|
71
|
+
workspace = path.resolve()
|
|
72
|
+
|
|
73
|
+
# Print startup banner
|
|
74
|
+
console.print()
|
|
75
|
+
console.print("[bold blue]hexdag studio[/bold blue] v0.1.0")
|
|
76
|
+
console.print()
|
|
77
|
+
console.print(f" [dim]Workspace:[/dim] {workspace}")
|
|
78
|
+
console.print(
|
|
79
|
+
f" [dim]Local:[/dim] [link=http://{host}:{port}]http://{host}:{port}[/link]"
|
|
80
|
+
)
|
|
81
|
+
console.print()
|
|
82
|
+
console.print(" [dim]Press Ctrl+C to stop[/dim]")
|
|
83
|
+
console.print()
|
|
84
|
+
|
|
85
|
+
# Open browser
|
|
86
|
+
if not no_browser:
|
|
87
|
+
import threading
|
|
88
|
+
import webbrowser
|
|
89
|
+
|
|
90
|
+
def open_browser() -> None:
|
|
91
|
+
import time
|
|
92
|
+
|
|
93
|
+
time.sleep(1) # Wait for server to start
|
|
94
|
+
webbrowser.open(f"http://{host}:{port}")
|
|
95
|
+
|
|
96
|
+
threading.Thread(target=open_browser, daemon=True).start()
|
|
97
|
+
|
|
98
|
+
# Start server
|
|
99
|
+
from hexdag.studio.server.main import run_server
|
|
100
|
+
|
|
101
|
+
run_server(workspace, host=host, port=port)
|