its-magic 0.1.2-39 → 0.1.2-40
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.
- package/installer.sh +643 -643
- package/package.json +5 -1
- package/scripts/guard_installer_publish.py +70 -0
- package/scripts/remote_config_summary.py +243 -0
- package/template/.cursor/commands/execute.md +6 -0
- package/template/.cursor/commands/qa.md +5 -0
- package/template/.cursor/scratchpad.md +7 -3
- package/template/docs/engineering/context/installer-owned-paths.manifest +6 -0
- package/template/docs/engineering/runbook.md +31 -0
- package/template/docs/engineering/runtime-connectivity.md +20 -0
- package/template/docs/engineering/us-0084-remote-e2e.md +39 -0
- package/template/scripts/guard_installer_publish.py +70 -0
- package/template/scripts/remote_config_summary.py +243 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Summarize .cursor/remote.json for operators (US-0084 / US-0064 alignment).
|
|
4
|
+
|
|
5
|
+
Stdout: non-secret labels and env-reference names only (no key material).
|
|
6
|
+
Stderr: errors and skip reasons.
|
|
7
|
+
|
|
8
|
+
Exit codes:
|
|
9
|
+
0 OK or REMOTE_EXECUTION=0 skip (DEC-0070)
|
|
10
|
+
1 usage / CLI error
|
|
11
|
+
2 config missing or unreadable
|
|
12
|
+
3 invalid JSON
|
|
13
|
+
4 schema / contract mismatch vs runbook remote.json contract
|
|
14
|
+
5 reserved (unused; DEC-0070 maps REMOTE_EXECUTION=0 to 0)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
ENV_VAR_NAME = re.compile(r"^[A-Z][A-Z0-9_]*$")
|
|
28
|
+
ALLOWED_TYPES = frozenset({"docker", "ssh", "vm"})
|
|
29
|
+
AUTH_MODES = frozenset({"none", "env"})
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _truthy_remote_execution(raw: str | None) -> bool:
|
|
33
|
+
if raw is None:
|
|
34
|
+
return False
|
|
35
|
+
return raw.strip() in {"1", "true", "TRUE", "yes", "YES", "on", "ON"}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _parse_args(argv: list[str]) -> tuple[argparse.Namespace, list[str]]:
|
|
39
|
+
p = argparse.ArgumentParser(description=__doc__.split("\n\n")[0], add_help=True)
|
|
40
|
+
p.add_argument(
|
|
41
|
+
"--config",
|
|
42
|
+
default=None,
|
|
43
|
+
help="Path to remote JSON (default: REMOTE_CONFIG env or .cursor/remote.json)",
|
|
44
|
+
)
|
|
45
|
+
return p.parse_known_args(argv)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _config_path(explicit: str | None) -> Path:
|
|
49
|
+
if explicit:
|
|
50
|
+
return Path(explicit).expanduser()
|
|
51
|
+
env = os.environ.get("REMOTE_CONFIG")
|
|
52
|
+
if env:
|
|
53
|
+
return Path(env).expanduser()
|
|
54
|
+
return Path(".cursor/remote.json")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _validate_env_ref(name: str, value: Any, prefix: str) -> str | None:
|
|
58
|
+
if not isinstance(value, str):
|
|
59
|
+
return f"{prefix}: {name} must be a string env-reference name"
|
|
60
|
+
if not ENV_VAR_NAME.match(value):
|
|
61
|
+
return f"{prefix}: {name} must look like an env var name (A-Z_0-9)"
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def validate_and_summarize(path: Path, data: dict[str, Any]) -> tuple[list[str], list[str]]:
|
|
66
|
+
"""Return (stdout_lines, error_messages)."""
|
|
67
|
+
errs: list[str] = []
|
|
68
|
+
out: list[str] = []
|
|
69
|
+
out.append(f"remote_config={path.as_posix()}")
|
|
70
|
+
|
|
71
|
+
if not isinstance(data.get("version"), int):
|
|
72
|
+
errs.append("[REMOTE_CONFIG_ERROR] root.version: expected integer")
|
|
73
|
+
if not isinstance(data.get("defaultTarget"), str) or not data["defaultTarget"]:
|
|
74
|
+
errs.append("[REMOTE_CONFIG_ERROR] root.defaultTarget: expected non-empty string")
|
|
75
|
+
targets = data.get("targets")
|
|
76
|
+
if not isinstance(targets, list) or not targets:
|
|
77
|
+
errs.append("[REMOTE_CONFIG_ERROR] root.targets: expected non-empty array")
|
|
78
|
+
|
|
79
|
+
if errs:
|
|
80
|
+
return out, errs
|
|
81
|
+
|
|
82
|
+
by_id: dict[str, dict[str, Any]] = {}
|
|
83
|
+
for i, t in enumerate(targets):
|
|
84
|
+
prefix = f"targets[{i}]"
|
|
85
|
+
if not isinstance(t, dict):
|
|
86
|
+
errs.append(f"[REMOTE_CONFIG_ERROR] {prefix}: expected object")
|
|
87
|
+
continue
|
|
88
|
+
tid = t.get("id")
|
|
89
|
+
if not isinstance(tid, str) or not tid:
|
|
90
|
+
errs.append(f"[REMOTE_CONFIG_ERROR] {prefix}.id: expected non-empty string")
|
|
91
|
+
ttype = t.get("type")
|
|
92
|
+
if ttype not in ALLOWED_TYPES:
|
|
93
|
+
errs.append(
|
|
94
|
+
f"[REMOTE_CONFIG_ERROR] {prefix}.type: expected one of {sorted(ALLOWED_TYPES)}"
|
|
95
|
+
)
|
|
96
|
+
if not isinstance(t.get("enabled"), bool):
|
|
97
|
+
errs.append(f"[REMOTE_CONFIG_ERROR] {prefix}.enabled: expected boolean")
|
|
98
|
+
if not isinstance(t.get("host"), str) or not t["host"]:
|
|
99
|
+
errs.append(f"[REMOTE_CONFIG_ERROR] {prefix}.host: expected non-empty string")
|
|
100
|
+
port = t.get("port")
|
|
101
|
+
if not isinstance(port, int) or not (1 <= port <= 65535):
|
|
102
|
+
errs.append(f"[REMOTE_CONFIG_ERROR] {prefix}.port: expected integer 1..65535")
|
|
103
|
+
if not isinstance(t.get("workspaceRoot"), str) or not t["workspaceRoot"]:
|
|
104
|
+
errs.append(
|
|
105
|
+
f"[REMOTE_CONFIG_ERROR] {prefix}.workspaceRoot: expected non-empty string"
|
|
106
|
+
)
|
|
107
|
+
auth = t.get("auth")
|
|
108
|
+
if auth is not None:
|
|
109
|
+
if not isinstance(auth, dict):
|
|
110
|
+
errs.append(f"[REMOTE_CONFIG_ERROR] {prefix}.auth: expected object")
|
|
111
|
+
else:
|
|
112
|
+
mode = auth.get("mode")
|
|
113
|
+
if mode not in AUTH_MODES:
|
|
114
|
+
errs.append(
|
|
115
|
+
f"[REMOTE_CONFIG_ERROR] {prefix}.auth.mode: expected one of {sorted(AUTH_MODES)}"
|
|
116
|
+
)
|
|
117
|
+
elif mode == "env":
|
|
118
|
+
env_keys = (
|
|
119
|
+
"tokenEnv",
|
|
120
|
+
"passwordEnv",
|
|
121
|
+
"privateKeyPathEnv",
|
|
122
|
+
"usernameEnv",
|
|
123
|
+
)
|
|
124
|
+
any_ref = False
|
|
125
|
+
for k in env_keys:
|
|
126
|
+
if k not in auth:
|
|
127
|
+
continue
|
|
128
|
+
any_ref = True
|
|
129
|
+
err = _validate_env_ref(k, auth[k], f"{prefix}.auth")
|
|
130
|
+
if err:
|
|
131
|
+
errs.append(f"[REMOTE_CONFIG_ERROR] {err}")
|
|
132
|
+
if not any_ref:
|
|
133
|
+
errs.append(
|
|
134
|
+
f"[REMOTE_CONFIG_ERROR] {prefix}.auth: mode=env requires at least one *Env field"
|
|
135
|
+
)
|
|
136
|
+
if isinstance(tid, str) and tid:
|
|
137
|
+
by_id[tid] = t
|
|
138
|
+
|
|
139
|
+
default = data["defaultTarget"]
|
|
140
|
+
if default not in by_id:
|
|
141
|
+
errs.append(
|
|
142
|
+
f"[REMOTE_CONFIG_ERROR] defaultTarget={default!r} not found in targets[].id"
|
|
143
|
+
)
|
|
144
|
+
elif not by_id[default].get("enabled"):
|
|
145
|
+
errs.append(
|
|
146
|
+
f"[REMOTE_CONFIG_ERROR] defaultTarget={default!r} must reference an enabled target"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if errs:
|
|
150
|
+
return out, errs
|
|
151
|
+
|
|
152
|
+
out.append(f"defaultTarget={data['defaultTarget']}")
|
|
153
|
+
for t in targets:
|
|
154
|
+
assert isinstance(t, dict)
|
|
155
|
+
tid = t["id"]
|
|
156
|
+
parts = [
|
|
157
|
+
f"id={tid}",
|
|
158
|
+
f"type={t['type']}",
|
|
159
|
+
f"enabled={t['enabled']!s}",
|
|
160
|
+
f"host={t['host']}",
|
|
161
|
+
f"port={t['port']}",
|
|
162
|
+
f"workspaceRoot={t['workspaceRoot']}",
|
|
163
|
+
]
|
|
164
|
+
auth = t.get("auth")
|
|
165
|
+
if isinstance(auth, dict):
|
|
166
|
+
parts.append(f"auth.mode={auth.get('mode')}")
|
|
167
|
+
for k in (
|
|
168
|
+
"tokenEnv",
|
|
169
|
+
"passwordEnv",
|
|
170
|
+
"privateKeyPathEnv",
|
|
171
|
+
"usernameEnv",
|
|
172
|
+
):
|
|
173
|
+
if k in auth and isinstance(auth[k], str):
|
|
174
|
+
parts.append(f"auth.{k}={auth[k]}")
|
|
175
|
+
out.append("target:" + " ".join(parts))
|
|
176
|
+
|
|
177
|
+
return out, []
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def main(argv: list[str] | None = None) -> int:
|
|
181
|
+
args, rest = _parse_args(argv or sys.argv[1:])
|
|
182
|
+
if rest:
|
|
183
|
+
print(
|
|
184
|
+
f"[REMOTE_CONFIG_SUMMARY] unknown arguments: {' '.join(rest)}",
|
|
185
|
+
file=sys.stderr,
|
|
186
|
+
)
|
|
187
|
+
return 1
|
|
188
|
+
if not _truthy_remote_execution(os.environ.get("REMOTE_EXECUTION")):
|
|
189
|
+
print(
|
|
190
|
+
"[REMOTE_CONFIG_SUMMARY] REMOTE_EXECUTION!=1: skip validation/summary "
|
|
191
|
+
"(zero overhead; DEC-0070 / US-0084).",
|
|
192
|
+
file=sys.stderr,
|
|
193
|
+
)
|
|
194
|
+
return 0
|
|
195
|
+
|
|
196
|
+
path = _config_path(args.config)
|
|
197
|
+
if not path.is_file():
|
|
198
|
+
print(
|
|
199
|
+
f"[REMOTE_CONFIG_ERROR] {path}: file missing or unreadable. "
|
|
200
|
+
"Fix: create config or set REMOTE_EXECUTION=0.",
|
|
201
|
+
file=sys.stderr,
|
|
202
|
+
)
|
|
203
|
+
return 2
|
|
204
|
+
try:
|
|
205
|
+
raw = path.read_text(encoding="utf-8")
|
|
206
|
+
except OSError as e:
|
|
207
|
+
print(
|
|
208
|
+
f"[REMOTE_CONFIG_ERROR] {path}: read failed ({e}). Fix: permissions/path.",
|
|
209
|
+
file=sys.stderr,
|
|
210
|
+
)
|
|
211
|
+
return 2
|
|
212
|
+
try:
|
|
213
|
+
data = json.loads(raw)
|
|
214
|
+
except json.JSONDecodeError as e:
|
|
215
|
+
print(
|
|
216
|
+
f"[REMOTE_CONFIG_ERROR] {path}: invalid JSON ({e}). Fix: syntax.",
|
|
217
|
+
file=sys.stderr,
|
|
218
|
+
)
|
|
219
|
+
return 3
|
|
220
|
+
if not isinstance(data, dict):
|
|
221
|
+
print(
|
|
222
|
+
f"[REMOTE_CONFIG_ERROR] {path}: expected JSON object at root.",
|
|
223
|
+
file=sys.stderr,
|
|
224
|
+
)
|
|
225
|
+
return 4
|
|
226
|
+
|
|
227
|
+
lines, errs = validate_and_summarize(path, data)
|
|
228
|
+
if errs:
|
|
229
|
+
for line in errs:
|
|
230
|
+
print(line, file=sys.stderr)
|
|
231
|
+
print(
|
|
232
|
+
f"[REMOTE_CONFIG_ERROR] {path}: contract check failed. "
|
|
233
|
+
"Fix: align with docs/engineering/runbook.md remote contract (US-0084).",
|
|
234
|
+
file=sys.stderr,
|
|
235
|
+
)
|
|
236
|
+
return 4
|
|
237
|
+
for line in lines:
|
|
238
|
+
print(line)
|
|
239
|
+
return 0
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
if __name__ == "__main__":
|
|
243
|
+
raise SystemExit(main())
|