@delta-infra/cli 1.0.96 → 1.0.98

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 (36) hide show
  1. package/README.md +473 -471
  2. package/checksums.txt +5 -5
  3. package/package.json +1 -1
  4. package/scripts/install-wizard.js +701 -701
  5. package/scripts/install.js +442 -442
  6. package/scripts/run.js +65 -65
  7. package/skills/delta-sandbox/SKILL.md +344 -345
  8. package/skills/delta-sandbox/agents/openai.yaml +4 -4
  9. package/skills/delta-sandbox/references/commands.md +58 -58
  10. package/skills/delta-sandbox/references/lifecycle.md +100 -98
  11. package/skills/delta-sandbox/references/recipes.md +138 -138
  12. package/skills/delta-science/AGENTS.md +46 -49
  13. package/skills/delta-science/SKILL.md +147 -136
  14. package/skills/delta-science/agents/openai.yaml +4 -4
  15. package/skills/delta-science/references/antbo.md +100 -100
  16. package/skills/delta-science/references/delta-bo.md +89 -89
  17. package/skills/delta-science/references/gsasii.md +116 -116
  18. package/skills/delta-science/references/lammps.md +66 -66
  19. package/skills/delta-science/references/ldm-bo.md +129 -129
  20. package/skills/delta-science/references/materials-design.md +109 -0
  21. package/skills/delta-science/references/pubchem.md +126 -126
  22. package/skills/delta-science/references/pymatgen.md +68 -68
  23. package/skills/delta-science/references/qe.md +123 -0
  24. package/skills/delta-science/references/rdkit.md +138 -138
  25. package/skills/delta-science/references/strbo.md +87 -87
  26. package/skills/delta-science/references/synbo.md +198 -198
  27. package/skills/delta-science/scripts/catalog.py +92 -0
  28. package/skills/delta-science/scripts/generated_catalog.py +69 -0
  29. package/skills/delta-science/scripts/invoke.py +346 -0
  30. package/skills/delta-shared/SKILL.md +97 -97
  31. package/skills/delta-shared/agents/openai.yaml +4 -4
  32. package/skills/manifest.json +19 -19
  33. package/skills/delta-science/references/cli-contract.md +0 -53
  34. package/skills/delta-science/references/ldm.md +0 -73
  35. package/skills/delta-science/references/routing-index.md +0 -36
  36. package/skills/delta-science/references/workflows.md +0 -61
@@ -0,0 +1,69 @@
1
+ """Generated Science catalog aliases. Do not edit by hand."""
2
+
3
+ LEGACY_TOOL_ALIASES = {
4
+ "synbo": "synbo-service",
5
+ "antbo": "antbo-service",
6
+ }
7
+
8
+ LEGACY_ENDPOINTS = {
9
+ "pubchem": {
10
+ "health": "chem_pubchem_health",
11
+ "compound-resolve-cids": "chem_pubchem_compound_resolve-cids",
12
+ "compound-properties": "chem_pubchem_compound_properties",
13
+ "compound-synonyms": "chem_pubchem_compound_synonyms",
14
+ "compound-summary": "chem_pubchem_compound_summary",
15
+ "compound-batch-summary": "chem_pubchem_compound_batch-summary",
16
+ },
17
+ "rdkit": {
18
+ "health": "chem_rdkit_health",
19
+ "parse": "chem_rdkit_parse",
20
+ "descriptors": "chem_rdkit_descriptors",
21
+ "batch-descriptors": "chem_rdkit_batch-descriptors",
22
+ "batch-parse-describe": "chem_rdkit_batch-parse-describe",
23
+ "render": "chem_rdkit_render",
24
+ "fingerprint": "chem_rdkit_fingerprint",
25
+ "similarity": "chem_rdkit_similarity",
26
+ "similarity-matrix": "chem_rdkit_similarity-matrix",
27
+ "substructure": "chem_rdkit_substructure",
28
+ },
29
+ "pymatgen": {
30
+ "health": "chem_pymatgen_health",
31
+ "composition-parse": "chem_pymatgen_composition_parse",
32
+ "structure-parse": "chem_pymatgen_structure_parse",
33
+ "structure-summary": "chem_pymatgen_structure_summary",
34
+ "structure-convert": "chem_pymatgen_structure_convert",
35
+ "structure-symmetry": "chem_pymatgen_structure_symmetry",
36
+ },
37
+ "gsasii": {
38
+ "health": "chem_gsasii_health",
39
+ "powder-simulate": "chem_gsasii_powder_simulate",
40
+ "powder-refine": "chem_gsasii_powder_refine",
41
+ },
42
+ "lammps": {
43
+ "health": "chem_lammps_health",
44
+ "lj-melt-example": "chem_lammps_examples_lj-melt",
45
+ "run": "chem_lammps_run",
46
+ },
47
+ "delta-bo": {
48
+ "commands": "chem_delta-bo_commands",
49
+ "generate": "chem_delta-bo_generate",
50
+ },
51
+ "ldm-bo": {
52
+ "health": "chem_ldm-bo_health",
53
+ "recommend": "chem_ldm-bo_recommend",
54
+ "trajectory": "chem_ldm-bo_trajectory",
55
+ },
56
+ "synbo": {
57
+ "health": "chem_synbo_health",
58
+ "initialize": "chem_synbo_initialize",
59
+ "optimize": "chem_synbo_optimize",
60
+ },
61
+ "antbo": {
62
+ "health": "biology_antbo_health",
63
+ "run-default-job": "biology_antbo_run_default_job",
64
+ "run": "biology_antbo_run",
65
+ "log": "biology_antbo_log",
66
+ "jobs": "biology_antbo_jobs",
67
+ "stop": "biology_antbo_stop",
68
+ },
69
+ }
@@ -0,0 +1,346 @@
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
+ SKILL_DIR = Path(__file__).resolve().parents[1]
17
+ _CLI_ERROR_TYPES = {
18
+ 2: "validation",
19
+ 3: "auth",
20
+ 4: "permission",
21
+ 5: "not_found",
22
+ 6: "network",
23
+ 7: "api",
24
+ 10: "internal",
25
+ }
26
+
27
+
28
+ def _json_object(raw: str | None, flag: str) -> dict[str, Any] | None:
29
+ if raw is None:
30
+ return None
31
+ try:
32
+ value = json.loads(raw)
33
+ except json.JSONDecodeError as exc:
34
+ raise ValueError(f"{flag} is not valid JSON: {exc}") from exc
35
+ if not isinstance(value, dict):
36
+ raise ValueError(f"{flag} must decode to a JSON object")
37
+ return value
38
+
39
+
40
+ def _normalize_synbo_payload(
41
+ tool: str,
42
+ endpoint: str,
43
+ data: dict[str, Any] | None,
44
+ ) -> tuple[dict[str, Any] | None, dict[str, Any] | None]:
45
+ """Normalize SynBO's discrete condition contract before any remote request."""
46
+ canonical_tool = tool.removesuffix("-service")
47
+ if canonical_tool != "synbo" or endpoint not in {"initialize", "optimize"}:
48
+ return data, None
49
+ if data is None:
50
+ raise ValueError(f"synbo/{endpoint} requires --data-json")
51
+
52
+ condition_dict = data.get("condition_dict")
53
+ if not isinstance(condition_dict, dict) or not condition_dict:
54
+ raise ValueError("SynBO condition_dict must be a non-empty object")
55
+
56
+ normalized_conditions: dict[str, list[str]] = {}
57
+ for name, raw_values in condition_dict.items():
58
+ if not isinstance(raw_values, list) or not raw_values:
59
+ raise ValueError(
60
+ f"SynBO condition_dict.{name} must be a non-empty explicit list; "
61
+ "range objects are not supported"
62
+ )
63
+ values = [str(value) for value in raw_values]
64
+ if any(value == "" for value in values):
65
+ raise ValueError(f"SynBO condition_dict.{name} contains an empty value")
66
+ if len(set(values)) != len(values):
67
+ raise ValueError(
68
+ f"SynBO condition_dict.{name} contains duplicate values after "
69
+ "string normalization"
70
+ )
71
+ normalized_conditions[str(name)] = values
72
+
73
+ normalized = dict(data)
74
+ normalized["condition_dict"] = normalized_conditions
75
+ normalization = {
76
+ "condition_values": "strings",
77
+ "condition_space_size": _condition_space_size(normalized_conditions),
78
+ }
79
+
80
+ if endpoint == "initialize":
81
+ return normalized, normalization
82
+
83
+ metrics = normalized.get("opt_metrics")
84
+ if not isinstance(metrics, list) or not metrics:
85
+ raise ValueError("SynBO optimize requires a non-empty opt_metrics list")
86
+ metric_names = [str(metric) for metric in metrics]
87
+ normalized["opt_metrics"] = metric_names
88
+
89
+ previous_results = normalized.get("previous_results")
90
+ if not isinstance(previous_results, list) or not previous_results:
91
+ raise ValueError("SynBO optimize requires non-empty previous_results")
92
+
93
+ normalized_rows: list[dict[str, Any]] = []
94
+ for index, raw_row in enumerate(previous_results):
95
+ if not isinstance(raw_row, dict):
96
+ raise ValueError(f"SynBO previous_results[{index}] must be an object")
97
+ row = dict(raw_row)
98
+ for condition_name, allowed_values in normalized_conditions.items():
99
+ if condition_name not in row:
100
+ raise ValueError(
101
+ f"SynBO previous_results[{index}] is missing condition "
102
+ f"{condition_name!r}"
103
+ )
104
+ value = str(row[condition_name])
105
+ if value not in allowed_values:
106
+ raise ValueError(
107
+ f"SynBO previous_results[{index}].{condition_name}={value!r} "
108
+ "is not present in condition_dict"
109
+ )
110
+ row[condition_name] = value
111
+ for metric_name in metric_names:
112
+ metric_value = row.get(metric_name)
113
+ if (
114
+ metric_name not in row
115
+ or isinstance(metric_value, bool)
116
+ or not isinstance(metric_value, (int, float))
117
+ ):
118
+ raise ValueError(
119
+ f"SynBO previous_results[{index}].{metric_name} must be numeric"
120
+ )
121
+ normalized_rows.append(row)
122
+
123
+ normalized["previous_results"] = normalized_rows
124
+ normalized.setdefault("accuracy", "tiny")
125
+ normalized.setdefault("device", "cpu")
126
+ normalized.setdefault("surrogate_model", "RF")
127
+ normalized.setdefault("acq_func", "UCB")
128
+ normalization["defaults"] = {
129
+ key: normalized[key]
130
+ for key in ("accuracy", "device", "surrogate_model", "acq_func")
131
+ }
132
+ return normalized, normalization
133
+
134
+
135
+ def _condition_space_size(condition_dict: dict[str, list[str]]) -> int:
136
+ size = 1
137
+ for values in condition_dict.values():
138
+ size *= len(values)
139
+ return size
140
+
141
+
142
+ def _runtime_config() -> dict[str, Any]:
143
+ path = SKILL_DIR / "runtime.local.json"
144
+ if not path.is_file():
145
+ return {}
146
+ try:
147
+ value = json.loads(path.read_text(encoding="utf-8"))
148
+ except (OSError, json.JSONDecodeError):
149
+ return {}
150
+ return value if isinstance(value, dict) else {}
151
+
152
+
153
+ def _resolve_cli(explicit: str | None, runtime: dict[str, Any]) -> str:
154
+ candidates: list[str | None] = [
155
+ explicit,
156
+ os.environ.get("DELTA_CLI_PATH"),
157
+ runtime.get("cli_path"),
158
+ shutil.which("delta-cli"),
159
+ shutil.which("delta-cli.exe"),
160
+ ]
161
+ package_root = SKILL_DIR.parents[1]
162
+ exe_name = "delta-cli.exe" if os.name == "nt" else "delta-cli"
163
+ candidates.append(str(package_root / "bin" / exe_name))
164
+ for candidate in candidates:
165
+ if candidate and Path(candidate).is_file():
166
+ return str(Path(candidate).resolve())
167
+ raise FileNotFoundError(
168
+ "delta-cli not found; install @delta-infra/cli, set DELTA_CLI_PATH, "
169
+ "or add cli_path to delta-science/runtime.local.json"
170
+ )
171
+
172
+
173
+ def _unwrap(stdout: str) -> tuple[Any, int]:
174
+ try:
175
+ root = json.loads(stdout)
176
+ except json.JSONDecodeError as exc:
177
+ raise RuntimeError(f"CLI stdout is not JSON: {exc}") from exc
178
+ if not isinstance(root, dict):
179
+ raise RuntimeError("CLI stdout root is not an object")
180
+ if root.get("ok") is not True:
181
+ raise RuntimeError(json.dumps(root.get("error", root), ensure_ascii=False))
182
+ if "data" not in root:
183
+ raise RuntimeError("CLI success envelope is missing data")
184
+
185
+ value: Any = root["data"]
186
+ depth = 0
187
+ while isinstance(value, dict):
188
+ if "status_code" in value and "data" in value:
189
+ status_code = value.get("status_code")
190
+ if not isinstance(status_code, int) or not 200 <= status_code < 300:
191
+ raise RuntimeError(json.dumps(value, ensure_ascii=False))
192
+ value = value["data"]
193
+ elif (
194
+ "code" in value
195
+ and "data" in value
196
+ and set(value).intersection({"message", "msg"})
197
+ ):
198
+ code = value.get("code")
199
+ if code not in (0, "0", None):
200
+ raise RuntimeError(json.dumps(value, ensure_ascii=False))
201
+ value = value["data"]
202
+ else:
203
+ break
204
+ depth += 1
205
+ if depth > 4:
206
+ raise RuntimeError("too many nested service envelopes")
207
+ if isinstance(value, dict) and value.get("valid") is False:
208
+ raise RuntimeError(json.dumps({
209
+ "valid": False,
210
+ "error": value.get("error"),
211
+ "warnings": value.get("warnings"),
212
+ }, ensure_ascii=False))
213
+ return value, depth
214
+
215
+
216
+ def _validate_native(tool: str, endpoint: str, native: Any) -> None:
217
+ canonical_tool = tool.removesuffix("-service")
218
+ if canonical_tool == "synbo" and endpoint == "optimize":
219
+ if not isinstance(native, dict):
220
+ raise RuntimeError("SynBO optimize returned a non-object result")
221
+ recommendations = native.get("recommendations")
222
+ if not isinstance(recommendations, list) or not recommendations:
223
+ raise RuntimeError("SynBO optimize returned no recommendations")
224
+
225
+
226
+ def _parse_cli_error(completed: subprocess.CompletedProcess[str]) -> tuple[str, str]:
227
+ detail = completed.stderr.strip() or completed.stdout.strip()
228
+ error_type = _CLI_ERROR_TYPES.get(completed.returncode, "cli")
229
+ try:
230
+ envelope = json.loads(detail)
231
+ error = envelope.get("error", {}) if isinstance(envelope, dict) else {}
232
+ if isinstance(error, dict):
233
+ error_type = str(error.get("type") or error_type)
234
+ message = error.get("message")
235
+ if message:
236
+ detail = str(message)
237
+ except (AttributeError, json.JSONDecodeError):
238
+ pass
239
+ return error_type, detail
240
+
241
+
242
+ def _parser() -> argparse.ArgumentParser:
243
+ parser = argparse.ArgumentParser(description=__doc__)
244
+ parser.add_argument("--tool", required=True)
245
+ parser.add_argument("--endpoint", required=True)
246
+ body = parser.add_mutually_exclusive_group()
247
+ body.add_argument("--data-json")
248
+ body.add_argument("--params-json")
249
+ parser.add_argument("--cli")
250
+ parser.add_argument("--science-base-url")
251
+ parser.add_argument("--timeout", type=float, default=120.0)
252
+ return parser
253
+
254
+
255
+ def main() -> int:
256
+ args = _parser().parse_args()
257
+ started = time.perf_counter()
258
+ stage = "arguments"
259
+ cli_exit_code: int | None = None
260
+ error_type = "unknown"
261
+ normalization: dict[str, Any] | None = None
262
+ try:
263
+ data = _json_object(args.data_json, "--data-json")
264
+ params = _json_object(args.params_json, "--params-json")
265
+ data, normalization = _normalize_synbo_payload(
266
+ args.tool, args.endpoint, data
267
+ )
268
+ runtime = _runtime_config()
269
+ stage = "cli-resolution"
270
+ cli = _resolve_cli(args.cli, runtime)
271
+
272
+ env = os.environ.copy()
273
+ base_url = args.science_base_url or runtime.get("science_base_url")
274
+ if base_url:
275
+ env["DELTA_INFRA_SCIENCE_BASE_URL"] = str(base_url)
276
+
277
+ command = [
278
+ cli, "science", "invoke",
279
+ "--tool", args.tool,
280
+ "--endpoint", args.endpoint,
281
+ ]
282
+ if data is not None:
283
+ command.extend(["--data", json.dumps(data, ensure_ascii=False, separators=(",", ":"))])
284
+ if params is not None:
285
+ command.extend(["--params", json.dumps(params, ensure_ascii=False, separators=(",", ":"))])
286
+
287
+ stage = "delta-cli"
288
+ completed = subprocess.run(
289
+ command,
290
+ capture_output=True,
291
+ text=True,
292
+ encoding="utf-8",
293
+ errors="replace",
294
+ env=env,
295
+ timeout=args.timeout,
296
+ check=False,
297
+ )
298
+ if completed.returncode != 0:
299
+ cli_exit_code = completed.returncode
300
+ error_type, detail = _parse_cli_error(completed)
301
+ raise RuntimeError(f"exit_code={completed.returncode}: {detail}")
302
+ stage = "response-validation"
303
+ native, depth = _unwrap(completed.stdout)
304
+ _validate_native(args.tool, args.endpoint, native)
305
+ result = {
306
+ "ok": True,
307
+ "transport": "delta-cli",
308
+ "tool": args.tool,
309
+ "endpoint": args.endpoint,
310
+ "elapsed_seconds": round(time.perf_counter() - started, 3),
311
+ "envelope_depth": depth,
312
+ "native": native,
313
+ }
314
+ if normalization is not None:
315
+ result["request_normalization"] = normalization
316
+ print(json.dumps(result, ensure_ascii=False))
317
+ return 0
318
+ except subprocess.TimeoutExpired:
319
+ error_type = "timeout"
320
+ error = f"timeout after {args.timeout}s"
321
+ except ValueError as exc:
322
+ error_type = "validation"
323
+ error = str(exc)
324
+ except OSError as exc:
325
+ error_type = "cli_resolution"
326
+ error = str(exc)
327
+ except RuntimeError as exc:
328
+ if error_type == "unknown":
329
+ error_type = "response_validation"
330
+ error = str(exc)
331
+ print(json.dumps({
332
+ "ok": False,
333
+ "transport": "delta-cli",
334
+ "tool": args.tool,
335
+ "endpoint": args.endpoint,
336
+ "stage": stage,
337
+ "error_type": error_type,
338
+ "cli_exit_code": cli_exit_code,
339
+ "elapsed_seconds": round(time.perf_counter() - started, 3),
340
+ "error": error[:2000],
341
+ }, ensure_ascii=False))
342
+ return 1
343
+
344
+
345
+ if __name__ == "__main__":
346
+ sys.exit(main())
@@ -1,97 +1,97 @@
1
- ---
2
- name: delta-shared
3
- description: "delta-cli 全局共享规则:配置/认证检查、安全红线、错误处理、sandbox 销毁。"
4
- ---
5
-
6
- # delta-cli 共享规则
7
-
8
- 指导 AI 使用 `delta-cli` 的通用规则。核心原则:**只检查、不猜测、不泄露密钥**。
9
-
10
- ## 执行任何操作之前
11
-
12
- 1. 确认服务端地址:
13
- ```bash
14
- delta-cli config show
15
- ```
16
- 2. 确认认证状态:
17
- ```bash
18
- delta-cli auth status
19
- ```
20
-
21
- - 若 `auth status` 显示 `method: token`,**直接执行目标命令**,不要重复登录或初始化配置。
22
- - 若显示 `method: none`,或操作返回 `error.type: auth` / `permission`,才进入下面的“补全配置”流程。
23
-
24
- ## 补全配置
25
-
26
- 缺少服务端地址或凭证时,**向用户询问一次**,然后用 flag 初始化:
27
-
28
- ```bash
29
- delta-cli config init --base-url <server-url> --token <token>
30
- ```
31
-
32
- 不要用交互式 `config init`(无 flag),避免在 stdin 不可用时卡住。
33
-
34
- ### 信息来源优先级
35
-
36
- 1. 环境变量:`DELTA_INFRA_BASE_URL`、`DELTA_INFRA_TOKEN`
37
- 2. `~/.delta-infra/config.json` 中的 `base_url` / `token`
38
- 3. 默认值:仅 `base_url` 有内部默认值
39
- 4. 以上都缺失时,**必须询问用户**,禁止虚构
40
-
41
- ### 运行时凭证
42
-
43
- CLI 使用 `token` 发送请求头 `Authorization: Bearer <token>`,并自动从 JWT 中提取 `uid` 作为 `X-User-Uid`。
44
-
45
- ## 安全红线
46
-
47
- - **禁止输出密钥**:`token` 不得出现在日志、消息、终端。
48
- - **禁止自己拼 URL / 构造 HTTP 请求**:所有请求都走 `delta-cli`。
49
- - **不要主动安装 CLI**:如果用户说没装,建议他去 README 或 install.sh,不要自己选安装方式。
50
- - **写入/删除操作前确认意图**:`sandbox kill`、`sandbox finish`、`sandbox write` 等必须告诉用户影响。
51
- - `~/.delta-infra/config.json` 权限为 `0600`,不要手动放宽。
52
- - **sandbox 必须销毁**:调用 `sandbox create` 后,当前任务结束前必须调用 `sandbox kill`。server 端无自动清理,不销毁 = 永久占用 GPU;即使出错也不得跳过。
53
-
54
- ## JSON 输出约定
55
-
56
- ```json
57
- {"ok":true,"data":{...}}
58
- {"ok":false,"error":{"type":"auth","message":"...","hint":"..."}}
59
- ```
60
-
61
- AI Agent 解析规则:
62
- - 始终检查 `ok`,不要只看 exit code。
63
- - `error.type` 是稳定分支字段:`auth`, `permission`, `not_found`, `validation`, `network`, `api`, `command_failed`, `internal`。
64
- - `error.hint` 可展示给用户。
65
-
66
- ## 退出码约定
67
-
68
- | Type | Exit Code | 场景 |
69
- |------|-----------|------|
70
- | validation | 2 | flag / 参数错误 |
71
- | auth | 3 | 未认证或认证失败 |
72
- | permission | 4 | 权限不足 |
73
- | not_found | 5 | sandbox/file 不存在 |
74
- | network | 6 | DNS、超时、连接失败 |
75
- | api | 7 | 服务端非 2xx |
76
- | command_failed | 8 | sandbox 内命令执行失败(非零 exit_code 或报错);SSE 路径在 complete 帧后追加该 error 信封,**message 附 stderr 尾部**便于快速扫读 |
77
- | internal | 10 | 客户端内部错误 |
78
-
79
- ## 升级检查
80
-
81
- 完成用户请求后,如果 JSON 中有 `_notice.update`,可建议:
82
-
83
- ```bash
84
- delta-cli upgrade
85
- ```
86
-
87
- ## 常见恢复
88
-
89
- | 错误 / 现象 | 恢复动作 |
90
- |-------------|----------|
91
- | `error.type: auth` / `method: none` | 询问用户后执行 `config init --base-url <url> --token <token>` |
92
- | `error.type: permission` | 确认当前认证是否有权限;必要时换 token |
93
- | `error.type: not_found` | 检查 `sandbox_id` / path |
94
- | `error.type: network` | 检查 `base_url` 和网络连通性 |
95
- | `error.type: validation` | 检查必填 flag 和参数格式 |
96
- | `error.type: command_failed` | sandbox 内命令失败:信封 `message` 已带 stderr 尾部,可快速定位;完整 `stderr`/`result_summary` 在 complete 帧里,重试或修正命令 |
97
- | 配置文件解析失败 | 检查 `~/.delta-infra/config.json` 是否为有效 JSON |
1
+ ---
2
+ name: delta-shared
3
+ description: "delta-cli 全局共享规则:配置/认证检查、安全红线、错误处理、sandbox 销毁。"
4
+ ---
5
+
6
+ # delta-cli 共享规则
7
+
8
+ 指导 AI 使用 `delta-cli` 的通用规则。核心原则:**只检查、不猜测、不泄露密钥**。
9
+
10
+ ## 执行任何操作之前
11
+
12
+ 1. 确认服务端地址:
13
+ ```bash
14
+ delta-cli config show
15
+ ```
16
+ 2. 确认认证状态:
17
+ ```bash
18
+ delta-cli auth status
19
+ ```
20
+
21
+ - 若 `auth status` 显示 `method: token`,**直接执行目标命令**,不要重复登录或初始化配置。
22
+ - 若显示 `method: none`,或操作返回 `error.type: auth` / `permission`,才进入下面的“补全配置”流程。
23
+
24
+ ## 补全配置
25
+
26
+ 缺少服务端地址或凭证时,**向用户询问一次**,然后用 flag 初始化:
27
+
28
+ ```bash
29
+ delta-cli config init --base-url <server-url> --token <token>
30
+ ```
31
+
32
+ 不要用交互式 `config init`(无 flag),避免在 stdin 不可用时卡住。
33
+
34
+ ### 信息来源优先级
35
+
36
+ 1. 环境变量:`DELTA_INFRA_BASE_URL`、`DELTA_INFRA_TOKEN`
37
+ 2. `~/.delta-infra/config.json` 中的 `base_url` / `token`
38
+ 3. 默认值:仅 `base_url` 有内部默认值
39
+ 4. 以上都缺失时,**必须询问用户**,禁止虚构
40
+
41
+ ### 运行时凭证
42
+
43
+ CLI 使用 `token` 发送请求头 `Authorization: Bearer <token>`,并自动从 JWT 中提取 `uid` 作为 `X-User-Uid`。
44
+
45
+ ## 安全红线
46
+
47
+ - **禁止输出密钥**:`token` 不得出现在日志、消息、终端。
48
+ - **禁止自己拼 URL / 构造 HTTP 请求**:所有请求都走 `delta-cli`。
49
+ - **不要主动安装 CLI**:如果用户说没装,建议他去 README 或 install.sh,不要自己选安装方式。
50
+ - **写入/删除操作前确认意图**:`sandbox kill`、`sandbox finish`、`sandbox write` 等必须告诉用户影响。
51
+ - `~/.delta-infra/config.json` 权限为 `0600`,不要手动放宽。
52
+ - **sandbox 必须销毁**:调用 `sandbox create` 后,当前任务结束前必须调用 `sandbox kill`。server 端无自动清理,不销毁 = 永久占用 GPU;即使出错也不得跳过。
53
+
54
+ ## JSON 输出约定
55
+
56
+ ```json
57
+ {"ok":true,"data":{...}}
58
+ {"ok":false,"error":{"type":"auth","message":"...","hint":"..."}}
59
+ ```
60
+
61
+ AI Agent 解析规则:
62
+ - 始终检查 `ok`,不要只看 exit code。
63
+ - `error.type` 是稳定分支字段:`auth`, `permission`, `not_found`, `validation`, `network`, `api`, `command_failed`, `internal`。
64
+ - `error.hint` 可展示给用户。
65
+
66
+ ## 退出码约定
67
+
68
+ | Type | Exit Code | 场景 |
69
+ |------|-----------|------|
70
+ | validation | 2 | flag / 参数错误 |
71
+ | auth | 3 | 未认证或认证失败 |
72
+ | permission | 4 | 权限不足 |
73
+ | not_found | 5 | sandbox/file 不存在 |
74
+ | network | 6 | DNS、超时、连接失败 |
75
+ | api | 7 | 服务端非 2xx |
76
+ | command_failed | 8 | sandbox 内命令执行失败(非零 exit_code 或报错);SSE 路径在 complete 帧后追加该 error 信封,**message 附 stderr 尾部**便于快速扫读 |
77
+ | internal | 10 | 客户端内部错误 |
78
+
79
+ ## 升级检查
80
+
81
+ 完成用户请求后,如果 JSON 中有 `_notice.update`,可建议:
82
+
83
+ ```bash
84
+ delta-cli upgrade
85
+ ```
86
+
87
+ ## 常见恢复
88
+
89
+ | 错误 / 现象 | 恢复动作 |
90
+ |-------------|----------|
91
+ | `error.type: auth` / `method: none` | 询问用户后执行 `config init --base-url <url> --token <token>` |
92
+ | `error.type: permission` | 确认当前认证是否有权限;必要时换 token |
93
+ | `error.type: not_found` | 检查 `sandbox_id` / path |
94
+ | `error.type: network` | 检查 `base_url` 和网络连通性 |
95
+ | `error.type: validation` | 检查必填 flag 和参数格式 |
96
+ | `error.type: command_failed` | sandbox 内命令失败:信封 `message` 已带 stderr 尾部,可快速定位;完整 `stderr`/`result_summary` 在 complete 帧里,重试或修正命令 |
97
+ | 配置文件解析失败 | 检查 `~/.delta-infra/config.json` 是否为有效 JSON |
@@ -1,4 +1,4 @@
1
- interface:
2
- display_name: "Delta CLI 共享规则"
3
- short_description: "处理 Delta CLI 的配置、认证、错误与安全边界"
4
- default_prompt: "使用 $delta-shared 检查并处理 Delta CLI 的配置或认证问题。"
1
+ interface:
2
+ display_name: "Delta CLI 共享规则"
3
+ short_description: "处理 Delta CLI 的配置、认证、错误与安全边界"
4
+ default_prompt: "使用 $delta-shared 检查并处理 Delta CLI 的配置或认证问题。"
@@ -1,19 +1,19 @@
1
- {
2
- "skills": [
3
- "delta-sandbox",
4
- "delta-shared",
5
- "delta-science"
6
- ],
7
- "retired_skills": [
8
- "pubchem",
9
- "rdkit",
10
- "pymatgen",
11
- "gsasii",
12
- "lammps",
13
- "delta-bo",
14
- "ldm-bo",
15
- "synbo-service",
16
- "antbo-service",
17
- "antbo-ldm-guard"
18
- ]
19
- }
1
+ {
2
+ "skills": [
3
+ "delta-sandbox",
4
+ "delta-shared",
5
+ "delta-science"
6
+ ],
7
+ "retired_skills": [
8
+ "pubchem",
9
+ "rdkit",
10
+ "pymatgen",
11
+ "gsasii",
12
+ "lammps",
13
+ "delta-bo",
14
+ "ldm-bo",
15
+ "synbo-service",
16
+ "antbo-service",
17
+ "antbo-ldm-guard"
18
+ ]
19
+ }