deepwork 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deepwork/__init__.py +25 -0
- deepwork/cli/__init__.py +1 -0
- deepwork/cli/install.py +290 -0
- deepwork/cli/main.py +25 -0
- deepwork/cli/sync.py +176 -0
- deepwork/core/__init__.py +1 -0
- deepwork/core/adapters.py +373 -0
- deepwork/core/detector.py +93 -0
- deepwork/core/generator.py +290 -0
- deepwork/core/hooks_syncer.py +206 -0
- deepwork/core/parser.py +310 -0
- deepwork/core/policy_parser.py +285 -0
- deepwork/hooks/__init__.py +1 -0
- deepwork/hooks/evaluate_policies.py +159 -0
- deepwork/schemas/__init__.py +1 -0
- deepwork/schemas/job_schema.py +212 -0
- deepwork/schemas/policy_schema.py +68 -0
- deepwork/standard_jobs/deepwork_jobs/job.yml +102 -0
- deepwork/standard_jobs/deepwork_jobs/steps/define.md +359 -0
- deepwork/standard_jobs/deepwork_jobs/steps/implement.md +435 -0
- deepwork/standard_jobs/deepwork_jobs/steps/refine.md +447 -0
- deepwork/standard_jobs/deepwork_policy/hooks/capture_work_tree.sh +26 -0
- deepwork/standard_jobs/deepwork_policy/hooks/get_changed_files.sh +30 -0
- deepwork/standard_jobs/deepwork_policy/hooks/global_hooks.yml +8 -0
- deepwork/standard_jobs/deepwork_policy/hooks/policy_stop_hook.sh +72 -0
- deepwork/standard_jobs/deepwork_policy/hooks/user_prompt_submit.sh +17 -0
- deepwork/standard_jobs/deepwork_policy/job.yml +35 -0
- deepwork/standard_jobs/deepwork_policy/steps/define.md +174 -0
- deepwork/templates/__init__.py +1 -0
- deepwork/templates/claude/command-job-step.md.jinja +210 -0
- deepwork/templates/gemini/command-job-step.toml.jinja +169 -0
- deepwork/utils/__init__.py +1 -0
- deepwork/utils/fs.py +128 -0
- deepwork/utils/git.py +164 -0
- deepwork/utils/validation.py +31 -0
- deepwork/utils/yaml_utils.py +89 -0
- deepwork-0.1.0.dist-info/METADATA +389 -0
- deepwork-0.1.0.dist-info/RECORD +41 -0
- deepwork-0.1.0.dist-info/WHEEL +4 -0
- deepwork-0.1.0.dist-info/entry_points.txt +2 -0
- deepwork-0.1.0.dist-info/licenses/LICENSE.md +60 -0
deepwork/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""DeepWork - Framework for enabling AI agents to perform complex, multi-step work tasks."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
__author__ = "DeepWork Contributors"
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"__version__",
|
|
8
|
+
"__author__",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Lazy imports to avoid circular dependencies and missing modules during development
|
|
13
|
+
def __getattr__(name: str) -> object:
|
|
14
|
+
"""Lazy import for core modules."""
|
|
15
|
+
if name in ("JobDefinition", "ParseError", "Step", "StepInput", "parse_job_definition"):
|
|
16
|
+
from deepwork.core.parser import (
|
|
17
|
+
JobDefinition,
|
|
18
|
+
ParseError,
|
|
19
|
+
Step,
|
|
20
|
+
StepInput,
|
|
21
|
+
parse_job_definition,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
return locals()[name]
|
|
25
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
deepwork/cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI commands for DeepWork."""
|
deepwork/cli/install.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"""Install command for DeepWork CLI."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from deepwork.core.adapters import AgentAdapter
|
|
10
|
+
from deepwork.core.detector import PlatformDetector
|
|
11
|
+
from deepwork.utils.fs import ensure_dir
|
|
12
|
+
from deepwork.utils.git import is_git_repo
|
|
13
|
+
from deepwork.utils.yaml_utils import load_yaml, save_yaml
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InstallError(Exception):
|
|
19
|
+
"""Exception raised for installation errors."""
|
|
20
|
+
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _inject_standard_job(job_name: str, jobs_dir: Path, project_path: Path) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Inject a standard job definition into the project.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
job_name: Name of the standard job to inject
|
|
30
|
+
jobs_dir: Path to .deepwork/jobs directory
|
|
31
|
+
project_path: Path to project root (for relative path display)
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
InstallError: If injection fails
|
|
35
|
+
"""
|
|
36
|
+
# Find the standard jobs directory
|
|
37
|
+
standard_jobs_dir = Path(__file__).parent.parent / "standard_jobs" / job_name
|
|
38
|
+
|
|
39
|
+
if not standard_jobs_dir.exists():
|
|
40
|
+
raise InstallError(
|
|
41
|
+
f"Standard job '{job_name}' not found at {standard_jobs_dir}. "
|
|
42
|
+
"DeepWork installation may be corrupted."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Target directory
|
|
46
|
+
target_dir = jobs_dir / job_name
|
|
47
|
+
|
|
48
|
+
# Copy the entire directory
|
|
49
|
+
try:
|
|
50
|
+
if target_dir.exists():
|
|
51
|
+
# Remove existing if present (for reinstall/upgrade)
|
|
52
|
+
shutil.rmtree(target_dir)
|
|
53
|
+
|
|
54
|
+
shutil.copytree(standard_jobs_dir, target_dir)
|
|
55
|
+
console.print(
|
|
56
|
+
f" [green]✓[/green] Installed {job_name} ({target_dir.relative_to(project_path)})"
|
|
57
|
+
)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
raise InstallError(f"Failed to install {job_name}: {e}") from e
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _inject_deepwork_jobs(jobs_dir: Path, project_path: Path) -> None:
|
|
63
|
+
"""
|
|
64
|
+
Inject the deepwork_jobs job definition into the project.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
jobs_dir: Path to .deepwork/jobs directory
|
|
68
|
+
project_path: Path to project root (for relative path display)
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
InstallError: If injection fails
|
|
72
|
+
"""
|
|
73
|
+
_inject_standard_job("deepwork_jobs", jobs_dir, project_path)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _inject_deepwork_policy(jobs_dir: Path, project_path: Path) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Inject the deepwork_policy job definition into the project.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
jobs_dir: Path to .deepwork/jobs directory
|
|
82
|
+
project_path: Path to project root (for relative path display)
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
InstallError: If injection fails
|
|
86
|
+
"""
|
|
87
|
+
_inject_standard_job("deepwork_policy", jobs_dir, project_path)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _create_deepwork_gitignore(deepwork_dir: Path) -> None:
|
|
91
|
+
"""
|
|
92
|
+
Create .gitignore file in .deepwork/ directory.
|
|
93
|
+
|
|
94
|
+
This ensures that temporary files like .last_work_tree are not committed.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
deepwork_dir: Path to .deepwork directory
|
|
98
|
+
"""
|
|
99
|
+
gitignore_path = deepwork_dir / ".gitignore"
|
|
100
|
+
gitignore_content = """# DeepWork temporary files
|
|
101
|
+
# These files are used for policy evaluation during sessions
|
|
102
|
+
.last_work_tree
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
# Only write if it doesn't exist or doesn't contain the entry
|
|
106
|
+
if gitignore_path.exists():
|
|
107
|
+
existing_content = gitignore_path.read_text()
|
|
108
|
+
if ".last_work_tree" not in existing_content:
|
|
109
|
+
# Append to existing
|
|
110
|
+
with open(gitignore_path, "a") as f:
|
|
111
|
+
f.write("\n" + gitignore_content)
|
|
112
|
+
else:
|
|
113
|
+
gitignore_path.write_text(gitignore_content)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class DynamicChoice(click.Choice):
|
|
117
|
+
"""A Click Choice that gets its values dynamically from AgentAdapter."""
|
|
118
|
+
|
|
119
|
+
def __init__(self) -> None:
|
|
120
|
+
# Get choices at runtime from registered adapters
|
|
121
|
+
super().__init__(AgentAdapter.list_names(), case_sensitive=False)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@click.command()
|
|
125
|
+
@click.option(
|
|
126
|
+
"--platform",
|
|
127
|
+
"-p",
|
|
128
|
+
type=DynamicChoice(),
|
|
129
|
+
required=False,
|
|
130
|
+
help="AI platform to install for. If not specified, will auto-detect.",
|
|
131
|
+
)
|
|
132
|
+
@click.option(
|
|
133
|
+
"--path",
|
|
134
|
+
type=click.Path(exists=True, file_okay=False, path_type=Path),
|
|
135
|
+
default=".",
|
|
136
|
+
help="Path to project directory (default: current directory)",
|
|
137
|
+
)
|
|
138
|
+
def install(platform: str | None, path: Path) -> None:
|
|
139
|
+
"""
|
|
140
|
+
Install DeepWork in a project.
|
|
141
|
+
|
|
142
|
+
Adds the specified AI platform to the project configuration and syncs
|
|
143
|
+
commands for all configured platforms.
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
_install_deepwork(platform, path)
|
|
147
|
+
except InstallError as e:
|
|
148
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
149
|
+
raise click.Abort() from e
|
|
150
|
+
except Exception as e:
|
|
151
|
+
console.print(f"[red]Unexpected error:[/red] {e}")
|
|
152
|
+
raise
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _install_deepwork(platform_name: str | None, project_path: Path) -> None:
|
|
156
|
+
"""
|
|
157
|
+
Install DeepWork in a project.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
platform_name: Platform to install for (or None to auto-detect)
|
|
161
|
+
project_path: Path to project directory
|
|
162
|
+
|
|
163
|
+
Raises:
|
|
164
|
+
InstallError: If installation fails
|
|
165
|
+
"""
|
|
166
|
+
console.print("\n[bold cyan]DeepWork Installation[/bold cyan]\n")
|
|
167
|
+
|
|
168
|
+
# Step 1: Check Git repository
|
|
169
|
+
console.print("[yellow]→[/yellow] Checking Git repository...")
|
|
170
|
+
if not is_git_repo(project_path):
|
|
171
|
+
raise InstallError(
|
|
172
|
+
"Not a Git repository. DeepWork requires a Git repository.\n"
|
|
173
|
+
"Run 'git init' to initialize a repository."
|
|
174
|
+
)
|
|
175
|
+
console.print(" [green]✓[/green] Git repository found")
|
|
176
|
+
|
|
177
|
+
# Step 2: Detect or validate platform
|
|
178
|
+
detector = PlatformDetector(project_path)
|
|
179
|
+
|
|
180
|
+
if platform_name:
|
|
181
|
+
# User specified platform - check if it's available
|
|
182
|
+
console.print(f"[yellow]→[/yellow] Checking for {platform_name.title()}...")
|
|
183
|
+
adapter = detector.detect_platform(platform_name.lower())
|
|
184
|
+
|
|
185
|
+
if adapter is None:
|
|
186
|
+
# Platform not detected - provide helpful message
|
|
187
|
+
adapter = detector.get_adapter(platform_name.lower())
|
|
188
|
+
raise InstallError(
|
|
189
|
+
f"{adapter.display_name} not detected in this project.\n"
|
|
190
|
+
f"Expected to find '{adapter.config_dir}/' directory.\n"
|
|
191
|
+
f"Please ensure {adapter.display_name} is set up in this project."
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
console.print(f" [green]✓[/green] {adapter.display_name} detected")
|
|
195
|
+
platform_to_add = adapter.name
|
|
196
|
+
else:
|
|
197
|
+
# Auto-detect platform
|
|
198
|
+
console.print("[yellow]→[/yellow] Auto-detecting AI platform...")
|
|
199
|
+
available_adapters = detector.detect_all_platforms()
|
|
200
|
+
|
|
201
|
+
if not available_adapters:
|
|
202
|
+
supported = ", ".join(
|
|
203
|
+
f"{AgentAdapter.get(name).display_name} ({AgentAdapter.get(name).config_dir}/)"
|
|
204
|
+
for name in AgentAdapter.list_names()
|
|
205
|
+
)
|
|
206
|
+
raise InstallError(
|
|
207
|
+
f"No AI platform detected.\n"
|
|
208
|
+
f"DeepWork supports: {supported}.\n"
|
|
209
|
+
"Please set up one of these platforms first, or use --platform to specify."
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if len(available_adapters) > 1:
|
|
213
|
+
# Multiple platforms - ask user to specify
|
|
214
|
+
platform_names = ", ".join(a.display_name for a in available_adapters)
|
|
215
|
+
raise InstallError(
|
|
216
|
+
f"Multiple AI platforms detected: {platform_names}\n"
|
|
217
|
+
"Please specify which platform to use with --platform option."
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
adapter = available_adapters[0]
|
|
221
|
+
console.print(f" [green]✓[/green] {adapter.display_name} detected")
|
|
222
|
+
platform_to_add = adapter.name
|
|
223
|
+
|
|
224
|
+
# Step 3: Create .deepwork/ directory structure
|
|
225
|
+
console.print("[yellow]→[/yellow] Creating DeepWork directory structure...")
|
|
226
|
+
deepwork_dir = project_path / ".deepwork"
|
|
227
|
+
jobs_dir = deepwork_dir / "jobs"
|
|
228
|
+
ensure_dir(deepwork_dir)
|
|
229
|
+
ensure_dir(jobs_dir)
|
|
230
|
+
console.print(f" [green]✓[/green] Created {deepwork_dir.relative_to(project_path)}/")
|
|
231
|
+
|
|
232
|
+
# Step 3b: Inject standard jobs (core job definitions)
|
|
233
|
+
console.print("[yellow]→[/yellow] Installing core job definitions...")
|
|
234
|
+
_inject_deepwork_jobs(jobs_dir, project_path)
|
|
235
|
+
_inject_deepwork_policy(jobs_dir, project_path)
|
|
236
|
+
|
|
237
|
+
# Step 3c: Create .gitignore for temporary files
|
|
238
|
+
_create_deepwork_gitignore(deepwork_dir)
|
|
239
|
+
console.print(" [green]✓[/green] Created .deepwork/.gitignore")
|
|
240
|
+
|
|
241
|
+
# Step 4: Load or create config.yml
|
|
242
|
+
console.print("[yellow]→[/yellow] Updating configuration...")
|
|
243
|
+
config_file = deepwork_dir / "config.yml"
|
|
244
|
+
|
|
245
|
+
if config_file.exists():
|
|
246
|
+
config_data = load_yaml(config_file)
|
|
247
|
+
if config_data is None:
|
|
248
|
+
config_data = {}
|
|
249
|
+
else:
|
|
250
|
+
config_data = {}
|
|
251
|
+
|
|
252
|
+
# Initialize config structure
|
|
253
|
+
if "version" not in config_data:
|
|
254
|
+
config_data["version"] = "0.1.0"
|
|
255
|
+
|
|
256
|
+
if "platforms" not in config_data:
|
|
257
|
+
config_data["platforms"] = []
|
|
258
|
+
|
|
259
|
+
# Add platform if not already present
|
|
260
|
+
if platform_to_add not in config_data["platforms"]:
|
|
261
|
+
config_data["platforms"].append(platform_to_add)
|
|
262
|
+
console.print(f" [green]✓[/green] Added {adapter.display_name} to platforms")
|
|
263
|
+
else:
|
|
264
|
+
console.print(f" [dim]•[/dim] {adapter.display_name} already configured")
|
|
265
|
+
|
|
266
|
+
save_yaml(config_file, config_data)
|
|
267
|
+
console.print(f" [green]✓[/green] Updated {config_file.relative_to(project_path)}")
|
|
268
|
+
|
|
269
|
+
# Step 5: Run sync to generate commands
|
|
270
|
+
console.print()
|
|
271
|
+
console.print("[yellow]→[/yellow] Running sync to generate commands...")
|
|
272
|
+
console.print()
|
|
273
|
+
|
|
274
|
+
from deepwork.cli.sync import sync_commands
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
sync_commands(project_path)
|
|
278
|
+
except Exception as e:
|
|
279
|
+
raise InstallError(f"Failed to sync commands: {e}") from e
|
|
280
|
+
|
|
281
|
+
# Success message
|
|
282
|
+
console.print()
|
|
283
|
+
console.print(
|
|
284
|
+
f"[bold green]✓ DeepWork installed successfully for {adapter.display_name}![/bold green]"
|
|
285
|
+
)
|
|
286
|
+
console.print()
|
|
287
|
+
console.print("[bold]Next steps:[/bold]")
|
|
288
|
+
console.print(" 1. Start your agent CLI (ex. [cyan]claude[/cyan] or [cyan]gemini[/cyan])")
|
|
289
|
+
console.print(" 2. Define your first job using the command [cyan]/deepwork_jobs.define[/cyan]")
|
|
290
|
+
console.print()
|
deepwork/cli/main.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""DeepWork CLI entry point."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
console = Console()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
@click.version_option(package_name="deepwork")
|
|
11
|
+
def cli() -> None:
|
|
12
|
+
"""DeepWork - Framework for AI-powered multi-step workflows."""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Import commands
|
|
17
|
+
from deepwork.cli.install import install # noqa: E402
|
|
18
|
+
from deepwork.cli.sync import sync # noqa: E402
|
|
19
|
+
|
|
20
|
+
cli.add_command(install)
|
|
21
|
+
cli.add_command(sync)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
cli()
|
deepwork/cli/sync.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Sync command for DeepWork CLI."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from deepwork.core.adapters import AgentAdapter
|
|
10
|
+
from deepwork.core.generator import CommandGenerator
|
|
11
|
+
from deepwork.core.hooks_syncer import collect_job_hooks, sync_hooks_to_platform
|
|
12
|
+
from deepwork.core.parser import parse_job_definition
|
|
13
|
+
from deepwork.utils.fs import ensure_dir
|
|
14
|
+
from deepwork.utils.yaml_utils import load_yaml
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SyncError(Exception):
|
|
20
|
+
"""Exception raised for sync errors."""
|
|
21
|
+
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.command()
|
|
26
|
+
@click.option(
|
|
27
|
+
"--path",
|
|
28
|
+
type=click.Path(exists=True, file_okay=False, path_type=Path),
|
|
29
|
+
default=".",
|
|
30
|
+
help="Path to project directory (default: current directory)",
|
|
31
|
+
)
|
|
32
|
+
def sync(path: Path) -> None:
|
|
33
|
+
"""
|
|
34
|
+
Sync DeepWork commands to all configured platforms.
|
|
35
|
+
|
|
36
|
+
Regenerates all slash-commands for job steps and core commands based on
|
|
37
|
+
the current job definitions in .deepwork/jobs/.
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
sync_commands(path)
|
|
41
|
+
except SyncError as e:
|
|
42
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
43
|
+
raise click.Abort() from e
|
|
44
|
+
except Exception as e:
|
|
45
|
+
console.print(f"[red]Unexpected error:[/red] {e}")
|
|
46
|
+
raise
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def sync_commands(project_path: Path) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Sync commands to all configured platforms.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
project_path: Path to project directory
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
SyncError: If sync fails
|
|
58
|
+
"""
|
|
59
|
+
project_path = Path(project_path)
|
|
60
|
+
deepwork_dir = project_path / ".deepwork"
|
|
61
|
+
|
|
62
|
+
# Load config
|
|
63
|
+
config_file = deepwork_dir / "config.yml"
|
|
64
|
+
if not config_file.exists():
|
|
65
|
+
raise SyncError(
|
|
66
|
+
"DeepWork not initialized in this project.\n"
|
|
67
|
+
"Run 'deepwork install --platform <platform>' first."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
config = load_yaml(config_file)
|
|
71
|
+
if not config or "platforms" not in config:
|
|
72
|
+
raise SyncError("Invalid config.yml: missing 'platforms' field")
|
|
73
|
+
|
|
74
|
+
platforms = config["platforms"]
|
|
75
|
+
if not platforms:
|
|
76
|
+
raise SyncError(
|
|
77
|
+
"No platforms configured.\n"
|
|
78
|
+
"Run 'deepwork install --platform <platform>' to add a platform."
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
console.print("[bold cyan]Syncing DeepWork Commands[/bold cyan]\n")
|
|
82
|
+
|
|
83
|
+
# Discover jobs
|
|
84
|
+
jobs_dir = deepwork_dir / "jobs"
|
|
85
|
+
if not jobs_dir.exists():
|
|
86
|
+
job_dirs = []
|
|
87
|
+
else:
|
|
88
|
+
job_dirs = [d for d in jobs_dir.iterdir() if d.is_dir() and (d / "job.yml").exists()]
|
|
89
|
+
|
|
90
|
+
console.print(f"[yellow]→[/yellow] Found {len(job_dirs)} job(s) to sync")
|
|
91
|
+
|
|
92
|
+
# Parse all jobs
|
|
93
|
+
jobs = []
|
|
94
|
+
failed_jobs: list[tuple[str, str]] = []
|
|
95
|
+
for job_dir in job_dirs:
|
|
96
|
+
try:
|
|
97
|
+
job_def = parse_job_definition(job_dir)
|
|
98
|
+
jobs.append(job_def)
|
|
99
|
+
console.print(f" [green]✓[/green] Loaded {job_def.name} v{job_def.version}")
|
|
100
|
+
except Exception as e:
|
|
101
|
+
console.print(f" [red]✗[/red] Failed to load {job_dir.name}: {e}")
|
|
102
|
+
failed_jobs.append((job_dir.name, str(e)))
|
|
103
|
+
|
|
104
|
+
# Fail early if any jobs failed to parse
|
|
105
|
+
if failed_jobs:
|
|
106
|
+
console.print()
|
|
107
|
+
console.print("[bold red]Sync aborted due to job parsing errors:[/bold red]")
|
|
108
|
+
for job_name, error in failed_jobs:
|
|
109
|
+
console.print(f" • {job_name}: {error}")
|
|
110
|
+
raise SyncError(f"Failed to parse {len(failed_jobs)} job(s)")
|
|
111
|
+
|
|
112
|
+
# Collect hooks from all jobs
|
|
113
|
+
job_hooks_list = collect_job_hooks(jobs_dir)
|
|
114
|
+
if job_hooks_list:
|
|
115
|
+
console.print(f"[yellow]→[/yellow] Found {len(job_hooks_list)} job(s) with hooks")
|
|
116
|
+
|
|
117
|
+
# Sync each platform
|
|
118
|
+
generator = CommandGenerator()
|
|
119
|
+
stats = {"platforms": 0, "commands": 0, "hooks": 0}
|
|
120
|
+
|
|
121
|
+
for platform_name in platforms:
|
|
122
|
+
try:
|
|
123
|
+
adapter_cls = AgentAdapter.get(platform_name)
|
|
124
|
+
except Exception:
|
|
125
|
+
console.print(f"[yellow]⚠[/yellow] Unknown platform '{platform_name}', skipping")
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
adapter = adapter_cls(project_path)
|
|
129
|
+
console.print(f"\n[yellow]→[/yellow] Syncing to {adapter.display_name}...")
|
|
130
|
+
|
|
131
|
+
platform_dir = project_path / adapter.config_dir
|
|
132
|
+
commands_dir = platform_dir / adapter.commands_dir
|
|
133
|
+
|
|
134
|
+
# Create commands directory
|
|
135
|
+
ensure_dir(commands_dir)
|
|
136
|
+
|
|
137
|
+
# Generate commands for all jobs
|
|
138
|
+
if jobs:
|
|
139
|
+
console.print(" [dim]•[/dim] Generating commands...")
|
|
140
|
+
for job in jobs:
|
|
141
|
+
try:
|
|
142
|
+
job_paths = generator.generate_all_commands(job, adapter, platform_dir)
|
|
143
|
+
stats["commands"] += len(job_paths)
|
|
144
|
+
console.print(f" [green]✓[/green] {job.name} ({len(job_paths)} commands)")
|
|
145
|
+
except Exception as e:
|
|
146
|
+
console.print(f" [red]✗[/red] Failed for {job.name}: {e}")
|
|
147
|
+
|
|
148
|
+
# Sync hooks to platform settings
|
|
149
|
+
if job_hooks_list:
|
|
150
|
+
console.print(" [dim]•[/dim] Syncing hooks...")
|
|
151
|
+
try:
|
|
152
|
+
hooks_count = sync_hooks_to_platform(project_path, adapter, job_hooks_list)
|
|
153
|
+
stats["hooks"] += hooks_count
|
|
154
|
+
if hooks_count > 0:
|
|
155
|
+
console.print(f" [green]✓[/green] Synced {hooks_count} hook(s)")
|
|
156
|
+
except Exception as e:
|
|
157
|
+
console.print(f" [red]✗[/red] Failed to sync hooks: {e}")
|
|
158
|
+
|
|
159
|
+
stats["platforms"] += 1
|
|
160
|
+
|
|
161
|
+
# Summary
|
|
162
|
+
console.print()
|
|
163
|
+
console.print("[bold green]✓ Sync complete![/bold green]")
|
|
164
|
+
console.print()
|
|
165
|
+
|
|
166
|
+
table = Table(show_header=False, box=None, padding=(0, 2))
|
|
167
|
+
table.add_column("Metric", style="cyan")
|
|
168
|
+
table.add_column("Count", style="green")
|
|
169
|
+
|
|
170
|
+
table.add_row("Platforms synced", str(stats["platforms"]))
|
|
171
|
+
table.add_row("Total commands", str(stats["commands"]))
|
|
172
|
+
if stats["hooks"] > 0:
|
|
173
|
+
table.add_row("Hooks synced", str(stats["hooks"]))
|
|
174
|
+
|
|
175
|
+
console.print(table)
|
|
176
|
+
console.print()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core components for DeepWork."""
|