designaround 0.1.3__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 (56) hide show
  1. designaround-0.1.3.dist-info/METADATA +27 -0
  2. designaround-0.1.3.dist-info/RECORD +56 -0
  3. designaround-0.1.3.dist-info/WHEEL +4 -0
  4. designaround-0.1.3.dist-info/entry_points.txt +3 -0
  5. designaround_cli/__init__.py +1 -0
  6. designaround_cli/__main__.py +3 -0
  7. designaround_cli/batch.py +146 -0
  8. designaround_cli/bundle/agents/package-lock.json +1555 -0
  9. designaround_cli/bundle/agents/package.json +25 -0
  10. designaround_cli/bundle/agents/src/assistant.ts +514 -0
  11. designaround_cli/bundle/agents/src/chat-cli.ts +117 -0
  12. designaround_cli/bundle/agents/src/design.ts +791 -0
  13. designaround_cli/bundle/agents/src/engine.ts +79 -0
  14. designaround_cli/bundle/agents/src/env.ts +21 -0
  15. designaround_cli/bundle/agents/src/main.ts +131 -0
  16. designaround_cli/bundle/agents/src/prompts.ts +59 -0
  17. designaround_cli/bundle/agents/src/runtime.ts +205 -0
  18. designaround_cli/bundle/agents/src/team.ts +306 -0
  19. designaround_cli/bundle/agents/src/tools.ts +141 -0
  20. designaround_cli/bundle/agents/src/ui-server.ts +576 -0
  21. designaround_cli/bundle/agents/ui/app.css +373 -0
  22. designaround_cli/bundle/agents/ui/app.js +1208 -0
  23. designaround_cli/bundle/agents/ui/art/welcome.jpg +0 -0
  24. designaround_cli/bundle/agents/ui/favicon.png +0 -0
  25. designaround_cli/bundle/agents/ui/fonts/JETBRAINS-MONO-LICENSE.txt +93 -0
  26. designaround_cli/bundle/agents/ui/fonts/JetBrainsMono.woff2 +0 -0
  27. designaround_cli/bundle/agents/ui/fonts/MONA-SANS-LICENSE.txt +93 -0
  28. designaround_cli/bundle/agents/ui/fonts/MonaSans.woff2 +0 -0
  29. designaround_cli/bundle/agents/ui/i18n.js +355 -0
  30. designaround_cli/bundle/agents/ui/icons.js +2 -0
  31. designaround_cli/bundle/agents/ui/index.html +178 -0
  32. designaround_cli/bundle/agents/ui/mark.svg +1 -0
  33. designaround_cli/bundle/agents/ui/markdown.js +64 -0
  34. designaround_cli/bundle/skill/SKILL.md +136 -0
  35. designaround_cli/bundle/skill/assets/constraints.template.yaml +25 -0
  36. designaround_cli/bundle/skill/references/cadquery.md +67 -0
  37. designaround_cli/bundle/skill/references/constraints.md +17 -0
  38. designaround_cli/bundle/skill/references/jurisdictions.md +41 -0
  39. designaround_cli/bundle/skill/references/output-format.md +35 -0
  40. designaround_cli/bundle/skill/references/safety.md +30 -0
  41. designaround_cli/bundle/skill/references/workflow.md +137 -0
  42. designaround_cli/bundle/skill/scripts/da.py +51 -0
  43. designaround_cli/bundles.py +114 -0
  44. designaround_cli/client.py +76 -0
  45. designaround_cli/config.py +176 -0
  46. designaround_cli/detached.py +73 -0
  47. designaround_cli/figures.py +173 -0
  48. designaround_cli/guarantee.py +164 -0
  49. designaround_cli/main.py +2050 -0
  50. designaround_cli/mcp.py +347 -0
  51. designaround_cli/monitor.py +238 -0
  52. designaround_cli/notify.py +104 -0
  53. designaround_cli/output.py +83 -0
  54. designaround_cli/runner.py +419 -0
  55. designaround_cli/screening.py +247 -0
  56. designaround_cli/share.py +194 -0
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.4
2
+ Name: designaround
3
+ Version: 0.1.3
4
+ Summary: DesignAround CLI + local engine: design-patent screening, design-around, patent views and filing packages. Works with Claude Code, Codex, Cursor, Gemini CLI, OpenCode, OpenClaw and Kiro via the bundled Skill.
5
+ License: Apache-2.0
6
+ Requires-Python: <3.13,>=3.11
7
+ Requires-Dist: designaround-core
8
+ Requires-Dist: designaround-engine
9
+ Requires-Dist: httpx==0.28.1
10
+ Requires-Dist: keyring==25.7.0
11
+ Requires-Dist: pyyaml==6.0.3
12
+ Requires-Dist: rich==15.0.0
13
+ Requires-Dist: typer==0.27.3
14
+ Description-Content-Type: text/markdown
15
+
16
+ # designaround (CLI + local engine)
17
+
18
+ ```bash
19
+ uv tool install designaround # or: pipx install designaround
20
+ designaround onboard # trial key, stored in the OS keychain
21
+ designaround skill install claude-code # or: cursor, codex, gemini-cli, opencode, openclaw, kiro, all
22
+ designaround config mode host # inside an agent host; or platform / byok
23
+ ```
24
+
25
+ Commands print one JSON envelope with `--json` (default when stdout is not a terminal). Exit codes: 0 ok, 2 needs a person, 3 budget or plan, 4 auth, 5 invalid input, 6 error.
26
+
27
+ See the repository README for the full workflow.
@@ -0,0 +1,56 @@
1
+ designaround_cli/__init__.py,sha256=XEqb2aiIn8fzGE68Mph4ck1FtQqsR_am0wRWvrYPffQ,22
2
+ designaround_cli/__main__.py,sha256=CnXNTEvTGzcH7oL7CozD_C3vDVuBfMWH6V93GuaEaDQ,29
3
+ designaround_cli/batch.py,sha256=qjTx7PVRxeSgj-4VpJ9pd3lm6HOIH0CWzeRs07dLe0s,7895
4
+ designaround_cli/bundles.py,sha256=TPEgdwiWbakc0C8MpLQEOFiIrktWfvI9ZB7SUie5c9M,4488
5
+ designaround_cli/client.py,sha256=KnDdcnV3Hy9yiC9kLqefJWJb4KqSg7DA-alBpaquq-E,4609
6
+ designaround_cli/config.py,sha256=WqEl4engoARK3kD0OF8aToud5aQYKduX7tZ3fKYVYNI,4821
7
+ designaround_cli/detached.py,sha256=7OU3A6m-8hrEdl32NoLpvvcJ-isHO3tvN6kzyvaSBmI,3166
8
+ designaround_cli/figures.py,sha256=_G7CDRbiRT2KVtEdK78ZmybNOKdxslMZDk0u_4ceZRI,7251
9
+ designaround_cli/guarantee.py,sha256=6onpoOhzbxcgv4XnONODmGUZ5Ii5mXaqINboo_fnQN8,9191
10
+ designaround_cli/main.py,sha256=DvrLxPlngjPJdJIi9PuGz1BU5Yc9-rumBFQd2vtTkNY,122474
11
+ designaround_cli/mcp.py,sha256=5IUtvUf5J8xtxtJ48oZAWoQOm7bmNNpe24IIys6aKcQ,18242
12
+ designaround_cli/monitor.py,sha256=2VImG_a_08cFBb_U71Red62VCr_VCYqYWJEWmUR3ZB8,12112
13
+ designaround_cli/notify.py,sha256=gEpJHSwv6f057Pc017xEc-iFlDqZTXVuR7jQaMsF964,5147
14
+ designaround_cli/output.py,sha256=IOnymgWO7BsRmSYML31EVPk5G_ixalJFYN_DSuumPlU,2884
15
+ designaround_cli/runner.py,sha256=HbqsartVOmLNWKLpde_tVhsXsIjO6zPeIXQliouLJ9Q,21008
16
+ designaround_cli/screening.py,sha256=TiF8SByj7J6N4O9oKlyY2GaERgOhesnGqum4qzWA3BQ,11080
17
+ designaround_cli/share.py,sha256=SXZFB5dQJKS4MGy6d9eB9FubKmlbmSPcS2NOX3u3OEI,9301
18
+ designaround_cli/bundle/skill/SKILL.md,sha256=cH4Hcb5szpuLmmfjlOeqNG3GkIs9nkLVPNSj1Fn0WNY,12140
19
+ designaround_cli/bundle/skill/assets/constraints.template.yaml,sha256=sk7k4wlblCxdW7qUffsOi16fz5rOlv2Na1IO9JKNicU,651
20
+ designaround_cli/bundle/skill/references/cadquery.md,sha256=zEmTuaZdjCQxxCKqXDztgv9EMQ0MOm8mDQcEc_XQmYM,3317
21
+ designaround_cli/bundle/skill/references/constraints.md,sha256=zCerA69YwEy9LSdW97jDVvuCz_NkDW5M33DTN_E61XY,1271
22
+ designaround_cli/bundle/skill/references/jurisdictions.md,sha256=itRue27QtBlkb2BvMV1E8PQd7twYiM3QzNYpJvxRzJA,3778
23
+ designaround_cli/bundle/skill/references/output-format.md,sha256=_zVUKaBzTZH2HKA9ndNE_jlc2Eskf2SLuxnVVcxwCvY,1635
24
+ designaround_cli/bundle/skill/references/safety.md,sha256=he0HB68Jd-vRGvxH88tI2RgiOrmPlcl5qoP45hc8iIM,2046
25
+ designaround_cli/bundle/skill/references/workflow.md,sha256=V9oHD1txVK3hMq-UH1S1eP8szI641I62R38LSdvIU6c,6850
26
+ designaround_cli/bundle/skill/scripts/da.py,sha256=K6AseZlZxAHuNIpjVx0Sh2K843960x3vxJaMyXFr_n0,1919
27
+ designaround_cli/bundle/agents/package-lock.json,sha256=fzXugjLfSgZZT7ldzYORRHOFdHrAL66PO4u-el4hVBc,58236
28
+ designaround_cli/bundle/agents/package.json,sha256=mY8knQ7ZRmnlodxfGnSoSzb7GGopnN25oKqYeH4kq9E,607
29
+ designaround_cli/bundle/agents/src/assistant.ts,sha256=GDoxZjXhcdpnp9VT6D1k35gczimND-oZM_qOk3FibeQ,52476
30
+ designaround_cli/bundle/agents/src/chat-cli.ts,sha256=l3arbRnG79_1EaHfrftqH2FFlGy6yZITKVSYRWmpwZI,5043
31
+ designaround_cli/bundle/agents/src/design.ts,sha256=4cIQXpU-iRGX7dfti3UcXVxgF6mq1FpdsO61oj77rNE,54443
32
+ designaround_cli/bundle/agents/src/engine.ts,sha256=5KSUIRf5icpJ4bJJQ7065Uh0JlFVQjgcE6b7XNRF5a8,2713
33
+ designaround_cli/bundle/agents/src/env.ts,sha256=vIxtmcl2_YdWuevPz4qCIa4MZlEUXSb39_NjlzNU-Ag,933
34
+ designaround_cli/bundle/agents/src/main.ts,sha256=gcrYt-NwezbA-qmt4YtCiYo9Y7wb8o5mDFZLSLw8fa8,5855
35
+ designaround_cli/bundle/agents/src/prompts.ts,sha256=CapanU9UP6mOAfRlGj60jsaaYAHUA8w1qA1ORTYob0k,4382
36
+ designaround_cli/bundle/agents/src/runtime.ts,sha256=10qs15UAcYNt_9g4x5JZI6jo8LUK62SvkDdw-SQU9o8,9362
37
+ designaround_cli/bundle/agents/src/team.ts,sha256=X2AMRng93qeBkCUjdxOCMXPSbdrfHgp-8JpiBU19rSA,16258
38
+ designaround_cli/bundle/agents/src/tools.ts,sha256=oiY5l92bcpzO8FU39lug0tSOpEyM52SBLNGS9CQEBb4,6750
39
+ designaround_cli/bundle/agents/src/ui-server.ts,sha256=8CDCOEy-mTsF-rACZzTX8k-zEUaTbGP0kMSP66sTfjE,34668
40
+ designaround_cli/bundle/agents/ui/app.css,sha256=V4Ctl1l5QcFppMQbBQB6TGb7rhORirhTI2X0SUNnQbw,28388
41
+ designaround_cli/bundle/agents/ui/app.js,sha256=g6GXCoJapg9gW07JyM2NC-lTgY7XobYKLcE_CTXl1kw,56387
42
+ designaround_cli/bundle/agents/ui/favicon.png,sha256=ngnx3M6_QnvqvhZsVA9D6bdhxM0xy-kzrZINVBKrka4,980
43
+ designaround_cli/bundle/agents/ui/i18n.js,sha256=zSCUem3VzMIu5fFS9j_0K8RyDD0gOLREbv40FarUREg,21796
44
+ designaround_cli/bundle/agents/ui/icons.js,sha256=JWei5nsv0Gz8UMBC8CkSIfN2UTYbXSEryLw61FFtKqE,24550
45
+ designaround_cli/bundle/agents/ui/index.html,sha256=VnbfzQiFmhtLO-7HsoQIcwvmD4-du1q4WmL03K1gyJg,10832
46
+ designaround_cli/bundle/agents/ui/mark.svg,sha256=M7m4GPEpRTU7Yu4bC-ni1gxww3XOqQqpa8t0XmX43lI,447
47
+ designaround_cli/bundle/agents/ui/markdown.js,sha256=Fgj9_bXBIGSWpjvJ8F6sGI5nC6zUc8ZnEkJR2kitSKM,2764
48
+ designaround_cli/bundle/agents/ui/art/welcome.jpg,sha256=i0VUvgDcL0Ydy2CUvCtgxMlyTJs04gWWL7ti-nt1njQ,174534
49
+ designaround_cli/bundle/agents/ui/fonts/JETBRAINS-MONO-LICENSE.txt,sha256=QDWBtp2sXP9AeSBeAca0Z-Vq9EnsvXJHaT3bG6r6AFs,4524
50
+ designaround_cli/bundle/agents/ui/fonts/JetBrainsMono.woff2,sha256=GL5FJyS_3CNsB0ypSiSaf0GoZ1LH0EqyWM6e1WUfan4,40404
51
+ designaround_cli/bundle/agents/ui/fonts/MONA-SANS-LICENSE.txt,sha256=QemRFJPeWJmiAza68Ttupkg7Xuo04jfy_-0iw5vcSo0,4564
52
+ designaround_cli/bundle/agents/ui/fonts/MonaSans.woff2,sha256=XqN0BjPlrHr1bBPX6OwyvK4S53I-U9NChEaaiUfMsvM,98124
53
+ designaround-0.1.3.dist-info/METADATA,sha256=1B0uO7_urXrzo7kVBHXaOKSMNDNpwlaJcK-5_AS_2Tg,1181
54
+ designaround-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
55
+ designaround-0.1.3.dist-info/entry_points.txt,sha256=roVKAb5qM4RTr-F-D5iH04fY2Q_4rDRkkVnnsK_FIeo,90
56
+ designaround-0.1.3.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ da = designaround_cli.main:run
3
+ designaround = designaround_cli.main:run
@@ -0,0 +1 @@
1
+ __version__ = "0.1.3"
@@ -0,0 +1,3 @@
1
+ from .main import run
2
+
3
+ run()
@@ -0,0 +1,146 @@
1
+ """Batch screening from a CSV (FR-RISK-06, FR-CASE-08, FR-CLI-07).
2
+
3
+ Columns (header row, UTF-8; only ``name`` and ``article`` are required):
4
+
5
+ name, article, article_zh, jurisdictions ("US;CN"), image (path), cad (STEP/IGES path), case
6
+
7
+ Each row becomes a case (or reuses ``case``), is screened on the server, and, when a
8
+ 3D version exists (``cad`` column or an existing case), ranked locally. Progress is
9
+ kept in ``<csv>.state.json`` next to the CSV, so a second run continues where the
10
+ first stopped and retries only failed rows. The whole run has one credit cap,
11
+ checked against the estimate before anything is charged.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import csv
17
+ import hashlib
18
+ import json
19
+ import threading
20
+ import time
21
+ from concurrent.futures import ThreadPoolExecutor, as_completed
22
+ from pathlib import Path
23
+
24
+ from designaround_core import catalog
25
+ from designaround_engine import api
26
+ from designaround_engine.errors import EngineError
27
+
28
+ from .output import EXIT_BUDGET, EXIT_INVALID, CliExit
29
+
30
+ MAX_ROWS = 1000
31
+ OFFICES = {"US", "CN", "EU"}
32
+
33
+
34
+ def _key(row: dict) -> str:
35
+ raw = json.dumps({k: (row.get(k) or "").strip() for k in ("name", "article", "image", "cad", "case")}, sort_keys=True, ensure_ascii=False)
36
+ return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:16]
37
+
38
+
39
+ def read_rows(path: Path) -> list[dict]:
40
+ if not path.exists():
41
+ raise CliExit(EXIT_INVALID, "not_found", f"CSV not found: {path}")
42
+ with path.open(encoding="utf-8-sig", newline="") as fh:
43
+ rows = [{(k or "").strip().lower(): (v or "").strip() for k, v in r.items()} for r in csv.DictReader(fh)]
44
+ rows = [r for r in rows if any(r.values())]
45
+ if not rows:
46
+ raise CliExit(EXIT_INVALID, "empty_csv", "The CSV has no rows.", next_actions=["Header: name,article,article_zh,jurisdictions,image,cad"])
47
+ if len(rows) > MAX_ROWS:
48
+ raise CliExit(EXIT_INVALID, "too_many_rows", f"{len(rows)} rows; one batch handles at most {MAX_ROWS}.")
49
+ bad = [i + 2 for i, r in enumerate(rows) if not (r.get("name") and (r.get("article") or r.get("case")))]
50
+ if bad:
51
+ raise CliExit(EXIT_INVALID, "missing_columns", f"Rows {bad[:10]} need name and article (or an existing case id).")
52
+ for r in rows:
53
+ r["_key"] = _key(r)
54
+ r["_offices"] = [o.strip().upper() for o in (r.get("jurisdictions") or "US").replace(",", ";").split(";") if o.strip().upper() in OFFICES] or ["US"]
55
+ for col in ("image", "cad"):
56
+ if r.get(col) and not Path(r[col]).expanduser().is_absolute():
57
+ r[col] = str((path.parent / r[col]).resolve())
58
+ return rows
59
+
60
+
61
+ def estimate(rows: list[dict], deep: bool, done: set[str]) -> int:
62
+ per = catalog.ACTION_CREDITS["screen.deep.batch" if deep and len(rows) >= 100 else ("screen.deep" if deep else "screen.quick")]
63
+ return per * sum(1 for r in rows if r["_key"] not in done)
64
+
65
+
66
+ def run_batch(csv_path: Path, *, deep: bool, max_credits: int, concurrency: int = 2, fetch: int = 8, figures_per: int = 3, out: Path | None = None) -> dict:
67
+ from .screening import STATE as SCREEN_STATE
68
+ from .screening import run_screen
69
+
70
+ rows = read_rows(csv_path)
71
+ state_path = csv_path.with_suffix(csv_path.suffix + ".state.json")
72
+ state = json.loads(state_path.read_text(encoding="utf-8")) if state_path.exists() else {"rows": {}}
73
+ done = {k for k, v in state["rows"].items() if v.get("status") == "done"}
74
+ est = estimate(rows, deep, done)
75
+ if est > max_credits:
76
+ raise CliExit(EXIT_BUDGET, "budget_exceeded", f"Estimated {est} credits for {len(rows) - len(done)} rows exceeds --max-credits {max_credits}.", data={"estimate": est, "rows": len(rows), "done": len(done)}, next_actions=[f"Re-run with --max-credits {est}", "Use quick screening (drop --deep) for a first pass"])
77
+ lock = threading.Lock()
78
+ SCREEN_STATE["quiet"] = True
79
+
80
+ def save() -> None:
81
+ tmp = state_path.with_suffix(".tmp")
82
+ tmp.write_text(json.dumps(state, indent=2, ensure_ascii=False), encoding="utf-8")
83
+ tmp.replace(state_path)
84
+
85
+ def one(row: dict) -> dict:
86
+ record = {"name": row["name"], "status": "running", "started": time.time(), "error": None}
87
+ try:
88
+ case_id = row.get("case") or (state["rows"].get(row["_key"]) or {}).get("case")
89
+ if not case_id:
90
+ case_id = api.case_create(row["name"], article=row.get("article") or None, article_zh=row.get("article_zh") or None, jurisdictions=row["_offices"])["id"]
91
+ api.case_update(case_id, status="batch")
92
+ record["case"] = case_id
93
+ with lock:
94
+ state["rows"][row["_key"]] = {**(state["rows"].get(row["_key"]) or {}), **record}
95
+ save()
96
+ if row.get("cad") and not api.case_show(case_id).get("original"):
97
+ api.import_model_file(case_id, row["cad"])
98
+ image = Path(row["image"]).expanduser() if row.get("image") else None
99
+ if image is not None and not image.exists():
100
+ raise CliExit(EXIT_INVALID, "not_found", f"Image not found: {image}")
101
+ data = run_screen(case_id, image=image, query=None, deep=deep, jurisdictions=row["_offices"], fetch=fetch, figures_per=figures_per, version=None, hires=2 if deep else 0)
102
+ ranked = data.get("ranked") or []
103
+ record.update({
104
+ "status": "done",
105
+ "candidates": data.get("candidates", 0),
106
+ "prior_art_added": len(data.get("added") or []),
107
+ "top": [{"number": r.get("number"), "shape": round((r.get("shape") or {}).get("score", r.get("shape_score", 0)) or 0, 3)} for r in ranked[:3]],
108
+ "version": data.get("version"),
109
+ "charged": data.get("charged", 0),
110
+ "source_failures": len(data.get("source_failures") or []),
111
+ })
112
+ except CliExit as exc:
113
+ record.update({"status": "failed", "error": f"{exc.kind}: {exc.message}"[:300]})
114
+ if exc.code == EXIT_BUDGET:
115
+ record["budget"] = True
116
+ except EngineError as exc:
117
+ record.update({"status": "failed", "error": f"{exc.code}: {exc}"[:300]})
118
+ except Exception as exc: # noqa: BLE001 - one bad row must not stop the batch
119
+ record.update({"status": "failed", "error": f"{type(exc).__name__}: {exc}"[:300]})
120
+ record["finished"] = time.time()
121
+ with lock:
122
+ state["rows"][row["_key"]] = {**(state["rows"].get(row["_key"]) or {}), **record}
123
+ save()
124
+ return record
125
+
126
+ todo = [r for r in rows if r["_key"] not in done]
127
+ spent = 0
128
+ stopped = False
129
+ with ThreadPoolExecutor(max_workers=max(1, min(concurrency, 6))) as pool:
130
+ futures = {pool.submit(one, r): r for r in todo}
131
+ for future in as_completed(futures):
132
+ rec = future.result()
133
+ spent += int(rec.get("charged") or 0)
134
+ if rec.get("budget"):
135
+ stopped = True
136
+ results = []
137
+ for r in rows:
138
+ rec = state["rows"].get(r["_key"]) or {"status": "pending"}
139
+ results.append({"name": r["name"], "case": rec.get("case"), "status": rec.get("status"), "candidates": rec.get("candidates"), "top": "; ".join(f"{t['number']} ({t['shape']})" for t in rec.get("top") or []), "error": rec.get("error")})
140
+ target = out or csv_path.with_name(csv_path.stem + "-results.csv")
141
+ with target.open("w", encoding="utf-8-sig", newline="") as fh:
142
+ writer = csv.DictWriter(fh, fieldnames=["name", "case", "status", "candidates", "top", "error"])
143
+ writer.writeheader()
144
+ writer.writerows(results)
145
+ counts = {s: sum(1 for x in results if x["status"] == s) for s in ("done", "failed", "pending")}
146
+ return {"rows": len(rows), "counts": counts, "estimate": est, "charged_this_run": spent, "stopped_on_budget": stopped, "results_csv": str(target), "state": str(state_path), "results": results}