deepparallel 0.2.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.
@@ -0,0 +1,110 @@
1
+ """Sandboxed code execution.
2
+
3
+ Runs a snippet in an ephemeral Docker container when the daemon is reachable
4
+ (real isolation: no network, memory cap), else falls back to a timeboxed local
5
+ subprocess. Force a mode with DEEPPARALLEL_SANDBOX=docker|subprocess (default auto).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ import subprocess
13
+ import sys
14
+ import tempfile
15
+ from pathlib import Path
16
+
17
+ from deepparallel.tools import tool
18
+
19
+ _LANGS = {
20
+ "python": {
21
+ "image": "python:3.12-slim",
22
+ "argv": ["python"],
23
+ "local": [sys.executable],
24
+ "ext": ".py",
25
+ },
26
+ "javascript": {"image": "node:20-slim", "argv": ["node"], "local": ["node"], "ext": ".js"},
27
+ "bash": {"image": "bash:5", "argv": ["bash"], "local": ["bash"], "ext": ".sh"},
28
+ }
29
+ _ALIASES = {"py": "python", "js": "javascript", "node": "javascript", "sh": "bash", "shell": "bash"}
30
+
31
+ _MAX_OUT = 50_000
32
+
33
+
34
+ def _docker_available() -> bool:
35
+ try:
36
+ proc = subprocess.run(["docker", "version"], capture_output=True, text=True, timeout=4)
37
+ return proc.returncode == 0
38
+ except Exception: # noqa: BLE001 - any failure means no usable daemon
39
+ return False
40
+
41
+
42
+ def _run(cmd: list[str], cwd: str | None, timeout: int):
43
+ proc = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, timeout=timeout)
44
+ return proc.stdout or "", proc.stderr or "", proc.returncode
45
+
46
+
47
+ def _use_docker() -> bool:
48
+ mode = os.environ.get("DEEPPARALLEL_SANDBOX", "auto").strip().lower()
49
+ if mode == "docker":
50
+ return True
51
+ if mode == "subprocess":
52
+ return False
53
+ return _docker_available()
54
+
55
+
56
+ @tool(dangerous=True)
57
+ def run_code(language: str, code: str, timeout_seconds: int = 30) -> str:
58
+ """Execute a code snippet in a sandbox and capture its output.
59
+
60
+ :param language: One of python, javascript, bash.
61
+ :param code: Source code to run.
62
+ :param timeout_seconds: Maximum execution time in seconds.
63
+ """
64
+ lang = _ALIASES.get(language.strip().lower(), language.strip().lower())
65
+ spec = _LANGS.get(lang)
66
+ if spec is None:
67
+ return json.dumps({"error": f"unsupported language: {language}"})
68
+ timeout = min(int(timeout_seconds), 300)
69
+
70
+ with tempfile.TemporaryDirectory() as tmp:
71
+ src = Path(tmp) / f"snippet{spec['ext']}"
72
+ src.write_text(code, encoding="utf-8")
73
+ if _use_docker():
74
+ sandbox = "docker"
75
+ cmd = [
76
+ "docker",
77
+ "run",
78
+ "--rm",
79
+ "--network",
80
+ "none",
81
+ "--memory",
82
+ "512m",
83
+ "-v",
84
+ f"{tmp}:/work",
85
+ "-w",
86
+ "/work",
87
+ spec["image"],
88
+ *spec["argv"],
89
+ src.name,
90
+ ]
91
+ cwd = None
92
+ else:
93
+ sandbox = "subprocess"
94
+ cmd = [*spec["local"], str(src)]
95
+ cwd = tmp
96
+ try:
97
+ stdout, stderr, rc = _run(cmd, cwd, timeout)
98
+ except subprocess.TimeoutExpired:
99
+ return json.dumps({"error": f"code timed out after {timeout}s", "sandbox": sandbox})
100
+ except FileNotFoundError as e:
101
+ return json.dumps({"error": f"runtime not available: {e}", "sandbox": sandbox})
102
+
103
+ return json.dumps(
104
+ {
105
+ "stdout": stdout[:_MAX_OUT],
106
+ "stderr": stderr[:10_000],
107
+ "exit_code": rc,
108
+ "sandbox": sandbox,
109
+ }
110
+ )
@@ -0,0 +1,38 @@
1
+ """Content-search tool (grep)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+ from pathlib import Path
8
+
9
+ from deepparallel.tools import tool
10
+
11
+
12
+ @tool(dangerous=False)
13
+ def grep(dir_path: str, pattern: str, include: str = "**/*") -> str:
14
+ """Search file contents for a regular-expression pattern.
15
+
16
+ :param dir_path: Root directory to search.
17
+ :param pattern: Python regular expression.
18
+ :param include: Glob restricting which files are searched.
19
+ """
20
+ root = Path(dir_path).expanduser().resolve()
21
+ try:
22
+ rx = re.compile(pattern)
23
+ except re.error as e:
24
+ return json.dumps({"error": f"bad pattern: {e}"})
25
+ matches = []
26
+ for p in sorted(root.glob(include)):
27
+ if not p.is_file():
28
+ continue
29
+ try:
30
+ text = p.read_text(encoding="utf-8", errors="replace")
31
+ except OSError:
32
+ continue
33
+ for i, line in enumerate(text.splitlines(), 1):
34
+ if rx.search(line):
35
+ matches.append({"path": str(p), "line_number": i, "line": line[:400]})
36
+ if len(matches) >= 500:
37
+ return json.dumps({"matches": matches, "truncated": True})
38
+ return json.dumps({"matches": matches})
@@ -0,0 +1,38 @@
1
+ """Shell execution tool."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import subprocess
7
+
8
+ from deepparallel.tools import tool
9
+
10
+
11
+ @tool(dangerous=True)
12
+ def run_shell(command: str, working_directory: str = "", timeout_seconds: int = 120) -> str:
13
+ """Run a shell command and capture its output.
14
+
15
+ :param command: Command to execute via the shell.
16
+ :param working_directory: Directory to run in (default: current).
17
+ :param timeout_seconds: Maximum execution time in seconds.
18
+ """
19
+ timeout_seconds = min(int(timeout_seconds), 600)
20
+ try:
21
+ r = subprocess.run(
22
+ command,
23
+ shell=True,
24
+ capture_output=True,
25
+ text=True,
26
+ timeout=timeout_seconds,
27
+ cwd=working_directory or None,
28
+ )
29
+ except subprocess.TimeoutExpired:
30
+ return json.dumps(
31
+ {"error": f"Command timed out after {timeout_seconds}s", "return_code": -1}
32
+ )
33
+ stdout = r.stdout or ""
34
+ if len(stdout) > 50000:
35
+ stdout = stdout[:50000] + "\n... (output truncated at 50KB)"
36
+ return json.dumps(
37
+ {"stdout": stdout, "stderr": (r.stderr or "")[:10000], "return_code": r.returncode}
38
+ )
@@ -0,0 +1,54 @@
1
+ """Vision tool: analyze a local image with a multimodal deployment.
2
+
3
+ The text backend (DeepParallel) is not multimodal, so this routes to a separate
4
+ vision deployment named by DEEPPARALLEL_VISION_DEPLOYMENT, using the same transport.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import base64
10
+ import json
11
+ import mimetypes
12
+ import os
13
+ from pathlib import Path
14
+
15
+ from deepparallel.tools import tool
16
+
17
+
18
+ @tool(dangerous=False)
19
+ def analyze_image(image_path: str, prompt: str = "Describe this image in detail.") -> str:
20
+ """Analyze a local image with a vision model and return a description.
21
+
22
+ Requires DEEPPARALLEL_VISION_DEPLOYMENT (a multimodal deployment).
23
+
24
+ :param image_path: Path to a local image file.
25
+ :param prompt: What to ask about the image.
26
+ """
27
+ path = Path(image_path).expanduser().resolve()
28
+ if not path.is_file():
29
+ return json.dumps({"error": f"image not found: {image_path}"})
30
+ # Defaults to a multimodal deployment so vision works out of the box;
31
+ # override with DEEPPARALLEL_VISION_DEPLOYMENT.
32
+ deployment = (os.environ.get("DEEPPARALLEL_VISION_DEPLOYMENT") or "Llama-4-Scout").strip()
33
+
34
+ from deepparallel import backend as backend_mod
35
+ from deepparallel.config import resolve_settings
36
+
37
+ settings = resolve_settings()
38
+ data = base64.b64encode(path.read_bytes()).decode()
39
+ mime = mimetypes.guess_type(str(path))[0] or "image/png"
40
+ messages = [
41
+ {
42
+ "role": "user",
43
+ "content": [
44
+ {"type": "text", "text": prompt},
45
+ {"type": "image_url", "image_url": {"url": f"data:{mime};base64,{data}"}},
46
+ ],
47
+ }
48
+ ]
49
+ vision = backend_mod.backend_for_deployment(settings, deployment)
50
+ try:
51
+ msg = vision.chat(messages, [], settings.temperature, settings.max_tokens)
52
+ except Exception as e: # noqa: BLE001 - surface vision failure to the model
53
+ return json.dumps({"error": f"vision call failed: {type(e).__name__}: {e}"})
54
+ return json.dumps({"description": (msg.get("content") or "").strip()})
@@ -0,0 +1,76 @@
1
+ """Web tools: fetch a page's text, and search (key-gated)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import re
8
+
9
+ import httpx
10
+
11
+ from deepparallel.tools import tool
12
+
13
+ _SCRIPT_STYLE = re.compile(r"<(script|style)\b[^>]*>.*?</\1>", re.IGNORECASE | re.DOTALL)
14
+ _TAG = re.compile(r"<[^>]+>")
15
+ _TITLE = re.compile(r"<title[^>]*>(.*?)</title>", re.IGNORECASE | re.DOTALL)
16
+ _WS = re.compile(r"\s+")
17
+ _TIMEOUT = 15.0
18
+ _UA = "DeepParallel/0.1"
19
+
20
+
21
+ @tool(dangerous=False)
22
+ def web_fetch(url: str, max_chars: int = 8000) -> str:
23
+ """Fetch a web page and return its readable text (HTML stripped).
24
+
25
+ :param url: The URL to fetch.
26
+ :param max_chars: Maximum characters of text to return.
27
+ """
28
+ try:
29
+ r = httpx.get(url, timeout=_TIMEOUT, follow_redirects=True, headers={"user-agent": _UA})
30
+ r.raise_for_status()
31
+ except Exception as e: # noqa: BLE001 - surface fetch failure to the model
32
+ return json.dumps({"error": f"fetch failed: {type(e).__name__}: {e}"})
33
+ html = r.text or ""
34
+ title_m = _TITLE.search(html)
35
+ title = _WS.sub(" ", _TAG.sub("", title_m.group(1))).strip() if title_m else ""
36
+ text = _WS.sub(" ", _TAG.sub(" ", _SCRIPT_STYLE.sub(" ", html))).strip()
37
+ return json.dumps({"url": url, "title": title, "text": text[:max_chars]})
38
+
39
+
40
+ @tool(dangerous=False)
41
+ def web_search(query: str, count: int = 5) -> str:
42
+ """Search the web and return result titles, URLs, and snippets.
43
+
44
+ Requires DEEPPARALLEL_SEARCH_API_KEY (Brave Search API by default).
45
+
46
+ :param query: The search query.
47
+ :param count: Maximum number of results.
48
+ """
49
+ key = os.environ.get("DEEPPARALLEL_SEARCH_API_KEY")
50
+ if not key:
51
+ return json.dumps(
52
+ {"error": "search not configured: set DEEPPARALLEL_SEARCH_API_KEY (Brave Search API)"}
53
+ )
54
+ url = os.environ.get(
55
+ "DEEPPARALLEL_SEARCH_URL", "https://api.search.brave.com/res/v1/web/search"
56
+ )
57
+ try:
58
+ r = httpx.get(
59
+ url,
60
+ params={"q": query, "count": count},
61
+ headers={"X-Subscription-Token": key, "accept": "application/json"},
62
+ timeout=_TIMEOUT,
63
+ )
64
+ r.raise_for_status()
65
+ data = r.json()
66
+ except Exception as e: # noqa: BLE001 - surface search failure to the model
67
+ return json.dumps({"error": f"search failed: {type(e).__name__}: {e}"})
68
+ results = [
69
+ {
70
+ "title": item.get("title", ""),
71
+ "url": item.get("url", ""),
72
+ "snippet": item.get("description", ""),
73
+ }
74
+ for item in (data.get("web", {}).get("results") or [])[:count]
75
+ ]
76
+ return json.dumps({"results": results})
@@ -0,0 +1,128 @@
1
+ Metadata-Version: 2.4
2
+ Name: deepparallel
3
+ Version: 0.2.0
4
+ Summary: DeepParallel - a multi-model agentic coding CLI with cross-model Guardian review, served via Crowe Logic.
5
+ Author-email: Michael Crowe <michael@crowelogic.com>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://crowelogic.com
8
+ Keywords: deepparallel,agent,coding-agent,cli,llm,code-review,crowe-logic
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: click>=8.1.0
12
+ Requires-Dist: rich>=14.0.0
13
+ Requires-Dist: prompt-toolkit>=3.0.0
14
+ Requires-Dist: httpx>=0.28.0
15
+ Requires-Dist: python-dotenv>=1.0.0
16
+ Requires-Dist: tree-sitter>=0.25.0
17
+ Requires-Dist: tree-sitter-language-pack>=1.8.0
18
+ Requires-Dist: cryptography>=42.0.0
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
21
+ Requires-Dist: ruff>=0.6.0; extra == "dev"
22
+
23
+ # DeepParallel
24
+
25
+ A focused command-line interface for the DeepParallel model, served via Crowe Logic.
26
+
27
+ ## Install
28
+
29
+ uv venv
30
+ uv pip install -p .venv -e .
31
+
32
+ ## Configure
33
+
34
+ Set the backend env vars (a `.env` file in the working directory is loaded automatically):
35
+
36
+ # default backend: azure
37
+ AZURE_CORE_ENDPOINT=https://<resource>.openai.azure.com
38
+ AZURE_CORE_API_KEY=<key>
39
+ # optional overrides
40
+ DEEPPARALLEL_API_VERSION=2024-08-01-preview
41
+
42
+ # or route through the Crowe Logic Foundry control plane:
43
+ DEEPPARALLEL_BACKEND=foundry
44
+ FOUNDRY_BASE_URL=https://<control-plane>
45
+ FOUNDRY_API_KEY=<token>
46
+
47
+ ## Use
48
+
49
+ deepparallel # interactive agent chat
50
+ deepparallel run "question" # one-shot, pipe-friendly (answer on stdout)
51
+ deepparallel tools # list the agent's tools
52
+ deepparallel info # model + backend status
53
+ deepparallel doctor # diagnose config + reachability
54
+ deepparallel run --no-tools "..." # plain chat, no tools
55
+ deepparallel run --yes "..." # auto-approve tool actions
56
+
57
+ ## Fusion (stacking models for stronger output)
58
+
59
+ `deepparallel` can stack the answerer with a separate reasoning model. All modes
60
+ compose hosted backends (no GPU/weight-merging), so they are API-call stacking.
61
+
62
+ deepparallel run --fuse reason "hard question" # reasoner thinks, answerer answers
63
+ deepparallel run --fuse escalate "question" # answer first; escalate to reasoner only if unsure
64
+ deepparallel run --deep "question" # heavy: many models in parallel + a judge (slow)
65
+ deepparallel run --dual "A,B" "question" # compare two deployments side by side
66
+ deepparallel run --dual "A,B" --synth "question" # ...and synthesize a merged answer
67
+
68
+ `--fuse` also works in interactive chat. Set a default with `DEEPPARALLEL_FUSION=reason`.
69
+
70
+ ## Fusion-native UX (what single-model agents cannot do)
71
+
72
+ - **Guardian review** - before an edit (`write_file` / `edit_file` / `ast_replace_symbol`)
73
+ is applied, a second model reviews the diff and its verdict
74
+ (`safe` / `risky: ...` / `bug: ...`) is shown in the confirm card. Advisory:
75
+ you still approve. Toggle with `DEEPPARALLEL_GUARDIAN`; pick the reviewer with
76
+ `DEEPPARALLEL_GUARDIAN_DEPLOYMENT`.
77
+ - **Consensus + divergence** - `run --deep` prints a `consensus:` chip from
78
+ cross-model agreement (agreement, not correctness), and on low agreement
79
+ lists the dissenting candidates.
80
+ - **Live dial** - in interactive chat, `/fast`, `/fuse`, `/escalate` switch the
81
+ fusion mode mid-session (shown in the prompt); `/deep` runs the next prompt as
82
+ a multi-model query.
83
+
84
+ ## Agent and tools
85
+
86
+ `deepparallel` is an agent: it can call tools to inspect and change your project.
87
+
88
+ - Read-only (run automatically): `read_file`, `list_dir`, `glob`, `grep`,
89
+ `ast_symbols`, `ast_show_symbol`, `web_fetch`, `web_search`, `analyze_image`.
90
+ - Mutating / executing (require confirmation): `write_file`, `edit_file`,
91
+ `ast_replace_symbol`, `run_shell`, `run_code`.
92
+
93
+ `web_search` needs `DEEPPARALLEL_SEARCH_API_KEY`; `analyze_image` works out of the
94
+ box on a multimodal deployment (override with `DEEPPARALLEL_VISION_DEPLOYMENT`).
95
+
96
+ In interactive chat, mutating actions prompt for y/n. In `run` (non-interactive)
97
+ they are denied unless you pass `--yes`. `ast_*` tools are multi-language via
98
+ tree-sitter; `run_code` executes in a Docker sandbox when available, else a
99
+ timeboxed local subprocess.
100
+
101
+ ## Configuration reference
102
+
103
+ | Variable | Purpose | Default |
104
+ |---|---|---|
105
+ | `DEEPPARALLEL_BACKEND` | `azure` or `foundry` | `azure` |
106
+ | `AZURE_CORE_ENDPOINT` / `AZURE_CORE_API_KEY` | Azure transport | (required for azure) |
107
+ | `DEEPPARALLEL_API_VERSION` | Azure API version | `2024-08-01-preview` |
108
+ | `FOUNDRY_BASE_URL` / `FOUNDRY_API_KEY` | control-plane transport | (required for foundry) |
109
+ | `DEEPPARALLEL_TEMPERATURE` | default sampling temperature | `0.4` |
110
+ | `DEEPPARALLEL_MAX_TOKENS` | response cap | `2048` |
111
+ | `DEEPPARALLEL_THINK` | surface reasoning stream | `0` (answer-only) |
112
+ | `DEEPPARALLEL_TOOLS` | enable agent tools | `1` (on) |
113
+ | `DEEPPARALLEL_AUTO_APPROVE` | auto-approve mutating tools | `0` (off) |
114
+ | `DEEPPARALLEL_MAX_STEPS` | max agent tool-call rounds | `12` |
115
+ | `DEEPPARALLEL_SHELL_TIMEOUT` | run_shell timeout (s) | `120` |
116
+ | `DEEPPARALLEL_SANDBOX` | `auto` / `docker` / `subprocess` for run_code | `auto` |
117
+ | `DEEPPARALLEL_PLAIN` | force plain (non-rich) output | `0` |
118
+ | `DEEPPARALLEL_FUSION` | default fusion: `off` / `reason` / `escalate` | `off` |
119
+ | `DEEPPARALLEL_REASONER_DEPLOYMENT` | reasoner model for fusion | `DeepSeek-R1-0528` |
120
+ | `DEEPPARALLEL_PARALLEL_MODELS` | comma-separated chains for `--deep` | (three defaults) |
121
+ | `DEEPPARALLEL_JUDGE_DEPLOYMENT` | judge/synthesizer model | the primary deployment |
122
+ | `DEEPPARALLEL_SEARCH_API_KEY` | enables web_search (Brave Search API) | (unset) |
123
+ | `DEEPPARALLEL_SEARCH_URL` | search API endpoint | Brave web search |
124
+ | `DEEPPARALLEL_VISION_DEPLOYMENT` | multimodal model for analyze_image | `Llama-4-Scout` |
125
+ | `DEEPPARALLEL_GUARDIAN` | second-model review of edits before apply | `1` (on) |
126
+ | `DEEPPARALLEL_GUARDIAN_DEPLOYMENT` | the reviewer model | the reasoner |
127
+
128
+ DeepParallel is served via Crowe Logic infrastructure. https://crowelogic.com
@@ -0,0 +1,26 @@
1
+ deepparallel/__init__.py,sha256=dcRd-DGbeCsxhE2nBkRv6XCRb0Gsg3zBKc7vcf-4h8o,55
2
+ deepparallel/agent.py,sha256=HH8IntUvRF6sbOKXOP3RYstVGV6XllNhRY0vv2p3EII,10598
3
+ deepparallel/backend.py,sha256=rKlpilOb3Wj8PkjK5OlYZg7p-0_UxY3pe6A_lMFY85Q,10257
4
+ deepparallel/branding.py,sha256=40FmA2syEK6gjzD886HeL5obaKMWROTxc_IveiTuntA,7208
5
+ deepparallel/cli.py,sha256=fVjwiXeeN7aHGF9fKa0e4J6PJ0FKd262ocFqLCaBH-Q,20217
6
+ deepparallel/config.py,sha256=G6xt7oyV89h8veJEZiA0KM6U-KozUHGjJhdbsIF6gss,5255
7
+ deepparallel/fusion.py,sha256=GoMJMmg5TkZDFpjNqo1dAEo7-Xuc0C_kyd7MHtyzR04,8265
8
+ deepparallel/licensing.py,sha256=1vG_VBuovNIjIy674Ej1w55wRSU8owc3XNSnwm4WrS8,3558
9
+ deepparallel/registry.json,sha256=HHKm4tIOHFGO9KR0Z2dEQMIo4qTUlAswE_IzNv187Q0,298
10
+ deepparallel/renderer.py,sha256=pW0bA1-1WN2DgawbZKAwT_vtaRVTUFLztdClLKfFhdI,7759
11
+ deepparallel/system_prompt.txt,sha256=WBWszuTW9B25DK0I584QVbcoo-fGCDV8hFs1qBv5H8g,667
12
+ deepparallel/tools/__init__.py,sha256=vgZQtfEIxH39Qho6hhpUo6rsYtZu4moEfcx7KINpQBk,611
13
+ deepparallel/tools/codeast.py,sha256=6wlH6XsBx39Jkizj7P0Wkb1_PyWxfZt1EA0SzXF-hWY,5860
14
+ deepparallel/tools/edit.py,sha256=puT4x6FTG54MqiE3ur1EL6YVQwg66f5rmT7AE5CUgAY,1060
15
+ deepparallel/tools/files.py,sha256=aydsTgovEsoCWNwp2y4V3afDa8p8oIBeZA5tXXw175g,2667
16
+ deepparallel/tools/registry.py,sha256=Eqv_VcWToZbPuOCvNul6_jx1ItAxEt7OLMP4eCr_XQw,4612
17
+ deepparallel/tools/sandbox.py,sha256=WZhsZnkVUnC1ZTDAgyoIUO4C8sUvZHab6Ji3ftfSu_o,3459
18
+ deepparallel/tools/search.py,sha256=jsX0ieouuUZzkWsgDrtwzVF-aV36NetarFqmQy4mT-4,1222
19
+ deepparallel/tools/shell.py,sha256=DjUGBT5DKYPNplEgMIpCy6z5ss7agbzlV2cby9LjGZo,1186
20
+ deepparallel/tools/vision.py,sha256=Nm8uIDllyaa6ucfgmzdM_272H1EVW27fO2ALybw6n0w,2044
21
+ deepparallel/tools/web.py,sha256=Q3HZ0uAOyHWt4EV9q-gNLdcp1zOOB0QDXhjgRX70YeY,2647
22
+ deepparallel-0.2.0.dist-info/METADATA,sha256=HY1ZGm6Rcht5yNrFGgYXnTfXeFeR056B80aniTLfAsQ,6110
23
+ deepparallel-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
24
+ deepparallel-0.2.0.dist-info/entry_points.txt,sha256=1WPP0AaAhVqet5SvmVlFj-SdkY4cYWGVXy12mAHTTXs,82
25
+ deepparallel-0.2.0.dist-info/top_level.txt,sha256=KpyrGzNnWKi_qNljdM2Z5dyfMAff_-6_lpDZv07NcEc,13
26
+ deepparallel-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ deepparallel = deepparallel.cli:main
3
+ dp = deepparallel.cli:main
@@ -0,0 +1 @@
1
+ deepparallel