@delta-infra/cli 1.0.73 → 1.0.74

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 (52) hide show
  1. package/README.md +422 -413
  2. package/checksums.txt +7 -5
  3. package/package.json +14 -5
  4. package/scripts/install-wizard.js +598 -602
  5. package/scripts/install.js +410 -410
  6. package/scripts/run.js +65 -65
  7. package/skills/antbo-ldm-guard/SKILL.md +54 -0
  8. package/skills/antbo-ldm-guard/agents/openai.yaml +4 -0
  9. package/skills/antbo-service/SKILL.md +74 -0
  10. package/skills/antbo-service/agents/openai.yaml +4 -0
  11. package/skills/antbo-service/references/operations.md +45 -0
  12. package/skills/delta-bo/SKILL.md +31 -0
  13. package/skills/delta-bo/agents/openai.yaml +4 -0
  14. package/skills/delta-bo/references/endpoints.md +263 -0
  15. package/skills/delta-sandbox/SKILL.md +356 -357
  16. package/skills/delta-sandbox/agents/openai.yaml +4 -0
  17. package/skills/delta-sandbox/references/commands.md +56 -56
  18. package/skills/delta-sandbox/references/lifecycle.md +92 -92
  19. package/skills/delta-sandbox/references/recipes.md +133 -133
  20. package/skills/delta-science/SKILL.md +166 -115
  21. package/skills/delta-science/agents/openai.yaml +4 -4
  22. package/skills/delta-science/references/cli-contract.md +84 -65
  23. package/skills/delta-science/references/operation-contracts.md +137 -0
  24. package/skills/delta-science/references/services.md +112 -85
  25. package/skills/delta-science/references/workflows.md +44 -34
  26. package/skills/delta-science/scripts/invoke.py +329 -189
  27. package/skills/delta-shared/SKILL.md +95 -96
  28. package/skills/delta-shared/agents/openai.yaml +4 -0
  29. package/skills/gsasii/SKILL.md +37 -0
  30. package/skills/gsasii/agents/openai.yaml +4 -0
  31. package/skills/gsasii/references/endpoints.md +130 -0
  32. package/skills/lammps/SKILL.md +34 -0
  33. package/skills/lammps/agents/openai.yaml +4 -0
  34. package/skills/lammps/references/endpoints.md +61 -0
  35. package/skills/ldm-bo/SKILL.md +30 -0
  36. package/skills/ldm-bo/agents/openai.yaml +4 -0
  37. package/skills/ldm-bo/references/endpoints.md +127 -0
  38. package/skills/manifest.json +17 -0
  39. package/skills/pubchem/SKILL.md +88 -0
  40. package/skills/pubchem/agents/openai.yaml +4 -0
  41. package/skills/pubchem/references/endpoints.md +122 -0
  42. package/skills/pubchem/references/operations.md +41 -0
  43. package/skills/pymatgen/SKILL.md +49 -0
  44. package/skills/pymatgen/agents/openai.yaml +4 -0
  45. package/skills/pymatgen/references/endpoints.md +68 -0
  46. package/skills/rdkit/SKILL.md +107 -0
  47. package/skills/rdkit/agents/openai.yaml +4 -0
  48. package/skills/rdkit/references/endpoints.md +133 -0
  49. package/skills/rdkit/references/operations.md +64 -0
  50. package/skills/synbo-service/SKILL.md +31 -0
  51. package/skills/synbo-service/agents/openai.yaml +4 -0
  52. package/skills/synbo-service/references/endpoints.md +157 -0
@@ -1,189 +1,329 @@
1
- #!/usr/bin/env python3
2
- """Invoke one Delta Science operation strictly through delta-cli."""
3
-
4
- from __future__ import annotations
5
-
6
- import argparse
7
- import json
8
- import os
9
- from pathlib import Path
10
- import shutil
11
- import subprocess
12
- import sys
13
- import time
14
- from typing import Any
15
-
16
-
17
- SKILL_DIR = Path(__file__).resolve().parents[1]
18
-
19
-
20
- def _json_object(raw: str | None, flag: str) -> dict[str, Any] | None:
21
- if raw is None:
22
- return None
23
- try:
24
- value = json.loads(raw)
25
- except json.JSONDecodeError as exc:
26
- raise ValueError(f"{flag} is not valid JSON: {exc}") from exc
27
- if not isinstance(value, dict):
28
- raise ValueError(f"{flag} must decode to a JSON object")
29
- return value
30
-
31
-
32
- def _runtime_config() -> dict[str, Any]:
33
- path = SKILL_DIR / "runtime.local.json"
34
- if not path.is_file():
35
- return {}
36
- try:
37
- value = json.loads(path.read_text(encoding="utf-8"))
38
- except (OSError, json.JSONDecodeError):
39
- return {}
40
- return value if isinstance(value, dict) else {}
41
-
42
-
43
- def _resolve_cli(explicit: str | None, runtime: dict[str, Any]) -> str:
44
- candidates: list[str | None] = [
45
- explicit,
46
- os.environ.get("DELTA_CLI_PATH"),
47
- runtime.get("cli_path"),
48
- shutil.which("delta-cli"),
49
- shutil.which("delta-cli.exe"),
50
- ]
51
- package_root = SKILL_DIR.parents[1]
52
- exe_name = "delta-cli.exe" if os.name == "nt" else "delta-cli"
53
- candidates.append(str(package_root / "bin" / exe_name))
54
- for candidate in candidates:
55
- if candidate and Path(candidate).is_file():
56
- return str(Path(candidate).resolve())
57
- raise FileNotFoundError(
58
- "delta-cli not found; install @delta-infra/cli, set DELTA_CLI_PATH, "
59
- "or add cli_path to delta-science/runtime.local.json"
60
- )
61
-
62
-
63
- def _unwrap(stdout: str) -> tuple[Any, int]:
64
- try:
65
- root = json.loads(stdout)
66
- except json.JSONDecodeError as exc:
67
- raise RuntimeError(f"CLI stdout is not JSON: {exc}") from exc
68
- if not isinstance(root, dict):
69
- raise RuntimeError("CLI stdout root is not an object")
70
- if root.get("ok") is not True:
71
- raise RuntimeError(json.dumps(root.get("error", root), ensure_ascii=False))
72
- if "data" not in root:
73
- raise RuntimeError("CLI success envelope is missing data")
74
-
75
- value: Any = root["data"]
76
- depth = 0
77
- while isinstance(value, dict):
78
- if "status_code" in value and "data" in value:
79
- status_code = value.get("status_code")
80
- if not isinstance(status_code, int) or not 200 <= status_code < 300:
81
- raise RuntimeError(json.dumps(value, ensure_ascii=False))
82
- value = value["data"]
83
- elif (
84
- "code" in value
85
- and "data" in value
86
- and set(value).intersection({"message", "msg"})
87
- ):
88
- code = value.get("code")
89
- if code not in (0, "0", None):
90
- raise RuntimeError(json.dumps(value, ensure_ascii=False))
91
- value = value["data"]
92
- else:
93
- break
94
- depth += 1
95
- if depth > 4:
96
- raise RuntimeError("too many nested service envelopes")
97
- if isinstance(value, dict) and value.get("valid") is False:
98
- raise RuntimeError(json.dumps({
99
- "valid": False,
100
- "error": value.get("error"),
101
- "warnings": value.get("warnings"),
102
- }, ensure_ascii=False))
103
- return value, depth
104
-
105
-
106
- def _parser() -> argparse.ArgumentParser:
107
- parser = argparse.ArgumentParser(description=__doc__)
108
- parser.add_argument("--tool", required=True)
109
- parser.add_argument("--endpoint", required=True)
110
- body = parser.add_mutually_exclusive_group()
111
- body.add_argument("--data-json")
112
- body.add_argument("--params-json")
113
- parser.add_argument("--cli")
114
- parser.add_argument("--science-base-url")
115
- parser.add_argument("--timeout", type=float, default=120.0)
116
- return parser
117
-
118
-
119
- def main() -> int:
120
- args = _parser().parse_args()
121
- started = time.perf_counter()
122
- stage = "arguments"
123
- try:
124
- data = _json_object(args.data_json, "--data-json")
125
- params = _json_object(args.params_json, "--params-json")
126
- runtime = _runtime_config()
127
- stage = "cli-resolution"
128
- cli = _resolve_cli(args.cli, runtime)
129
-
130
- command = [
131
- cli, "science", "invoke",
132
- "--tool", args.tool,
133
- "--endpoint", args.endpoint,
134
- ]
135
- if data is not None:
136
- command.extend(["--data", json.dumps(data, ensure_ascii=False, separators=(",", ":"))])
137
- if params is not None:
138
- command.extend(["--params", json.dumps(params, ensure_ascii=False, separators=(",", ":"))])
139
-
140
- env = os.environ.copy()
141
- base_url = args.science_base_url or runtime.get("science_base_url")
142
- if base_url:
143
- env["DELTA_INFRA_SCIENCE_BASE_URL"] = str(base_url)
144
-
145
- stage = "delta-cli"
146
- completed = subprocess.run(
147
- command,
148
- capture_output=True,
149
- text=True,
150
- encoding="utf-8",
151
- errors="replace",
152
- env=env,
153
- timeout=args.timeout,
154
- check=False,
155
- )
156
- if completed.returncode != 0:
157
- detail = completed.stderr.strip() or completed.stdout.strip()
158
- raise RuntimeError(f"exit_code={completed.returncode}: {detail}")
159
- stage = "response-validation"
160
- native, depth = _unwrap(completed.stdout)
161
- result = {
162
- "ok": True,
163
- "transport": "delta-cli",
164
- "tool": args.tool,
165
- "endpoint": args.endpoint,
166
- "elapsed_seconds": round(time.perf_counter() - started, 3),
167
- "envelope_depth": depth,
168
- "native": native,
169
- }
170
- print(json.dumps(result, ensure_ascii=False))
171
- return 0
172
- except subprocess.TimeoutExpired:
173
- error = f"timeout after {args.timeout}s"
174
- except (OSError, ValueError, RuntimeError) as exc:
175
- error = str(exc)
176
- print(json.dumps({
177
- "ok": False,
178
- "transport": "delta-cli",
179
- "tool": args.tool,
180
- "endpoint": args.endpoint,
181
- "stage": stage,
182
- "elapsed_seconds": round(time.perf_counter() - started, 3),
183
- "error": error[:2000],
184
- }, ensure_ascii=False))
185
- return 1
186
-
187
-
188
- if __name__ == "__main__":
189
- sys.exit(main())
1
+ #!/usr/bin/env python3
2
+ """Invoke one Delta Science operation strictly through delta-cli."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+ import shutil
11
+ import subprocess
12
+ import sys
13
+ import time
14
+ from typing import Any
15
+ from urllib.parse import urlsplit
16
+
17
+
18
+ SKILL_DIR = Path(__file__).resolve().parents[1]
19
+
20
+ LEGACY_TOOL_ALIASES = {
21
+ "antbo": "antbo-service",
22
+ "synbo": "synbo-service",
23
+ }
24
+
25
+ LEGACY_ENDPOINTS = {
26
+ "pubchem": {
27
+ "health": "chem_pubchem_health",
28
+ "compound-resolve-cids": "chem_pubchem_compound_resolve-cids",
29
+ "compound-properties": "chem_pubchem_compound_properties",
30
+ "compound-synonyms": "chem_pubchem_compound_synonyms",
31
+ "compound-summary": "chem_pubchem_compound_summary",
32
+ "compound-batch-summary": "chem_pubchem_compound_batch-summary",
33
+ },
34
+ "rdkit": {
35
+ "health": "chem_rdkit_health",
36
+ "parse": "chem_rdkit_parse",
37
+ "descriptors": "chem_rdkit_descriptors",
38
+ "batch-descriptors": "chem_rdkit_batch-descriptors",
39
+ "batch-parse-describe": "chem_rdkit_batch-parse-describe",
40
+ "render": "chem_rdkit_render",
41
+ "fingerprint": "chem_rdkit_fingerprint",
42
+ "similarity": "chem_rdkit_similarity",
43
+ "similarity-matrix": "chem_rdkit_similarity-matrix",
44
+ "substructure": "chem_rdkit_substructure",
45
+ },
46
+ "pymatgen": {
47
+ "health": "chem_pymatgen_health",
48
+ "composition-parse": "chem_pymatgen_composition_parse",
49
+ "structure-parse": "chem_pymatgen_structure_parse",
50
+ "structure-summary": "chem_pymatgen_structure_summary",
51
+ "structure-convert": "chem_pymatgen_structure_convert",
52
+ "structure-symmetry": "chem_pymatgen_structure_symmetry",
53
+ },
54
+ "gsasii": {
55
+ "health": "chem_gsasii_health",
56
+ "powder-simulate": "chem_gsasii_powder_simulate",
57
+ "powder-refine": "chem_gsasii_powder_refine",
58
+ },
59
+ "lammps": {
60
+ "health": "chem_lammps_health",
61
+ "lj-melt-example": "chem_lammps_examples_lj-melt",
62
+ "run": "chem_lammps_run",
63
+ },
64
+ "delta-bo": {
65
+ "commands": "chem_delta-bo_commands",
66
+ "generate": "chem_delta-bo_generate",
67
+ },
68
+ "ldm-bo": {
69
+ "health": "chem_ldm-bo_health",
70
+ "recommend": "chem_ldm-bo_recommend",
71
+ "trajectory": "chem_ldm-bo_trajectory",
72
+ },
73
+ "synbo": {
74
+ "health": "chem_synbo_health",
75
+ "initialize": "chem_synbo_initialize",
76
+ "optimize": "chem_synbo_optimize",
77
+ },
78
+ "antbo": {
79
+ "health": "biology_antbo_health",
80
+ "run-default-job": "biology_antbo_run_default_job",
81
+ "run": "biology_antbo_run",
82
+ "log": "biology_antbo_log",
83
+ "jobs": "biology_antbo_jobs",
84
+ "stop": "biology_antbo_stop",
85
+ },
86
+ }
87
+
88
+
89
+ def _json_object(raw: str | None, flag: str) -> dict[str, Any] | None:
90
+ if raw is None:
91
+ return None
92
+ try:
93
+ value = json.loads(raw)
94
+ except json.JSONDecodeError as exc:
95
+ raise ValueError(f"{flag} is not valid JSON: {exc}") from exc
96
+ if not isinstance(value, dict):
97
+ raise ValueError(f"{flag} must decode to a JSON object")
98
+ return value
99
+
100
+
101
+ def _runtime_config() -> dict[str, Any]:
102
+ path = SKILL_DIR / "runtime.local.json"
103
+ if not path.is_file():
104
+ return {}
105
+ try:
106
+ value = json.loads(path.read_text(encoding="utf-8"))
107
+ except (OSError, json.JSONDecodeError):
108
+ return {}
109
+ return value if isinstance(value, dict) else {}
110
+
111
+
112
+ def _resolve_cli(explicit: str | None, runtime: dict[str, Any]) -> str:
113
+ candidates: list[str | None] = [
114
+ explicit,
115
+ os.environ.get("DELTA_CLI_PATH"),
116
+ runtime.get("cli_path"),
117
+ shutil.which("delta-cli"),
118
+ shutil.which("delta-cli.exe"),
119
+ ]
120
+ package_root = SKILL_DIR.parents[1]
121
+ exe_name = "delta-cli.exe" if os.name == "nt" else "delta-cli"
122
+ candidates.append(str(package_root / "bin" / exe_name))
123
+ for candidate in candidates:
124
+ if candidate and Path(candidate).is_file():
125
+ return str(Path(candidate).resolve())
126
+ raise FileNotFoundError(
127
+ "delta-cli not found; install @delta-infra/cli, set DELTA_CLI_PATH, "
128
+ "or add cli_path to delta-science/runtime.local.json"
129
+ )
130
+
131
+
132
+ def _cli_science_base_url(cli: str, env: dict[str, str], timeout: float) -> str:
133
+ explicit = env.get("DELTA_INFRA_SCIENCE_BASE_URL", "").strip()
134
+ if explicit:
135
+ return explicit
136
+ completed = subprocess.run(
137
+ [cli, "config", "show"],
138
+ capture_output=True,
139
+ text=True,
140
+ encoding="utf-8",
141
+ errors="replace",
142
+ env=env,
143
+ timeout=min(timeout, 15.0),
144
+ check=False,
145
+ )
146
+ if completed.returncode != 0:
147
+ return ""
148
+ try:
149
+ root = json.loads(completed.stdout)
150
+ value = root.get("data", {}).get("science_base_url", "")
151
+ except (AttributeError, json.JSONDecodeError):
152
+ return ""
153
+ return str(value).strip()
154
+
155
+
156
+ def _catalog_profile(base_url: str, requested: str) -> str:
157
+ if requested != "auto":
158
+ return requested
159
+ try:
160
+ path = urlsplit(base_url).path.rstrip("/").lower()
161
+ except ValueError:
162
+ path = ""
163
+ return "legacy" if path.endswith("/science_tool") else "canonical"
164
+
165
+
166
+ def _resolve_catalog_names(tool: str, endpoint: str, profile: str) -> tuple[str, str]:
167
+ if profile == "canonical":
168
+ return tool, endpoint
169
+ canonical_tool = tool.removesuffix("-service") if tool in {"antbo-service", "synbo-service"} else tool
170
+ resolved_tool = LEGACY_TOOL_ALIASES.get(canonical_tool, canonical_tool)
171
+ if endpoint.startswith(("chem_", "biology_")):
172
+ return resolved_tool, endpoint
173
+ resolved_endpoint = LEGACY_ENDPOINTS.get(canonical_tool, {}).get(endpoint)
174
+ if not resolved_endpoint:
175
+ raise ValueError(
176
+ f"no legacy science_tool mapping for {canonical_tool}/{endpoint}"
177
+ )
178
+ return resolved_tool, resolved_endpoint
179
+
180
+
181
+ def _unwrap(stdout: str) -> tuple[Any, int]:
182
+ try:
183
+ root = json.loads(stdout)
184
+ except json.JSONDecodeError as exc:
185
+ raise RuntimeError(f"CLI stdout is not JSON: {exc}") from exc
186
+ if not isinstance(root, dict):
187
+ raise RuntimeError("CLI stdout root is not an object")
188
+ if root.get("ok") is not True:
189
+ raise RuntimeError(json.dumps(root.get("error", root), ensure_ascii=False))
190
+ if "data" not in root:
191
+ raise RuntimeError("CLI success envelope is missing data")
192
+
193
+ value: Any = root["data"]
194
+ depth = 0
195
+ while isinstance(value, dict):
196
+ if "status_code" in value and "data" in value:
197
+ status_code = value.get("status_code")
198
+ if not isinstance(status_code, int) or not 200 <= status_code < 300:
199
+ raise RuntimeError(json.dumps(value, ensure_ascii=False))
200
+ value = value["data"]
201
+ elif (
202
+ "code" in value
203
+ and "data" in value
204
+ and set(value).intersection({"message", "msg"})
205
+ ):
206
+ code = value.get("code")
207
+ if code not in (0, "0", None):
208
+ raise RuntimeError(json.dumps(value, ensure_ascii=False))
209
+ value = value["data"]
210
+ else:
211
+ break
212
+ depth += 1
213
+ if depth > 4:
214
+ raise RuntimeError("too many nested service envelopes")
215
+ if isinstance(value, dict) and value.get("valid") is False:
216
+ raise RuntimeError(json.dumps({
217
+ "valid": False,
218
+ "error": value.get("error"),
219
+ "warnings": value.get("warnings"),
220
+ }, ensure_ascii=False))
221
+ return value, depth
222
+
223
+
224
+ def _parser() -> argparse.ArgumentParser:
225
+ parser = argparse.ArgumentParser(description=__doc__)
226
+ parser.add_argument("--tool", required=True)
227
+ parser.add_argument("--endpoint", required=True)
228
+ body = parser.add_mutually_exclusive_group()
229
+ body.add_argument("--data-json")
230
+ body.add_argument("--params-json")
231
+ parser.add_argument("--cli")
232
+ parser.add_argument("--science-base-url")
233
+ parser.add_argument(
234
+ "--catalog-profile",
235
+ choices=("auto", "canonical", "legacy"),
236
+ default="auto",
237
+ )
238
+ parser.add_argument("--timeout", type=float, default=120.0)
239
+ return parser
240
+
241
+
242
+ def main() -> int:
243
+ args = _parser().parse_args()
244
+ started = time.perf_counter()
245
+ stage = "arguments"
246
+ profile = "unknown"
247
+ resolved_tool = args.tool
248
+ resolved_endpoint = args.endpoint
249
+ try:
250
+ data = _json_object(args.data_json, "--data-json")
251
+ params = _json_object(args.params_json, "--params-json")
252
+ runtime = _runtime_config()
253
+ stage = "cli-resolution"
254
+ cli = _resolve_cli(args.cli, runtime)
255
+
256
+ env = os.environ.copy()
257
+ base_url = args.science_base_url or runtime.get("science_base_url")
258
+ if base_url:
259
+ env["DELTA_INFRA_SCIENCE_BASE_URL"] = str(base_url)
260
+ configured_base_url = _cli_science_base_url(cli, env, args.timeout)
261
+ requested_profile = str(runtime.get("catalog_profile", args.catalog_profile))
262
+ if requested_profile not in {"auto", "canonical", "legacy"}:
263
+ raise ValueError("catalog_profile must be auto, canonical, or legacy")
264
+ profile = _catalog_profile(configured_base_url, requested_profile)
265
+ resolved_tool, resolved_endpoint = _resolve_catalog_names(
266
+ args.tool, args.endpoint, profile
267
+ )
268
+
269
+ command = [
270
+ cli, "science", "invoke",
271
+ "--tool", resolved_tool,
272
+ "--endpoint", resolved_endpoint,
273
+ ]
274
+ if data is not None:
275
+ command.extend(["--data", json.dumps(data, ensure_ascii=False, separators=(",", ":"))])
276
+ if params is not None:
277
+ command.extend(["--params", json.dumps(params, ensure_ascii=False, separators=(",", ":"))])
278
+
279
+ stage = "delta-cli"
280
+ completed = subprocess.run(
281
+ command,
282
+ capture_output=True,
283
+ text=True,
284
+ encoding="utf-8",
285
+ errors="replace",
286
+ env=env,
287
+ timeout=args.timeout,
288
+ check=False,
289
+ )
290
+ if completed.returncode != 0:
291
+ detail = completed.stderr.strip() or completed.stdout.strip()
292
+ raise RuntimeError(f"exit_code={completed.returncode}: {detail}")
293
+ stage = "response-validation"
294
+ native, depth = _unwrap(completed.stdout)
295
+ result = {
296
+ "ok": True,
297
+ "transport": "delta-cli",
298
+ "tool": args.tool,
299
+ "endpoint": args.endpoint,
300
+ "resolved_tool": resolved_tool,
301
+ "resolved_endpoint": resolved_endpoint,
302
+ "catalog_profile": profile,
303
+ "elapsed_seconds": round(time.perf_counter() - started, 3),
304
+ "envelope_depth": depth,
305
+ "native": native,
306
+ }
307
+ print(json.dumps(result, ensure_ascii=False))
308
+ return 0
309
+ except subprocess.TimeoutExpired:
310
+ error = f"timeout after {args.timeout}s"
311
+ except (OSError, ValueError, RuntimeError) as exc:
312
+ error = str(exc)
313
+ print(json.dumps({
314
+ "ok": False,
315
+ "transport": "delta-cli",
316
+ "tool": args.tool,
317
+ "endpoint": args.endpoint,
318
+ "resolved_tool": resolved_tool,
319
+ "resolved_endpoint": resolved_endpoint,
320
+ "catalog_profile": profile,
321
+ "stage": stage,
322
+ "elapsed_seconds": round(time.perf_counter() - started, 3),
323
+ "error": error[:2000],
324
+ }, ensure_ascii=False))
325
+ return 1
326
+
327
+
328
+ if __name__ == "__main__":
329
+ sys.exit(main())