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.
Files changed (41) hide show
  1. deepwork/__init__.py +25 -0
  2. deepwork/cli/__init__.py +1 -0
  3. deepwork/cli/install.py +290 -0
  4. deepwork/cli/main.py +25 -0
  5. deepwork/cli/sync.py +176 -0
  6. deepwork/core/__init__.py +1 -0
  7. deepwork/core/adapters.py +373 -0
  8. deepwork/core/detector.py +93 -0
  9. deepwork/core/generator.py +290 -0
  10. deepwork/core/hooks_syncer.py +206 -0
  11. deepwork/core/parser.py +310 -0
  12. deepwork/core/policy_parser.py +285 -0
  13. deepwork/hooks/__init__.py +1 -0
  14. deepwork/hooks/evaluate_policies.py +159 -0
  15. deepwork/schemas/__init__.py +1 -0
  16. deepwork/schemas/job_schema.py +212 -0
  17. deepwork/schemas/policy_schema.py +68 -0
  18. deepwork/standard_jobs/deepwork_jobs/job.yml +102 -0
  19. deepwork/standard_jobs/deepwork_jobs/steps/define.md +359 -0
  20. deepwork/standard_jobs/deepwork_jobs/steps/implement.md +435 -0
  21. deepwork/standard_jobs/deepwork_jobs/steps/refine.md +447 -0
  22. deepwork/standard_jobs/deepwork_policy/hooks/capture_work_tree.sh +26 -0
  23. deepwork/standard_jobs/deepwork_policy/hooks/get_changed_files.sh +30 -0
  24. deepwork/standard_jobs/deepwork_policy/hooks/global_hooks.yml +8 -0
  25. deepwork/standard_jobs/deepwork_policy/hooks/policy_stop_hook.sh +72 -0
  26. deepwork/standard_jobs/deepwork_policy/hooks/user_prompt_submit.sh +17 -0
  27. deepwork/standard_jobs/deepwork_policy/job.yml +35 -0
  28. deepwork/standard_jobs/deepwork_policy/steps/define.md +174 -0
  29. deepwork/templates/__init__.py +1 -0
  30. deepwork/templates/claude/command-job-step.md.jinja +210 -0
  31. deepwork/templates/gemini/command-job-step.toml.jinja +169 -0
  32. deepwork/utils/__init__.py +1 -0
  33. deepwork/utils/fs.py +128 -0
  34. deepwork/utils/git.py +164 -0
  35. deepwork/utils/validation.py +31 -0
  36. deepwork/utils/yaml_utils.py +89 -0
  37. deepwork-0.1.0.dist-info/METADATA +389 -0
  38. deepwork-0.1.0.dist-info/RECORD +41 -0
  39. deepwork-0.1.0.dist-info/WHEEL +4 -0
  40. deepwork-0.1.0.dist-info/entry_points.txt +2 -0
  41. 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}")
@@ -0,0 +1 @@
1
+ """CLI commands for DeepWork."""
@@ -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."""