agentera 0.0.0 → 3.0.0-dev.0

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 (252) hide show
  1. package/README.md +6 -45
  2. package/bundle/.agentera-npx-bundle.json +4 -0
  3. package/bundle/references/adapters/cursor.md +213 -0
  4. package/bundle/references/adapters/opencode.md +530 -0
  5. package/bundle/references/adapters/package-manifest-interface-model.yaml +337 -0
  6. package/bundle/references/adapters/package-registry.yaml +247 -0
  7. package/bundle/references/adapters/package-surface-characterization.md +48 -0
  8. package/bundle/references/adapters/runtime-adapter-characterization.md +79 -0
  9. package/bundle/references/adapters/runtime-adapter-interface-model.yaml +200 -0
  10. package/bundle/references/adapters/runtime-adapter-registry.yaml +548 -0
  11. package/bundle/references/adapters/runtime-feature-parity.md +189 -0
  12. package/bundle/references/analysis/benchmark.md +267 -0
  13. package/bundle/references/analysis/startup-measurement-contract.yaml +424 -0
  14. package/bundle/references/artifacts/artifact-registry-interface-model.yaml +288 -0
  15. package/bundle/references/cli/agent-ready-state-contract.yaml +950 -0
  16. package/bundle/references/cli/app-lifecycle-vocabulary.yaml +233 -0
  17. package/bundle/references/cli/audience-namespace-cli-migration.yaml +355 -0
  18. package/bundle/references/cli/bundle-skill-vocabulary.yaml +278 -0
  19. package/bundle/references/cli/capability-instruction-contract.yaml +123 -0
  20. package/bundle/references/cli/capability-tool-classification.yaml +53 -0
  21. package/bundle/references/cli/routing-execution-vocabulary.yaml +281 -0
  22. package/bundle/references/cli/update-channels.yaml +120 -0
  23. package/bundle/references/cli/vocabulary-index.yaml +160 -0
  24. package/bundle/references/cli/vocabulary.md +562 -0
  25. package/bundle/references/meta/documentation-inventory.md +43 -0
  26. package/bundle/references/v1-section-mapping.md +47 -0
  27. package/bundle/registry.json +39 -0
  28. package/bundle/skills/agentera/.claude-plugin/plugin.json +27 -0
  29. package/bundle/skills/agentera/SKILL.md +470 -0
  30. package/bundle/skills/agentera/agents/dokumentera.toml +6 -0
  31. package/bundle/skills/agentera/agents/hej.toml +6 -0
  32. package/bundle/skills/agentera/agents/inspektera.toml +6 -0
  33. package/bundle/skills/agentera/agents/inspirera.toml +6 -0
  34. package/bundle/skills/agentera/agents/optimera.toml +6 -0
  35. package/bundle/skills/agentera/agents/orkestrera.toml +6 -0
  36. package/bundle/skills/agentera/agents/planera.toml +6 -0
  37. package/bundle/skills/agentera/agents/profilera.toml +6 -0
  38. package/bundle/skills/agentera/agents/realisera.toml +6 -0
  39. package/bundle/skills/agentera/agents/resonera.toml +6 -0
  40. package/bundle/skills/agentera/agents/visionera.toml +6 -0
  41. package/bundle/skills/agentera/agents/visualisera.toml +6 -0
  42. package/bundle/skills/agentera/capabilities/dokumentera/instructions.md +428 -0
  43. package/bundle/skills/agentera/capabilities/dokumentera/schemas/artifacts.yaml +73 -0
  44. package/bundle/skills/agentera/capabilities/dokumentera/schemas/exit.yaml +35 -0
  45. package/bundle/skills/agentera/capabilities/dokumentera/schemas/triggers.yaml +35 -0
  46. package/bundle/skills/agentera/capabilities/dokumentera/schemas/validation.yaml +139 -0
  47. package/bundle/skills/agentera/capabilities/hej/instructions.md +331 -0
  48. package/bundle/skills/agentera/capabilities/hej/schemas/artifacts.yaml +69 -0
  49. package/bundle/skills/agentera/capabilities/hej/schemas/exit.yaml +32 -0
  50. package/bundle/skills/agentera/capabilities/hej/schemas/triggers.yaml +58 -0
  51. package/bundle/skills/agentera/capabilities/hej/schemas/validation.yaml +55 -0
  52. package/bundle/skills/agentera/capabilities/inspektera/instructions.md +514 -0
  53. package/bundle/skills/agentera/capabilities/inspektera/schemas/artifacts.yaml +76 -0
  54. package/bundle/skills/agentera/capabilities/inspektera/schemas/exit.yaml +36 -0
  55. package/bundle/skills/agentera/capabilities/inspektera/schemas/triggers.yaml +38 -0
  56. package/bundle/skills/agentera/capabilities/inspektera/schemas/validation.yaml +113 -0
  57. package/bundle/skills/agentera/capabilities/inspirera/instructions.md +280 -0
  58. package/bundle/skills/agentera/capabilities/inspirera/schemas/artifacts.yaml +24 -0
  59. package/bundle/skills/agentera/capabilities/inspirera/schemas/exit.yaml +33 -0
  60. package/bundle/skills/agentera/capabilities/inspirera/schemas/triggers.yaml +34 -0
  61. package/bundle/skills/agentera/capabilities/inspirera/schemas/validation.yaml +58 -0
  62. package/bundle/skills/agentera/capabilities/optimera/instructions.md +437 -0
  63. package/bundle/skills/agentera/capabilities/optimera/schemas/artifacts.yaml +69 -0
  64. package/bundle/skills/agentera/capabilities/optimera/schemas/exit.yaml +35 -0
  65. package/bundle/skills/agentera/capabilities/optimera/schemas/triggers.yaml +39 -0
  66. package/bundle/skills/agentera/capabilities/optimera/schemas/validation.yaml +91 -0
  67. package/bundle/skills/agentera/capabilities/orkestrera/instructions.md +433 -0
  68. package/bundle/skills/agentera/capabilities/orkestrera/schemas/artifacts.yaml +64 -0
  69. package/bundle/skills/agentera/capabilities/orkestrera/schemas/exit.yaml +34 -0
  70. package/bundle/skills/agentera/capabilities/orkestrera/schemas/triggers.yaml +42 -0
  71. package/bundle/skills/agentera/capabilities/orkestrera/schemas/validation.yaml +107 -0
  72. package/bundle/skills/agentera/capabilities/planera/instructions.md +368 -0
  73. package/bundle/skills/agentera/capabilities/planera/schemas/artifacts.yaml +62 -0
  74. package/bundle/skills/agentera/capabilities/planera/schemas/exit.yaml +33 -0
  75. package/bundle/skills/agentera/capabilities/planera/schemas/triggers.yaml +34 -0
  76. package/bundle/skills/agentera/capabilities/planera/schemas/validation.yaml +61 -0
  77. package/bundle/skills/agentera/capabilities/profilera/instructions.md +419 -0
  78. package/bundle/skills/agentera/capabilities/profilera/schemas/artifacts.yaml +18 -0
  79. package/bundle/skills/agentera/capabilities/profilera/schemas/exit.yaml +34 -0
  80. package/bundle/skills/agentera/capabilities/profilera/schemas/triggers.yaml +45 -0
  81. package/bundle/skills/agentera/capabilities/profilera/schemas/validation.yaml +57 -0
  82. package/bundle/skills/agentera/capabilities/realisera/instructions.md +403 -0
  83. package/bundle/skills/agentera/capabilities/realisera/schemas/artifacts.yaml +80 -0
  84. package/bundle/skills/agentera/capabilities/realisera/schemas/exit.yaml +35 -0
  85. package/bundle/skills/agentera/capabilities/realisera/schemas/triggers.yaml +39 -0
  86. package/bundle/skills/agentera/capabilities/realisera/schemas/validation.yaml +110 -0
  87. package/bundle/skills/agentera/capabilities/resonera/instructions.md +329 -0
  88. package/bundle/skills/agentera/capabilities/resonera/schemas/artifacts.yaml +47 -0
  89. package/bundle/skills/agentera/capabilities/resonera/schemas/exit.yaml +35 -0
  90. package/bundle/skills/agentera/capabilities/resonera/schemas/triggers.yaml +46 -0
  91. package/bundle/skills/agentera/capabilities/resonera/schemas/validation.yaml +77 -0
  92. package/bundle/skills/agentera/capabilities/visionera/instructions.md +309 -0
  93. package/bundle/skills/agentera/capabilities/visionera/schemas/artifacts.yaml +57 -0
  94. package/bundle/skills/agentera/capabilities/visionera/schemas/exit.yaml +35 -0
  95. package/bundle/skills/agentera/capabilities/visionera/schemas/triggers.yaml +41 -0
  96. package/bundle/skills/agentera/capabilities/visionera/schemas/validation.yaml +74 -0
  97. package/bundle/skills/agentera/capabilities/visualisera/instructions.md +400 -0
  98. package/bundle/skills/agentera/capabilities/visualisera/schemas/artifacts.yaml +44 -0
  99. package/bundle/skills/agentera/capabilities/visualisera/schemas/exit.yaml +34 -0
  100. package/bundle/skills/agentera/capabilities/visualisera/schemas/triggers.yaml +33 -0
  101. package/bundle/skills/agentera/capabilities/visualisera/schemas/validation.yaml +80 -0
  102. package/bundle/skills/agentera/capability_schema_contract.yaml +385 -0
  103. package/bundle/skills/agentera/protocol.yaml +463 -0
  104. package/bundle/skills/agentera/references/contract.md +1039 -0
  105. package/bundle/skills/agentera/schemas/artifacts/changelog.yaml +60 -0
  106. package/bundle/skills/agentera/schemas/artifacts/decisions.yaml +461 -0
  107. package/bundle/skills/agentera/schemas/artifacts/design.yaml +55 -0
  108. package/bundle/skills/agentera/schemas/artifacts/docs.yaml +402 -0
  109. package/bundle/skills/agentera/schemas/artifacts/experiments.yaml +373 -0
  110. package/bundle/skills/agentera/schemas/artifacts/health.yaml +484 -0
  111. package/bundle/skills/agentera/schemas/artifacts/objective.yaml +399 -0
  112. package/bundle/skills/agentera/schemas/artifacts/plan.yaml +342 -0
  113. package/bundle/skills/agentera/schemas/artifacts/progress.yaml +325 -0
  114. package/bundle/skills/agentera/schemas/artifacts/todo.yaml +110 -0
  115. package/bundle/skills/agentera/schemas/artifacts/vision.yaml +262 -0
  116. package/bundle/skills/hej/.claude-plugin/plugin.json +6 -0
  117. package/bundle/skills/hej/SKILL.md +69 -0
  118. package/bundle/skills/hej/agents/hej.toml +11 -0
  119. package/bundle/skills/hej/agents/openai.yaml +8 -0
  120. package/dist/analytics/extractCorpus.js +1791 -0
  121. package/dist/analytics/extractCorpus.js.map +1 -0
  122. package/dist/analytics/usageStats.js +487 -0
  123. package/dist/analytics/usageStats.js.map +1 -0
  124. package/dist/bin/agentera.js +4 -0
  125. package/dist/bin/agentera.js.map +1 -0
  126. package/dist/cli/appContext.js +226 -0
  127. package/dist/cli/appContext.js.map +1 -0
  128. package/dist/cli/argvalidate.js +41 -0
  129. package/dist/cli/argvalidate.js.map +1 -0
  130. package/dist/cli/capabilityContext.js +2421 -0
  131. package/dist/cli/capabilityContext.js.map +1 -0
  132. package/dist/cli/commands/backfill.js +84 -0
  133. package/dist/cli/commands/backfill.js.map +1 -0
  134. package/dist/cli/commands/capability.js +44 -0
  135. package/dist/cli/commands/capability.js.map +1 -0
  136. package/dist/cli/commands/compact.js +148 -0
  137. package/dist/cli/commands/compact.js.map +1 -0
  138. package/dist/cli/commands/doctor.js +180 -0
  139. package/dist/cli/commands/doctor.js.map +1 -0
  140. package/dist/cli/commands/lint.js +179 -0
  141. package/dist/cli/commands/lint.js.map +1 -0
  142. package/dist/cli/commands/prime.js +545 -0
  143. package/dist/cli/commands/prime.js.map +1 -0
  144. package/dist/cli/commands/query.js +346 -0
  145. package/dist/cli/commands/query.js.map +1 -0
  146. package/dist/cli/commands/report.js +210 -0
  147. package/dist/cli/commands/report.js.map +1 -0
  148. package/dist/cli/commands/schema.js +306 -0
  149. package/dist/cli/commands/schema.js.map +1 -0
  150. package/dist/cli/commands/state.js +1012 -0
  151. package/dist/cli/commands/state.js.map +1 -0
  152. package/dist/cli/commands/upgrade.js +49 -0
  153. package/dist/cli/commands/upgrade.js.map +1 -0
  154. package/dist/cli/commands/validate.js +519 -0
  155. package/dist/cli/commands/validate.js.map +1 -0
  156. package/dist/cli/commands/verify.js +204 -0
  157. package/dist/cli/commands/verify.js.map +1 -0
  158. package/dist/cli/dispatch.js +962 -0
  159. package/dist/cli/dispatch.js.map +1 -0
  160. package/dist/cli/orientation.js +595 -0
  161. package/dist/cli/orientation.js.map +1 -0
  162. package/dist/cli/prime-blob.js +3 -0
  163. package/dist/cli/prime-blob.js.map +1 -0
  164. package/dist/cli/stateQuery.js +292 -0
  165. package/dist/cli/stateQuery.js.map +1 -0
  166. package/dist/cli/structured.js +18 -0
  167. package/dist/cli/structured.js.map +1 -0
  168. package/dist/core/difflib.js +274 -0
  169. package/dist/core/difflib.js.map +1 -0
  170. package/dist/core/git.js +43 -0
  171. package/dist/core/git.js.map +1 -0
  172. package/dist/core/paths.js +50 -0
  173. package/dist/core/paths.js.map +1 -0
  174. package/dist/core/pyjson.js +101 -0
  175. package/dist/core/pyjson.js.map +1 -0
  176. package/dist/core/sourceRoot.js +72 -0
  177. package/dist/core/sourceRoot.js.map +1 -0
  178. package/dist/core/toml.js +11 -0
  179. package/dist/core/toml.js.map +1 -0
  180. package/dist/core/yaml.js +25 -0
  181. package/dist/core/yaml.js.map +1 -0
  182. package/dist/eval/evalSkills.js +258 -0
  183. package/dist/eval/evalSkills.js.map +1 -0
  184. package/dist/eval/semanticEval.js +148 -0
  185. package/dist/eval/semanticEval.js.map +1 -0
  186. package/dist/eval/semanticFixtures.js +227 -0
  187. package/dist/eval/semanticFixtures.js.map +1 -0
  188. package/dist/hooks/common.js +160 -0
  189. package/dist/hooks/common.js.map +1 -0
  190. package/dist/hooks/compaction.js +935 -0
  191. package/dist/hooks/compaction.js.map +1 -0
  192. package/dist/hooks/cursorPreToolUse.js +19 -0
  193. package/dist/hooks/cursorPreToolUse.js.map +1 -0
  194. package/dist/hooks/cursorSessionStart.js +71 -0
  195. package/dist/hooks/cursorSessionStart.js.map +1 -0
  196. package/dist/hooks/sessionStart.js +209 -0
  197. package/dist/hooks/sessionStart.js.map +1 -0
  198. package/dist/hooks/sessionStop.js +212 -0
  199. package/dist/hooks/sessionStop.js.map +1 -0
  200. package/dist/hooks/validateArtifact.js +933 -0
  201. package/dist/hooks/validateArtifact.js.map +1 -0
  202. package/dist/registries/artifactRegistry.js +206 -0
  203. package/dist/registries/artifactRegistry.js.map +1 -0
  204. package/dist/registries/capabilityContract.js +310 -0
  205. package/dist/registries/capabilityContract.js.map +1 -0
  206. package/dist/registries/packageRegistry.js +641 -0
  207. package/dist/registries/packageRegistry.js.map +1 -0
  208. package/dist/registries/runtimeAdapterRegistry.js +315 -0
  209. package/dist/registries/runtimeAdapterRegistry.js.map +1 -0
  210. package/dist/setup/codex.js +1052 -0
  211. package/dist/setup/codex.js.map +1 -0
  212. package/dist/setup/copilot.js +227 -0
  213. package/dist/setup/copilot.js.map +1 -0
  214. package/dist/setup/cursor.js +127 -0
  215. package/dist/setup/cursor.js.map +1 -0
  216. package/dist/setup/doctor.js +1269 -0
  217. package/dist/setup/doctor.js.map +1 -0
  218. package/dist/state/installRoot.js +279 -0
  219. package/dist/state/installRoot.js.map +1 -0
  220. package/dist/state/progressCommit.js +289 -0
  221. package/dist/state/progressCommit.js.map +1 -0
  222. package/dist/state/startupAnalysis.js +1953 -0
  223. package/dist/state/startupAnalysis.js.map +1 -0
  224. package/dist/upgrade/appModel.js +189 -0
  225. package/dist/upgrade/appModel.js.map +1 -0
  226. package/dist/upgrade/channels.js +197 -0
  227. package/dist/upgrade/channels.js.map +1 -0
  228. package/dist/upgrade/compatibility.js +197 -0
  229. package/dist/upgrade/compatibility.js.map +1 -0
  230. package/dist/upgrade/doctor.js +368 -0
  231. package/dist/upgrade/doctor.js.map +1 -0
  232. package/dist/upgrade/migrateArtifactsV2ToV3.js +412 -0
  233. package/dist/upgrade/migrateArtifactsV2ToV3.js.map +1 -0
  234. package/dist/upgrade/upgradeCommands.js +40 -0
  235. package/dist/upgrade/upgradeCommands.js.map +1 -0
  236. package/dist/upgrade/upgradeOrchestrator.js +280 -0
  237. package/dist/upgrade/upgradeOrchestrator.js.map +1 -0
  238. package/dist/validate/appHomeContract.js +150 -0
  239. package/dist/validate/appHomeContract.js.map +1 -0
  240. package/dist/validate/capability.js +412 -0
  241. package/dist/validate/capability.js.map +1 -0
  242. package/dist/validate/crossCapability.js +145 -0
  243. package/dist/validate/crossCapability.js.map +1 -0
  244. package/dist/validate/lifecycleAdapters.js +772 -0
  245. package/dist/validate/lifecycleAdapters.js.map +1 -0
  246. package/dist/validate/selfAudit.js +107 -0
  247. package/dist/validate/selfAudit.js.map +1 -0
  248. package/package.json +28 -8
  249. package/LICENSE +0 -201
  250. package/bin/agentera.mjs +0 -50
  251. package/lib/exec.mjs +0 -116
  252. package/lib/resolve.mjs +0 -129
@@ -0,0 +1,1269 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import path from "node:path";
3
+ import { expanduser, isFile, pathExists, resolvePath } from "../core/paths.js";
4
+ import { SETUP_EVIDENCE, classifyResolvedRoot, } from "../state/installRoot.js";
5
+ import { loadRegistry } from "../registries/runtimeAdapterRegistry.js";
6
+ import { parseToml as parseTomlLocal } from "../core/toml.js";
7
+ export const SCHEMA_VERSION = "agentera.setupDoctor.v1";
8
+ export const STATUSES = ["pass", "warn", "fail", "skip"];
9
+ const REGISTRY = loadRegistry();
10
+ export const DOCTOR_RUNTIME_VIEWS = Object.fromEntries(REGISTRY.runtimeIds.map((runtime) => [runtime, REGISTRY.consumerView("doctor", runtime)]));
11
+ export const RUNTIMES = REGISTRY.runtimeIds;
12
+ export const WRITABLE_RUNTIMES = ["copilot", "codex"];
13
+ export const RUNTIME_BINARIES = Object.fromEntries(RUNTIMES.map((runtime) => [runtime, DOCTOR_RUNTIME_VIEWS[runtime].host_detection.binary_names[0]]));
14
+ export const OPENCODE_SKILL_INSTALL_COMMAND = "npx skills add jgabor/agentera -g -a opencode --skill agentera -y";
15
+ export const OPENCODE_SKILL_NAMES = ["agentera"];
16
+ export const OPENCODE_COMMAND_DESCRIPTIONS = {
17
+ agentera: "Compound agent orchestration suite: 12 capabilities in one bundled skill",
18
+ };
19
+ export const CANONICAL_ENTRIES = SETUP_EVIDENCE;
20
+ // Self-contained model: runtime hooks invoke the bundled CLI (npx -y agentera
21
+ // hook <name>); there are no per-install helper script files to verify.
22
+ export const HELPER_ENTRIES = [];
23
+ export const SMOKE_TIMEOUT_SECONDS = 30;
24
+ export const ENV_FALLBACKS = ["AGENTERA_HOME", "CLAUDE_PLUGIN_ROOT"];
25
+ export const COPILOT_MARKER = "# agentera: AGENTERA_HOME (managed)";
26
+ export const INSTALLER_SCHEMA_VERSION = "agentera.setupInstaller.v1";
27
+ export const SUPPORT_PATH_RE = /(?<![\w/.$-])(?<path>references\/[A-Za-z0-9][A-Za-z0-9_./-]*)/;
28
+ // ── registry-view accessors (mirror the Python helpers) ─────────────
29
+ export function diagnosticCheckNames(runtime) {
30
+ return DOCTOR_RUNTIME_VIEWS[runtime].diagnostics.check_names;
31
+ }
32
+ export function diagnosticMessages(runtime) {
33
+ return DOCTOR_RUNTIME_VIEWS[runtime].diagnostics.primary_messages;
34
+ }
35
+ export function diagnosticStatusLabels(runtime) {
36
+ return DOCTOR_RUNTIME_VIEWS[runtime].diagnostics.status_labels;
37
+ }
38
+ export function diagnosticGapLabels(runtime) {
39
+ return DOCTOR_RUNTIME_VIEWS[runtime].diagnostics.gap_labels;
40
+ }
41
+ export function availabilityProbeLabel(runtime) {
42
+ return String(DOCTOR_RUNTIME_VIEWS[runtime].host_detection.availability_probe_label);
43
+ }
44
+ export const AVAILABILITY_CHECKS = Object.fromEntries(RUNTIMES.map((rt) => [rt, availabilityProbeLabel(rt)]));
45
+ export const PASS_STATUSES = Object.fromEntries(RUNTIMES.map((rt) => [rt, diagnosticStatusLabels(rt)[0]]));
46
+ export const WARN_STATUSES = Object.fromEntries(RUNTIMES.map((rt) => [rt, diagnosticStatusLabels(rt)[1]]));
47
+ export const FAIL_STATUSES = Object.fromEntries(RUNTIMES.map((rt) => [rt, diagnosticStatusLabels(rt)[2]]));
48
+ export const SKIP_STATUSES = Object.fromEntries(RUNTIMES.map((rt) => [rt, diagnosticStatusLabels(rt)[3]]));
49
+ export const USER_ENVIRONMENT_GAPS = Object.fromEntries(RUNTIMES.map((rt) => [rt, diagnosticGapLabels(rt)[1]]));
50
+ export const RUNTIME_CONFIG_GAPS = Object.fromEntries(RUNTIMES.map((rt) => [rt, diagnosticGapLabels(rt)[0]]));
51
+ export const INSTALLER_FIXABLE_GAPS = {
52
+ copilot: [RUNTIME_CONFIG_GAPS.copilot, USER_ENVIRONMENT_GAPS.copilot],
53
+ codex: [RUNTIME_CONFIG_GAPS.codex, USER_ENVIRONMENT_GAPS.codex],
54
+ };
55
+ // ── install-root classification ─────────────────────────────────────
56
+ export function verifyInstallRoot(root) {
57
+ const classification = classifyResolvedRoot(root, { source: "explicit" });
58
+ if (classification.kind === "managed_fresh")
59
+ return [];
60
+ return SETUP_EVIDENCE.filter((entry) => !pathExists(path.join(root, entry)));
61
+ }
62
+ export function verifyHelperAccess(root) {
63
+ return HELPER_ENTRIES.filter((entry) => !isFile(path.join(root, entry)));
64
+ }
65
+ function moduleDir() {
66
+ try {
67
+ return path.dirname(fileURLToPath(import.meta.url));
68
+ }
69
+ catch {
70
+ return process.cwd();
71
+ }
72
+ }
73
+ export function autoDetectInstallRoot(env, start = null) {
74
+ for (const variable of ENV_FALLBACKS) {
75
+ const value = env[variable];
76
+ if (value) {
77
+ const candidate = resolvePath(value);
78
+ if (verifyInstallRoot(candidate).length === 0)
79
+ return candidate;
80
+ }
81
+ }
82
+ let current = resolvePath(start ?? moduleDir());
83
+ for (;;) {
84
+ if (verifyInstallRoot(current).length === 0)
85
+ return current;
86
+ const parent = path.dirname(current);
87
+ if (parent === current)
88
+ return null;
89
+ current = parent;
90
+ }
91
+ }
92
+ function setupMissing(root) {
93
+ return SETUP_EVIDENCE.filter((entry) => !pathExists(path.join(root, entry)));
94
+ }
95
+ export function classifyInstallRoot(explicitRoot, env) {
96
+ const source = explicitRoot !== null ? "argument" : "auto";
97
+ const root = explicitRoot !== null ? resolvePath(expanduser(explicitRoot)) : autoDetectInstallRoot(env);
98
+ if (root === null) {
99
+ return {
100
+ status: "fail",
101
+ path: null,
102
+ source,
103
+ kind: null,
104
+ gap: "user_environment",
105
+ message: "could not resolve an Agentera install root; pass --install-root or set AGENTERA_HOME",
106
+ missing: [...SETUP_EVIDENCE],
107
+ };
108
+ }
109
+ const classification = classifyResolvedRoot(root, { source: explicitRoot !== null ? "explicit" : "default" });
110
+ if (classification.kind !== "managed_fresh") {
111
+ return {
112
+ status: "fail",
113
+ path: root,
114
+ source,
115
+ kind: null,
116
+ gap: "bundle_packaging",
117
+ message: "install root is missing canonical Agentera entries",
118
+ missing: setupMissing(root),
119
+ };
120
+ }
121
+ const helperMissing = verifyHelperAccess(root);
122
+ const status = helperMissing.length === 0 ? "pass" : "fail";
123
+ return {
124
+ status,
125
+ path: root,
126
+ source,
127
+ kind: pathExists(path.join(root, ".git")) ? "local-clone" : "installed-bundle",
128
+ gap: status === "pass" ? null : "bundle_packaging",
129
+ message: status === "pass"
130
+ ? "install root is valid"
131
+ : "install root is valid but shared helper scripts are missing",
132
+ missing: helperMissing,
133
+ };
134
+ }
135
+ // ── generic check / aggregation helpers ─────────────────────────────
136
+ export function mkCheck(name, status, message, opts = {}) {
137
+ return {
138
+ name,
139
+ status,
140
+ message,
141
+ source: opts.source ?? null,
142
+ path: opts.path ?? null,
143
+ gap: opts.gap ?? null,
144
+ details: opts.details ?? [],
145
+ };
146
+ }
147
+ export function aggregateStatus(checks) {
148
+ const statuses = checks.map((c) => c.status);
149
+ if (statuses.length > 0 && statuses.every((s) => s === "skip"))
150
+ return "skip";
151
+ if (statuses.includes("fail"))
152
+ return "fail";
153
+ if (statuses.includes("warn"))
154
+ return "warn";
155
+ if (statuses.includes("pass"))
156
+ return "pass";
157
+ return "skip";
158
+ }
159
+ export function summarizeStatuses(items) {
160
+ const counts = {};
161
+ for (const status of STATUSES)
162
+ counts[status] = 0;
163
+ const values = Array.isArray(items) ? items : Object.values(items);
164
+ for (const item of values)
165
+ counts[item.status] += 1;
166
+ return counts;
167
+ }
168
+ export function tail(text, limit = 5) {
169
+ const lines = text.split(/\r\n|\r|\n/).filter((line) => line.trim());
170
+ return lines.slice(-limit);
171
+ }
172
+ // ===========================================================================
173
+ // Slice 2: runtime scaffolding, OpenCode diagnostics, reference validation
174
+ // ===========================================================================
175
+ import fs from "node:fs";
176
+ import os from "node:os";
177
+ import { classifyResolvedRoot as _classifyResolvedRoot } from "../state/installRoot.js";
178
+ function rootClassification(root, source) {
179
+ return _classifyResolvedRoot(root, { source });
180
+ }
181
+ /** shutil.which: first PATH entry whose `${dir}/${cmd}` is an executable file. */
182
+ export function which(cmd, pathStr) {
183
+ const accessCheck = (fn) => {
184
+ try {
185
+ const st = fs.statSync(fn);
186
+ if (st.isDirectory())
187
+ return false;
188
+ fs.accessSync(fn, fs.constants.X_OK);
189
+ return true;
190
+ }
191
+ catch {
192
+ return false;
193
+ }
194
+ };
195
+ if (cmd.includes("/")) {
196
+ return accessCheck(cmd) ? cmd : null;
197
+ }
198
+ const entries = (pathStr ?? "").split(path.delimiter);
199
+ const seen = new Set();
200
+ for (const dir of entries) {
201
+ if (!dir || seen.has(dir))
202
+ continue;
203
+ seen.add(dir);
204
+ const fn = path.join(dir, cmd);
205
+ if (accessCheck(fn))
206
+ return fn;
207
+ }
208
+ return null;
209
+ }
210
+ export function runtimeSkip(runtime, env) {
211
+ const binary = RUNTIME_BINARIES[runtime];
212
+ return {
213
+ runtime,
214
+ status: SKIP_STATUSES[runtime],
215
+ available: false,
216
+ binary: null,
217
+ checks: [
218
+ mkCheck(AVAILABILITY_CHECKS[runtime], SKIP_STATUSES[runtime], `${binary} executable not found on PATH`, {
219
+ source: "PATH",
220
+ gap: USER_ENVIRONMENT_GAPS[runtime],
221
+ details: [env.PATH ?? ""],
222
+ }),
223
+ ],
224
+ };
225
+ }
226
+ export function configuredRootCheck(runtime, name, candidate, installRoot, source) {
227
+ const classification = rootClassification(candidate, "environment");
228
+ if (String(classification.kind).startsWith("missing_")) {
229
+ return mkCheck(name, FAIL_STATUSES[runtime], "configured Agentera root does not exist", {
230
+ source,
231
+ path: candidate,
232
+ gap: RUNTIME_CONFIG_GAPS[runtime],
233
+ });
234
+ }
235
+ if (classification.kind !== "managed_fresh") {
236
+ return mkCheck(name, FAIL_STATUSES[runtime], "configured Agentera root is not a valid suite bundle", {
237
+ source,
238
+ path: candidate,
239
+ gap: "bundle_packaging",
240
+ details: setupMissing(candidate),
241
+ });
242
+ }
243
+ const helperMissing = verifyHelperAccess(candidate);
244
+ if (helperMissing.length > 0) {
245
+ return mkCheck(name, FAIL_STATUSES[runtime], "configured Agentera root cannot reach shared helper scripts", {
246
+ source,
247
+ path: candidate,
248
+ gap: "bundle_packaging",
249
+ details: helperMissing,
250
+ });
251
+ }
252
+ if (resolvePath(candidate) !== resolvePath(installRoot)) {
253
+ return mkCheck(name, WARN_STATUSES[runtime], "runtime points at a different valid Agentera install root", {
254
+ source,
255
+ path: candidate,
256
+ gap: RUNTIME_CONFIG_GAPS[runtime],
257
+ });
258
+ }
259
+ return mkCheck(name, PASS_STATUSES[runtime], HELPER_ACCESS_MESSAGE, { source, path: candidate });
260
+ }
261
+ export function binaryPath(runtime, env) {
262
+ return which(RUNTIME_BINARIES[runtime], env.PATH);
263
+ }
264
+ export function runtimeHostPathProblem(runtime, env) {
265
+ const binary = RUNTIME_BINARIES[runtime];
266
+ for (const entry of (env.PATH ?? "").split(path.delimiter)) {
267
+ if (!entry)
268
+ continue;
269
+ const candidate = path.join(entry, binary);
270
+ try {
271
+ if (fs.statSync(candidate).isDirectory()) {
272
+ return [candidate, `${binary} PATH candidate is a directory, not an executable`];
273
+ }
274
+ }
275
+ catch {
276
+ /* not present */
277
+ }
278
+ if (pathExists(candidate)) {
279
+ try {
280
+ fs.accessSync(candidate, fs.constants.X_OK);
281
+ }
282
+ catch {
283
+ return [candidate, `${binary} PATH candidate is not executable`];
284
+ }
285
+ }
286
+ }
287
+ return null;
288
+ }
289
+ export function runtimeResult(runtime, env, checks) {
290
+ const binary = binaryPath(runtime, env);
291
+ if (binary === null)
292
+ return runtimeSkip(runtime, env);
293
+ const binaryCheck = mkCheck(AVAILABILITY_CHECKS[runtime], PASS_STATUSES[runtime], `${RUNTIME_BINARIES[runtime]} executable found`, { source: "PATH", path: binary });
294
+ const allChecks = [binaryCheck, ...checks];
295
+ return {
296
+ runtime,
297
+ status: aggregateStatus(allChecks),
298
+ available: true,
299
+ binary,
300
+ checks: allChecks,
301
+ };
302
+ }
303
+ // HELPER_ACCESS_MESSAGE mirrors COPILOT_HELPER_MESSAGE (registry-derived).
304
+ export const HELPER_ACCESS_MESSAGE = diagnosticMessages("copilot")[0];
305
+ // ── OpenCode diagnostics ────────────────────────────────────────────
306
+ const OPENCODE_PLUGIN_CHECK = diagnosticCheckNames("opencode")[0];
307
+ const OPENCODE_HOME_CHECK = diagnosticCheckNames("opencode")[1];
308
+ const OPENCODE_COMMANDS_CHECK = diagnosticCheckNames("opencode")[2];
309
+ const OPENCODE_SKILL_PATHS_CHECK = diagnosticCheckNames("opencode")[3];
310
+ const OPENCODE_SUPPORT_CHECK = diagnosticCheckNames("opencode")[4];
311
+ const OC_MSG = diagnosticMessages("opencode");
312
+ const OPENCODE_COMMANDS_CURRENT_MESSAGE = OC_MSG[4];
313
+ const OPENCODE_COMMANDS_DRIFT_MESSAGE = OC_MSG[5];
314
+ const OPENCODE_SKILL_PATHS_CURRENT_MESSAGE = OC_MSG[6];
315
+ const OPENCODE_SKILL_PATHS_DRIFT_MESSAGE = OC_MSG[7];
316
+ const OPENCODE_SUPPORT_REFERENCES_PASS_MESSAGE = OC_MSG[8];
317
+ const OPENCODE_SUPPORT_REFERENCES_DRIFT_MESSAGE = OC_MSG[9];
318
+ const OPENCODE_PASS_STATUS = diagnosticStatusLabels("opencode")[0];
319
+ const OPENCODE_WARN_STATUS = diagnosticStatusLabels("opencode")[1];
320
+ const OC_GAP = diagnosticGapLabels("opencode");
321
+ const OPENCODE_COMMAND_DRIFT_GAP = OC_GAP[2];
322
+ const OPENCODE_SKILL_PATH_DRIFT_GAP = OC_GAP[3];
323
+ const OPENCODE_VALIDATION_DRIFT_GAP = OC_GAP[4];
324
+ export function opencodeConfigDir(home, env) {
325
+ const value = env.OPENCODE_CONFIG_DIR;
326
+ return value ? resolvePath(expanduser(value)) : path.join(home, ".config", "opencode");
327
+ }
328
+ export function opencodeCommandTemplate(name) {
329
+ return ("---\n" +
330
+ `description: "${OPENCODE_COMMAND_DESCRIPTIONS[name]}"\n` +
331
+ "agentera_managed: true\n" +
332
+ "---\n" +
333
+ `Load and execute the ${name} skill for this project.\n`);
334
+ }
335
+ export function hasManagedMarker(text) {
336
+ const lines = text.split("\n");
337
+ if (lines.length === 0 || lines[0] !== "---")
338
+ return false;
339
+ const closing = lines.indexOf("---", 1);
340
+ if (closing === -1)
341
+ return false;
342
+ return lines.slice(1, closing).some((line) => line.trim() === "agentera_managed: true");
343
+ }
344
+ export function diagnoseOpencodeCommands(home, env) {
345
+ const commandsDir = path.join(opencodeConfigDir(home, env), "commands");
346
+ const missing = [];
347
+ const stale = [];
348
+ const userOwned = [];
349
+ for (const name of OPENCODE_SKILL_NAMES) {
350
+ const command = path.join(commandsDir, `${name}.md`);
351
+ const expected = opencodeCommandTemplate(name);
352
+ let actual;
353
+ try {
354
+ actual = fs.readFileSync(command, "utf8");
355
+ }
356
+ catch {
357
+ missing.push(name);
358
+ continue;
359
+ }
360
+ if (actual === expected)
361
+ continue;
362
+ if (hasManagedMarker(actual))
363
+ stale.push(name);
364
+ else
365
+ userOwned.push(name);
366
+ }
367
+ if (missing.length === 0 && stale.length === 0) {
368
+ const details = userOwned.map((name) => `user-owned command preserved: ${name}`);
369
+ return mkCheck(OPENCODE_COMMANDS_CHECK, OPENCODE_PASS_STATUS, OPENCODE_COMMANDS_CURRENT_MESSAGE, {
370
+ path: commandsDir,
371
+ details,
372
+ });
373
+ }
374
+ const details = [...missing.map((n) => `missing: ${n}`), ...stale.map((n) => `stale: ${n}`)];
375
+ if (userOwned.length > 0)
376
+ details.push(...userOwned.map((n) => `user-owned command preserved: ${n}`));
377
+ details.push("action: start OpenCode with the Agentera plugin to restore managed commands");
378
+ return mkCheck(OPENCODE_COMMANDS_CHECK, OPENCODE_WARN_STATUS, OPENCODE_COMMANDS_DRIFT_MESSAGE, {
379
+ path: commandsDir,
380
+ gap: OPENCODE_COMMAND_DRIFT_GAP,
381
+ details,
382
+ });
383
+ }
384
+ function isAgenteraManagedSkillPath(target, name) {
385
+ let linkTarget;
386
+ try {
387
+ linkTarget = fs.readlinkSync(target);
388
+ }
389
+ catch {
390
+ return false;
391
+ }
392
+ const normalized = linkTarget.toLowerCase();
393
+ return normalized.includes("agentera") || path.basename(linkTarget) === name;
394
+ }
395
+ function isSymlink(p) {
396
+ try {
397
+ return fs.lstatSync(p).isSymbolicLink();
398
+ }
399
+ catch {
400
+ return false;
401
+ }
402
+ }
403
+ export function diagnoseOpencodeSkillPaths(installRoot, home, env) {
404
+ const skillsDir = path.join(opencodeConfigDir(home, env), "skills");
405
+ const missing = [];
406
+ const broken = [];
407
+ const userOwned = [];
408
+ const missingSource = [];
409
+ for (const name of OPENCODE_SKILL_NAMES) {
410
+ const source = path.join(installRoot, "skills", name, "SKILL.md");
411
+ const target = path.join(skillsDir, name);
412
+ if (!isFile(source)) {
413
+ missingSource.push(name);
414
+ continue;
415
+ }
416
+ if (!pathExists(target) && !isSymlink(target)) {
417
+ missing.push(name);
418
+ continue;
419
+ }
420
+ if (isFile(path.join(target, "SKILL.md")))
421
+ continue;
422
+ if (isAgenteraManagedSkillPath(target, name))
423
+ broken.push(name);
424
+ else
425
+ userOwned.push(name);
426
+ }
427
+ if (missing.length === 0 && broken.length === 0 && missingSource.length === 0) {
428
+ const details = userOwned.map((name) => `user-owned skill path preserved: ${name}`);
429
+ return mkCheck(OPENCODE_SKILL_PATHS_CHECK, OPENCODE_PASS_STATUS, OPENCODE_SKILL_PATHS_CURRENT_MESSAGE, {
430
+ path: skillsDir,
431
+ details,
432
+ });
433
+ }
434
+ const details = [...missing.map((n) => `missing: ${n}`), ...broken.map((n) => `broken: ${n}`)];
435
+ if (missingSource.length > 0) {
436
+ details.push(...missingSource.map((n) => `missing install source: ${n}`));
437
+ details.push(`action: ${OPENCODE_SKILL_INSTALL_COMMAND}`);
438
+ }
439
+ else {
440
+ details.push("action: start OpenCode with the Agentera plugin to restore managed skill paths");
441
+ }
442
+ if (userOwned.length > 0)
443
+ details.push(...userOwned.map((n) => `user-owned skill path preserved: ${n}`));
444
+ return mkCheck(OPENCODE_SKILL_PATHS_CHECK, OPENCODE_WARN_STATUS, OPENCODE_SKILL_PATHS_DRIFT_MESSAGE, {
445
+ path: skillsDir,
446
+ gap: OPENCODE_SKILL_PATH_DRIFT_GAP,
447
+ details,
448
+ });
449
+ }
450
+ // ── bundled reference validation ────────────────────────────────────
451
+ function trimChars(s, chars) {
452
+ let start = 0;
453
+ let end = s.length;
454
+ while (start < end && chars.includes(s[start]))
455
+ start++;
456
+ while (end > start && chars.includes(s[end - 1]))
457
+ end--;
458
+ return s.slice(start, end);
459
+ }
460
+ export function normalizeReference(raw) {
461
+ const candidate = trimChars(raw, "`'\"()[]{}.,:;");
462
+ if (!candidate || path.isAbsolute(candidate) || candidate.includes("\\"))
463
+ return null;
464
+ const parts = candidate.split("/");
465
+ if (parts.some((part) => part === "" || part === "." || part === ".."))
466
+ return null;
467
+ return candidate;
468
+ }
469
+ export function extractReferencePaths(text) {
470
+ const refs = [];
471
+ const seen = new Set();
472
+ const re = /(?<![\w/.$-])(?<path>references\/[A-Za-z0-9][A-Za-z0-9_./-]*)/g;
473
+ for (const match of text.matchAll(re)) {
474
+ const ref = normalizeReference(match.groups.path);
475
+ if (ref !== null && !seen.has(ref)) {
476
+ refs.push(ref);
477
+ seen.add(ref);
478
+ }
479
+ }
480
+ return refs;
481
+ }
482
+ function globSkillFiles(skillsDir) {
483
+ let entries;
484
+ try {
485
+ entries = fs.readdirSync(skillsDir);
486
+ }
487
+ catch {
488
+ return [];
489
+ }
490
+ const out = [];
491
+ for (const entry of entries) {
492
+ const skillFile = path.join(skillsDir, entry, "SKILL.md");
493
+ if (isFile(skillFile))
494
+ out.push(skillFile);
495
+ }
496
+ return out.sort();
497
+ }
498
+ export function diagnoseBundledReferenceValidation(installRoot) {
499
+ const skillsDir = path.join(installRoot, "skills");
500
+ const missing = [];
501
+ for (const skillFile of globSkillFiles(skillsDir)) {
502
+ const text = fs.readFileSync(skillFile, "utf8");
503
+ const parentName = path.basename(path.dirname(skillFile));
504
+ for (const ref of extractReferencePaths(text)) {
505
+ if (!pathExists(path.join(path.dirname(skillFile), ref))) {
506
+ missing.push(`${parentName}: ${ref}`);
507
+ }
508
+ }
509
+ }
510
+ if (missing.length === 0) {
511
+ return mkCheck(OPENCODE_SUPPORT_CHECK, OPENCODE_PASS_STATUS, OPENCODE_SUPPORT_REFERENCES_PASS_MESSAGE, {
512
+ path: skillsDir,
513
+ });
514
+ }
515
+ return mkCheck(OPENCODE_SUPPORT_CHECK, OPENCODE_WARN_STATUS, OPENCODE_SUPPORT_REFERENCES_DRIFT_MESSAGE, {
516
+ path: skillsDir,
517
+ gap: OPENCODE_VALIDATION_DRIFT_GAP,
518
+ details: missing,
519
+ });
520
+ }
521
+ export function smokeCheck(name, category, status, message, opts = {}) {
522
+ return {
523
+ name,
524
+ category,
525
+ status,
526
+ message,
527
+ command: opts.command ?? [],
528
+ path: opts.path ?? null,
529
+ details: opts.details ?? [],
530
+ };
531
+ }
532
+ // ===========================================================================
533
+ // Slice 3: per-runtime diagnostics
534
+ // ===========================================================================
535
+ // Claude
536
+ const CLAUDE_ROOT_CHECK = diagnosticCheckNames("claude")[0];
537
+ const CLAUDE_MISSING_ENV_MESSAGE = diagnosticMessages("claude")[1];
538
+ const CLAUDE_WARN_STATUS = diagnosticStatusLabels("claude")[1];
539
+ const CLAUDE_USER_ENVIRONMENT_GAP = diagnosticGapLabels("claude")[1];
540
+ // OpenCode (additional to slice 2)
541
+ const OPENCODE_PLUGIN_PRESENT_MESSAGE = OC_MSG[0];
542
+ const OPENCODE_PLUGIN_MISSING_MESSAGE = OC_MSG[1];
543
+ const OPENCODE_HOME_MISSING_MESSAGE = OC_MSG[3];
544
+ const OPENCODE_RUNTIME_CONFIG_GAP = OC_GAP[0];
545
+ // Copilot
546
+ const COPILOT_HOME_CHECK = diagnosticCheckNames("copilot")[0];
547
+ const CO_MSG = diagnosticMessages("copilot");
548
+ const COPILOT_MISSING_MESSAGE = CO_MSG[1];
549
+ const COPILOT_RC_CONFIGURED_MESSAGE = CO_MSG[2];
550
+ const COPILOT_PASS_STATUS = diagnosticStatusLabels("copilot")[0];
551
+ const COPILOT_WARN_STATUS = diagnosticStatusLabels("copilot")[1];
552
+ const COPILOT_RUNTIME_CONFIG_GAP = diagnosticGapLabels("copilot")[0];
553
+ // Codex
554
+ const CODEX_HOME_CHECK = diagnosticCheckNames("codex")[0];
555
+ const CODEX_CONFIG_ERROR_MESSAGE = diagnosticMessages("codex")[1];
556
+ const CODEX_WARN_STATUS = diagnosticStatusLabels("codex")[1];
557
+ const CODEX_FAIL_STATUS = diagnosticStatusLabels("codex")[2];
558
+ const CODEX_RUNTIME_CONFIG_GAP = diagnosticGapLabels("codex")[0];
559
+ // Cursor
560
+ const CURSOR_HOME_CHECK = diagnosticCheckNames("cursor")[0];
561
+ const CURSOR_HOOKS_CHECK = diagnosticCheckNames("cursor")[1];
562
+ const CURSOR_AGENTS_CHECK = diagnosticCheckNames("cursor")[2];
563
+ const CURSOR_HELPER_MESSAGE = diagnosticMessages("cursor")[0];
564
+ const CURSOR_PASS_STATUS = diagnosticStatusLabels("cursor")[0];
565
+ const CURSOR_WARN_STATUS = diagnosticStatusLabels("cursor")[1];
566
+ const CURSOR_FAIL_STATUS = diagnosticStatusLabels("cursor")[2];
567
+ const CURSOR_USER_ENVIRONMENT_GAP = diagnosticGapLabels("cursor")[1];
568
+ const CURSOR_HOOK_DRIFT_GAP = diagnosticGapLabels("cursor")[2];
569
+ const CURSOR_AGENT_DRIFT_GAP = diagnosticGapLabels("cursor")[3];
570
+ // Cursor-agent
571
+ const CURSOR_AGENT_HOME_CHECK = diagnosticCheckNames("cursor-agent")[0];
572
+ const CURSOR_AGENT_WARN_STATUS = diagnosticStatusLabels("cursor-agent")[1];
573
+ const CURSOR_AGENT_USER_ENVIRONMENT_GAP = diagnosticGapLabels("cursor-agent")[1];
574
+ export function diagnoseClaude(installRoot, _home, env) {
575
+ const value = env.CLAUDE_PLUGIN_ROOT;
576
+ let checks;
577
+ if (!value) {
578
+ checks = [
579
+ mkCheck(CLAUDE_ROOT_CHECK, CLAUDE_WARN_STATUS, CLAUDE_MISSING_ENV_MESSAGE, {
580
+ source: "environment",
581
+ gap: CLAUDE_USER_ENVIRONMENT_GAP,
582
+ }),
583
+ ];
584
+ }
585
+ else {
586
+ checks = [
587
+ configuredRootCheck("claude", CLAUDE_ROOT_CHECK, resolvePath(expanduser(value)), installRoot, "environment"),
588
+ ];
589
+ }
590
+ return runtimeResult("claude", env, checks);
591
+ }
592
+ export function diagnoseOpencode(installRoot, home, env) {
593
+ const checks = [];
594
+ const configDir = opencodeConfigDir(home, env);
595
+ const plugin = path.join(configDir, "plugins", "agentera.js");
596
+ if (isFile(plugin)) {
597
+ checks.push(mkCheck(OPENCODE_PLUGIN_CHECK, OPENCODE_PASS_STATUS, OPENCODE_PLUGIN_PRESENT_MESSAGE, { path: plugin }));
598
+ }
599
+ else {
600
+ checks.push(mkCheck(OPENCODE_PLUGIN_CHECK, OPENCODE_WARN_STATUS, OPENCODE_PLUGIN_MISSING_MESSAGE, {
601
+ path: plugin,
602
+ gap: OPENCODE_RUNTIME_CONFIG_GAP,
603
+ }));
604
+ }
605
+ if (env.AGENTERA_HOME) {
606
+ const p = resolvePath(expanduser(env.AGENTERA_HOME));
607
+ checks.push(configuredRootCheck("opencode", OPENCODE_HOME_CHECK, p, installRoot, "environment"));
608
+ }
609
+ else {
610
+ const candidates = [
611
+ ["default-install-root", path.join(home, ".agents", "agentera")],
612
+ ["default-skill-root", path.join(home, ".agents", "skills", "agentera")],
613
+ ];
614
+ const existing = candidates.filter(([, p]) => pathExists(p));
615
+ if (existing.length > 0) {
616
+ const [source, p] = existing[0];
617
+ checks.push(configuredRootCheck("opencode", OPENCODE_HOME_CHECK, resolvePath(p), installRoot, source));
618
+ }
619
+ else {
620
+ checks.push(mkCheck(OPENCODE_HOME_CHECK, OPENCODE_WARN_STATUS, OPENCODE_HOME_MISSING_MESSAGE, {
621
+ source: "environment/defaults",
622
+ gap: OPENCODE_RUNTIME_CONFIG_GAP,
623
+ }));
624
+ }
625
+ }
626
+ checks.push(diagnoseOpencodeCommands(home, env));
627
+ checks.push(diagnoseOpencodeSkillPaths(installRoot, home, env));
628
+ checks.push(diagnoseBundledReferenceValidation(installRoot));
629
+ return runtimeResult("opencode", env, checks);
630
+ }
631
+ function copilotRcPaths(home) {
632
+ return [
633
+ path.join(home, ".bashrc"),
634
+ path.join(home, ".zshrc"),
635
+ path.join(home, ".config", "fish", "config.fish"),
636
+ ];
637
+ }
638
+ function extractCopilotMarkerRoot(text) {
639
+ const lines = text.split(/\r\n|\r|\n/);
640
+ for (let index = 0; index < lines.length; index++) {
641
+ if (lines[index].replace(/\s+$/, "") !== COPILOT_MARKER)
642
+ continue;
643
+ if (index + 1 >= lines.length)
644
+ return null;
645
+ const exportLine = lines[index + 1].trim();
646
+ for (const prefix of ['export AGENTERA_HOME="', 'set -x AGENTERA_HOME "']) {
647
+ if (exportLine.startsWith(prefix) && exportLine.endsWith('"')) {
648
+ return exportLine.slice(prefix.length, -1);
649
+ }
650
+ }
651
+ }
652
+ return null;
653
+ }
654
+ function copilotShellManualBoundaryMessage(prefix = "") {
655
+ const lead = prefix ? `${prefix} ` : "";
656
+ return (`${lead}Agentera will not edit shell startup files; cleanup is a ` +
657
+ "user-owned manual boundary. For Copilot app context, pass " +
658
+ "AGENTERA_HOME for a single invocation or use runtime-native " +
659
+ "environment support when available.");
660
+ }
661
+ export function diagnoseCopilot(installRoot, home, env) {
662
+ const value = env.AGENTERA_HOME;
663
+ if (value) {
664
+ const checks = [
665
+ configuredRootCheck("copilot", COPILOT_HOME_CHECK, resolvePath(expanduser(value)), installRoot, "environment"),
666
+ ];
667
+ return runtimeResult("copilot", env, checks);
668
+ }
669
+ for (const rcPath of copilotRcPaths(home)) {
670
+ if (!isFile(rcPath))
671
+ continue;
672
+ const rcText = fs.readFileSync(rcPath, "utf8");
673
+ const markerRoot = extractCopilotMarkerRoot(rcText);
674
+ if (markerRoot === null)
675
+ continue;
676
+ const check = configuredRootCheck("copilot", COPILOT_HOME_CHECK, resolvePath(expanduser(markerRoot)), installRoot, rcPath);
677
+ if (check.status === COPILOT_PASS_STATUS) {
678
+ check.message = COPILOT_RC_CONFIGURED_MESSAGE;
679
+ }
680
+ check.message = copilotShellManualBoundaryMessage(check.message);
681
+ return runtimeResult("copilot", env, [check]);
682
+ }
683
+ const checks = [
684
+ mkCheck(COPILOT_HOME_CHECK, COPILOT_WARN_STATUS, COPILOT_MISSING_MESSAGE, {
685
+ source: "environment/rc",
686
+ gap: COPILOT_RUNTIME_CONFIG_GAP,
687
+ }),
688
+ ];
689
+ return runtimeResult("copilot", env, checks);
690
+ }
691
+ export function readCodexAgenteraHome(configPath) {
692
+ if (!isFile(configPath))
693
+ return [null, "missing"];
694
+ let data;
695
+ try {
696
+ data = parseTomlLocal(fs.readFileSync(configPath, "utf8"));
697
+ }
698
+ catch (exc) {
699
+ return [null, `invalid TOML: ${exc.message}`];
700
+ }
701
+ const policy = data.shell_environment_policy;
702
+ if (!policy || typeof policy !== "object" || Array.isArray(policy)) {
703
+ return [null, "missing shell_environment_policy.set.AGENTERA_HOME"];
704
+ }
705
+ const setTable = policy.set;
706
+ if (!setTable || typeof setTable !== "object" || Array.isArray(setTable)) {
707
+ return [null, "missing shell_environment_policy.set.AGENTERA_HOME"];
708
+ }
709
+ const value = setTable.AGENTERA_HOME;
710
+ if (typeof value !== "string" || !value) {
711
+ return [null, "missing shell_environment_policy.set.AGENTERA_HOME"];
712
+ }
713
+ return [value, null];
714
+ }
715
+ export function diagnoseCodex(installRoot, home, env) {
716
+ const config = path.join(home, ".codex", "config.toml");
717
+ const [value, error] = readCodexAgenteraHome(config);
718
+ let checks;
719
+ if (error !== null) {
720
+ const status = error === "missing" || error.startsWith("missing ") ? CODEX_WARN_STATUS : CODEX_FAIL_STATUS;
721
+ checks = [
722
+ mkCheck(CODEX_HOME_CHECK, status, `${CODEX_CONFIG_ERROR_MESSAGE}: ${error}`, {
723
+ source: config,
724
+ path: config,
725
+ gap: CODEX_RUNTIME_CONFIG_GAP,
726
+ }),
727
+ ];
728
+ }
729
+ else {
730
+ checks = [
731
+ configuredRootCheck("codex", CODEX_HOME_CHECK, resolvePath(expanduser(value)), installRoot, config),
732
+ ];
733
+ }
734
+ return runtimeResult("codex", env, checks);
735
+ }
736
+ function globMdFiles(dir) {
737
+ let entries;
738
+ try {
739
+ entries = fs.readdirSync(dir);
740
+ }
741
+ catch {
742
+ return [];
743
+ }
744
+ return entries.filter((e) => e.endsWith(".md") && isFile(path.join(dir, e))).sort();
745
+ }
746
+ export function diagnoseCursor(installRoot, home, env) {
747
+ const checks = [];
748
+ const value = env.AGENTERA_HOME;
749
+ if (value) {
750
+ checks.push(configuredRootCheck("cursor", CURSOR_HOME_CHECK, resolvePath(expanduser(value)), installRoot, "environment"));
751
+ }
752
+ else {
753
+ checks.push(mkCheck(CURSOR_HOME_CHECK, CURSOR_WARN_STATUS, CURSOR_HELPER_MESSAGE, {
754
+ source: "environment",
755
+ gap: CURSOR_USER_ENVIRONMENT_GAP,
756
+ }));
757
+ }
758
+ const hooksPath = path.join(installRoot, ".cursor", "hooks.json");
759
+ if (isFile(hooksPath)) {
760
+ const text = fs.readFileSync(hooksPath, "utf8");
761
+ if (text.includes("cursor_session_start.py") && text.includes("cursor_pre_tool_use.py")) {
762
+ checks.push(mkCheck(CURSOR_HOOKS_CHECK, CURSOR_PASS_STATUS, "Cursor hooks.json is present and references Agentera helpers", {
763
+ path: hooksPath,
764
+ }));
765
+ }
766
+ else {
767
+ checks.push(mkCheck(CURSOR_HOOKS_CHECK, CURSOR_WARN_STATUS, "Cursor hooks.json is present but missing Agentera hook references", {
768
+ path: hooksPath,
769
+ gap: CURSOR_HOOK_DRIFT_GAP,
770
+ }));
771
+ }
772
+ }
773
+ else {
774
+ checks.push(mkCheck(CURSOR_HOOKS_CHECK, CURSOR_FAIL_STATUS, "Cursor hooks.json is missing", {
775
+ path: hooksPath,
776
+ gap: CURSOR_HOOK_DRIFT_GAP,
777
+ }));
778
+ }
779
+ const agentsDir = path.join(installRoot, ".cursor", "agents");
780
+ const managed = globMdFiles(agentsDir);
781
+ if (managed.length >= 12) {
782
+ checks.push(mkCheck(CURSOR_AGENTS_CHECK, CURSOR_PASS_STATUS, "Cursor managed capability agents are current", {
783
+ path: agentsDir,
784
+ details: managed,
785
+ }));
786
+ }
787
+ else if (managed.length > 0) {
788
+ checks.push(mkCheck(CURSOR_AGENTS_CHECK, CURSOR_WARN_STATUS, `Cursor managed agents incomplete (${managed.length}/12)`, {
789
+ path: agentsDir,
790
+ gap: CURSOR_AGENT_DRIFT_GAP,
791
+ }));
792
+ }
793
+ else {
794
+ checks.push(mkCheck(CURSOR_AGENTS_CHECK, CURSOR_FAIL_STATUS, "Cursor managed capability agents are missing", {
795
+ path: agentsDir,
796
+ gap: CURSOR_AGENT_DRIFT_GAP,
797
+ }));
798
+ }
799
+ return runtimeResult("cursor", env, checks);
800
+ }
801
+ export function diagnoseCursorAgent(installRoot, _home, env) {
802
+ const checks = [];
803
+ const value = env.AGENTERA_HOME;
804
+ if (value) {
805
+ checks.push(configuredRootCheck("cursor-agent", CURSOR_AGENT_HOME_CHECK, resolvePath(expanduser(value)), installRoot, "environment"));
806
+ }
807
+ else {
808
+ checks.push(mkCheck(CURSOR_AGENT_HOME_CHECK, CURSOR_AGENT_WARN_STATUS, "cursor-agent helper access depends on AGENTERA_HOME or shell rc configuration", { source: "environment", gap: CURSOR_AGENT_USER_ENVIRONMENT_GAP }));
809
+ }
810
+ return runtimeResult("cursor-agent", env, checks);
811
+ }
812
+ export const DIAGNOSTICS = {
813
+ claude: diagnoseClaude,
814
+ opencode: diagnoseOpencode,
815
+ copilot: diagnoseCopilot,
816
+ codex: diagnoseCodex,
817
+ cursor: diagnoseCursor,
818
+ "cursor-agent": diagnoseCursorAgent,
819
+ };
820
+ // ===========================================================================
821
+ // Slice 4: build_report, renderers, installer, CLI
822
+ // ===========================================================================
823
+ import { planChange as codexPlanChange } from "./codex.js";
824
+ /** Python json.dumps(obj, indent=2, sort_keys=True, ensure_ascii=True). */
825
+ function jsonAscii(str) {
826
+ let out = '"';
827
+ for (const ch of str) {
828
+ const cp = ch.codePointAt(0);
829
+ if (ch === '"')
830
+ out += '\\"';
831
+ else if (ch === "\\")
832
+ out += "\\\\";
833
+ else if (cp === 0x08)
834
+ out += "\\b";
835
+ else if (cp === 0x09)
836
+ out += "\\t";
837
+ else if (cp === 0x0a)
838
+ out += "\\n";
839
+ else if (cp === 0x0c)
840
+ out += "\\f";
841
+ else if (cp === 0x0d)
842
+ out += "\\r";
843
+ else if (cp < 0x20)
844
+ out += "\\u" + cp.toString(16).padStart(4, "0");
845
+ else if (cp < 0x80)
846
+ out += ch;
847
+ else if (cp > 0xffff) {
848
+ const v = cp - 0x10000;
849
+ out += "\\u" + (0xd800 + (v >> 10)).toString(16).padStart(4, "0");
850
+ out += "\\u" + (0xdc00 + (v & 0x3ff)).toString(16).padStart(4, "0");
851
+ }
852
+ else
853
+ out += "\\u" + cp.toString(16).padStart(4, "0");
854
+ }
855
+ return out + '"';
856
+ }
857
+ export function pyJsonIndent(value, level = 0) {
858
+ const pad = " ".repeat(level);
859
+ const padIn = " ".repeat(level + 1);
860
+ if (value === null || value === undefined)
861
+ return "null";
862
+ if (value === true)
863
+ return "true";
864
+ if (value === false)
865
+ return "false";
866
+ if (typeof value === "number")
867
+ return Number.isInteger(value) ? String(value) : String(value);
868
+ if (typeof value === "string")
869
+ return jsonAscii(value);
870
+ if (Array.isArray(value)) {
871
+ if (value.length === 0)
872
+ return "[]";
873
+ const items = value.map((v) => padIn + pyJsonIndent(v, level + 1));
874
+ return "[\n" + items.join(",\n") + "\n" + pad + "]";
875
+ }
876
+ if (typeof value === "object") {
877
+ const keys = Object.keys(value).sort();
878
+ if (keys.length === 0)
879
+ return "{}";
880
+ const items = keys.map((k) => padIn + jsonAscii(k) + ": " + pyJsonIndent(value[k], level + 1));
881
+ return "{\n" + items.join(",\n") + "\n" + pad + "}";
882
+ }
883
+ return "null";
884
+ }
885
+ function summarize(runtimes) {
886
+ return summarizeStatuses(runtimes);
887
+ }
888
+ export function buildReport(opts = {}) {
889
+ const runtimes = opts.runtimes ?? RUNTIMES;
890
+ const liveModelAllowed = opts.liveModelAllowed ?? false;
891
+ const sourceEnv = { ...(opts.env ?? process.env) };
892
+ const rootReport = classifyInstallRoot(opts.installRoot ?? null, sourceEnv);
893
+ const rootPath = rootReport.path ? rootReport.path : null;
894
+ const homePath = resolvePath(expanduser(opts.home ?? os.homedir()));
895
+ const runtimeReports = {};
896
+ if (rootPath === null || rootReport.status === "fail") {
897
+ for (const runtime of runtimes) {
898
+ runtimeReports[runtime] = runtimeResult(runtime, sourceEnv, [
899
+ mkCheck("install_root", "fail", "runtime diagnosis requires a valid Agentera install root", {
900
+ gap: rootReport.gap,
901
+ details: rootReport.missing,
902
+ }),
903
+ ]);
904
+ }
905
+ }
906
+ else {
907
+ for (const runtime of runtimes) {
908
+ runtimeReports[runtime] = DIAGNOSTICS[runtime](rootPath, homePath, sourceEnv);
909
+ }
910
+ }
911
+ const summaryCounts = summarize(runtimeReports);
912
+ const emptySummary = {};
913
+ for (const status of STATUSES)
914
+ emptySummary[status] = 0;
915
+ let smokeReport = {
916
+ enabled: false,
917
+ liveModelAllowed,
918
+ modelCallsAttempted: false,
919
+ summary: emptySummary,
920
+ checks: [],
921
+ };
922
+ if (opts.runSmoke) {
923
+ throw new Error("setup doctor --smoke is not yet ported (pending Phase 9 node hook invocation)");
924
+ }
925
+ const ok = rootReport.status !== "fail" && summaryCounts.fail === 0 && smokeReport.summary.fail === 0;
926
+ return {
927
+ schemaVersion: SCHEMA_VERSION,
928
+ ok,
929
+ installRoot: rootReport,
930
+ runtimes: runtimeReports,
931
+ summary: summaryCounts,
932
+ smoke: smokeReport,
933
+ };
934
+ }
935
+ export function renderHuman(report) {
936
+ const lines = [
937
+ "Agentera setup doctor",
938
+ `install root: ${report.installRoot.status} - ${report.installRoot.message}`,
939
+ ];
940
+ if (report.installRoot.path)
941
+ lines.push(` path: ${report.installRoot.path}`);
942
+ if (report.installRoot.missing && report.installRoot.missing.length > 0) {
943
+ lines.push(" missing: " + report.installRoot.missing.join(", "));
944
+ }
945
+ for (const [runtime, result] of Object.entries(report.runtimes)) {
946
+ lines.push(`${runtime}: ${result.status}`);
947
+ for (const check of result.checks) {
948
+ const suffix = check.gap ? ` [${check.gap}]` : "";
949
+ lines.push(` - ${check.name}: ${check.status} - ${check.message}${suffix}`);
950
+ if (check.path)
951
+ lines.push(` path: ${check.path}`);
952
+ if (check.details && check.details.length > 0)
953
+ lines.push(" details: " + check.details.join(", "));
954
+ }
955
+ }
956
+ const smoke = report.smoke ?? {};
957
+ if (smoke.enabled) {
958
+ lines.push("smoke: enabled");
959
+ lines.push(` model calls attempted: ${pyBool(smoke.modelCallsAttempted)}`);
960
+ for (const check of (smoke.checks ?? [])) {
961
+ lines.push(` - ${check.name}: ${check.status} - ${check.message} [${check.category}]`);
962
+ if (check.path)
963
+ lines.push(` path: ${check.path}`);
964
+ if (check.details && check.details.length > 0)
965
+ lines.push(" details: " + check.details.join(", "));
966
+ }
967
+ }
968
+ return lines.join("\n");
969
+ }
970
+ function pyBool(value) {
971
+ return value ? "True" : "False";
972
+ }
973
+ export function renderInstaller(installer) {
974
+ const lines = ["Agentera setup installer", `status: ${installer.message}`];
975
+ if (!installer.changes || installer.changes.length === 0)
976
+ return lines.join("\n");
977
+ for (const change of installer.changes) {
978
+ lines.push(`${change.runtime}: ${change.status}`);
979
+ lines.push(` target: ${change.target || "(none)"}`);
980
+ lines.push(` reason: ${change.reason}`);
981
+ lines.push(` action: ${change.action} - ${change.message}`);
982
+ }
983
+ if (installer.afterDoctor !== null && installer.afterDoctor !== undefined) {
984
+ const after = installer.afterDoctor;
985
+ lines.push(`doctor after install: ${after.ok ? "pass" : "fail"} (summary: ${pyDict(after.summary)})`);
986
+ }
987
+ else if (installer.summary.pending && !installer.dryRun) {
988
+ lines.push("confirmation required: re-run with --yes to apply these changes");
989
+ }
990
+ return lines.join("\n");
991
+ }
992
+ /** Python str(dict) repr for the summary line. */
993
+ function pyDict(obj) {
994
+ const parts = Object.entries(obj).map(([k, v]) => `'${k}': ${typeof v === "string" ? `'${v}'` : v}`);
995
+ return "{" + parts.join(", ") + "}";
996
+ }
997
+ export function publicInstaller(installer) {
998
+ if (installer === null)
999
+ return null;
1000
+ const pub = { ...installer };
1001
+ pub.changes = installer.changes.map((change) => {
1002
+ const c = {};
1003
+ for (const [key, value] of Object.entries(change)) {
1004
+ if (key !== "newText" && key !== "diff")
1005
+ c[key] = value;
1006
+ }
1007
+ return c;
1008
+ });
1009
+ return pub;
1010
+ }
1011
+ // ── installer ───────────────────────────────────────────────────────
1012
+ function installerChange(opts) {
1013
+ return {
1014
+ runtime: opts.runtime,
1015
+ target: opts.target,
1016
+ reason: opts.reason,
1017
+ status: opts.status,
1018
+ action: opts.action,
1019
+ message: opts.message,
1020
+ newText: opts.newText ?? "",
1021
+ diff: opts.diff ?? "",
1022
+ };
1023
+ }
1024
+ function fixableReason(runtimeReport, checkName, gaps = null) {
1025
+ if (!runtimeReport.available)
1026
+ return null;
1027
+ const runtime = String(runtimeReport.runtime ?? "");
1028
+ const fixableGaps = gaps ?? INSTALLER_FIXABLE_GAPS[runtime] ?? [];
1029
+ for (const check of (runtimeReport.checks ?? [])) {
1030
+ if (check.name !== checkName)
1031
+ continue;
1032
+ if (check.status !== WARN_STATUSES[runtime] && check.status !== FAIL_STATUSES[runtime])
1033
+ continue;
1034
+ if (!fixableGaps.includes(check.gap))
1035
+ continue;
1036
+ return String(check.message || "doctor found a fixable setup gap");
1037
+ }
1038
+ return null;
1039
+ }
1040
+ function planCodexInstallerChange(installRoot, home, runtimeReport) {
1041
+ const reason = fixableReason(runtimeReport, CODEX_HOME_CHECK, [CODEX_RUNTIME_CONFIG_GAP]);
1042
+ if (reason === null)
1043
+ return null;
1044
+ const target = path.join(home, ".codex", "config.toml");
1045
+ let outcome;
1046
+ try {
1047
+ const currentText = pathExists(target) ? fs.readFileSync(target, "utf8") : null;
1048
+ outcome = codexPlanChange(currentText, installRoot, { force: false });
1049
+ }
1050
+ catch (exc) {
1051
+ return installerChange({
1052
+ runtime: "codex",
1053
+ target,
1054
+ reason,
1055
+ status: "blocked",
1056
+ action: "blocked",
1057
+ message: `cannot safely plan Codex config change: ${exc.message}`,
1058
+ });
1059
+ }
1060
+ if (outcome.action === "noop") {
1061
+ return installerChange({ runtime: "codex", target, reason, status: "noop", action: outcome.action, message: outcome.message });
1062
+ }
1063
+ if (outcome.action === "conflict") {
1064
+ return installerChange({
1065
+ runtime: "codex",
1066
+ target,
1067
+ reason,
1068
+ status: "blocked",
1069
+ action: outcome.action,
1070
+ message: outcome.message,
1071
+ diff: outcome.diff,
1072
+ });
1073
+ }
1074
+ return installerChange({
1075
+ runtime: "codex",
1076
+ target,
1077
+ reason,
1078
+ status: "pending",
1079
+ action: outcome.action,
1080
+ message: outcome.message,
1081
+ newText: outcome.newText,
1082
+ diff: outcome.diff,
1083
+ });
1084
+ }
1085
+ function planCopilotInstallerChange() {
1086
+ return null;
1087
+ }
1088
+ function summarizeInstaller(changes) {
1089
+ const statuses = ["pending", "applied", "noop", "blocked", "failed"];
1090
+ const summary = {};
1091
+ for (const status of statuses)
1092
+ summary[status] = 0;
1093
+ for (const change of changes)
1094
+ summary[change.status] += 1;
1095
+ return summary;
1096
+ }
1097
+ export function buildInstallerPlan(report, opts) {
1098
+ const changes = [];
1099
+ const rootPath = report.installRoot?.path;
1100
+ if (!rootPath || report.installRoot?.status === "fail") {
1101
+ return {
1102
+ schemaVersion: INSTALLER_SCHEMA_VERSION,
1103
+ confirmed: opts.confirmed,
1104
+ dryRun: opts.dryRun,
1105
+ changes,
1106
+ summary: summarizeInstaller(changes),
1107
+ afterDoctor: null,
1108
+ message: "installer requires a valid Agentera install root",
1109
+ };
1110
+ }
1111
+ const installRoot = rootPath;
1112
+ for (const runtime of opts.runtimes) {
1113
+ const runtimeReport = report.runtimes[runtime];
1114
+ let change = null;
1115
+ if (runtime === "codex")
1116
+ change = planCodexInstallerChange(installRoot, opts.home, runtimeReport);
1117
+ else if (runtime === "copilot")
1118
+ change = planCopilotInstallerChange();
1119
+ if (change !== null)
1120
+ changes.push(change);
1121
+ }
1122
+ return {
1123
+ schemaVersion: INSTALLER_SCHEMA_VERSION,
1124
+ confirmed: opts.confirmed,
1125
+ dryRun: opts.dryRun,
1126
+ changes,
1127
+ summary: summarizeInstaller(changes),
1128
+ afterDoctor: null,
1129
+ message: changes.length === 0 ? "no installer changes needed" : "installer changes planned",
1130
+ };
1131
+ }
1132
+ export function applyInstallerPlan(plan) {
1133
+ for (const change of plan.changes) {
1134
+ if (change.status !== "pending")
1135
+ continue;
1136
+ const target = change.target;
1137
+ try {
1138
+ fs.mkdirSync(path.dirname(target), { recursive: true });
1139
+ fs.writeFileSync(target, change.newText, "utf8");
1140
+ }
1141
+ catch (exc) {
1142
+ change.status = "failed";
1143
+ change.message = `error writing ${target}: ${exc.message}`;
1144
+ continue;
1145
+ }
1146
+ change.status = "applied";
1147
+ change.message = `wrote ${target}: ${String(change.message).replaceAll("would ", "")}`;
1148
+ }
1149
+ plan.summary = summarizeInstaller(plan.changes);
1150
+ }
1151
+ export function doctorMain(argv = [], io = {}) {
1152
+ const writeOut = io.out ?? ((text) => process.stdout.write(text));
1153
+ const writeErr = io.err ?? ((text) => process.stderr.write(text));
1154
+ const out = (line) => writeOut(line + "\n");
1155
+ const env = io.env ?? process.env;
1156
+ const args = {
1157
+ installRoot: null,
1158
+ home: null,
1159
+ runtime: [],
1160
+ smoke: false,
1161
+ install: false,
1162
+ yes: false,
1163
+ dryRun: false,
1164
+ allowLiveModel: false,
1165
+ json: false,
1166
+ };
1167
+ const valueFlag = (a, name) => {
1168
+ if (a === name)
1169
+ return "__NEXT__";
1170
+ if (a.startsWith(name + "="))
1171
+ return a.slice(name.length + 1);
1172
+ return null;
1173
+ };
1174
+ for (let i = 0; i < argv.length; i++) {
1175
+ const a = argv[i];
1176
+ let v;
1177
+ if ((v = valueFlag(a, "--install-root")) !== null)
1178
+ args.installRoot = v === "__NEXT__" ? argv[++i] : v;
1179
+ else if ((v = valueFlag(a, "--home")) !== null)
1180
+ args.home = v === "__NEXT__" ? argv[++i] : v;
1181
+ else if ((v = valueFlag(a, "--runtime")) !== null) {
1182
+ const rt = v === "__NEXT__" ? argv[++i] : v;
1183
+ if (!RUNTIMES.includes(rt)) {
1184
+ writeErr(`setup_doctor: error: argument --runtime: invalid choice: '${rt}'\n`);
1185
+ return 2;
1186
+ }
1187
+ args.runtime.push(rt);
1188
+ }
1189
+ else if (a === "--smoke")
1190
+ args.smoke = true;
1191
+ else if (a === "--install")
1192
+ args.install = true;
1193
+ else if (a === "--yes")
1194
+ args.yes = true;
1195
+ else if (a === "--dry-run")
1196
+ args.dryRun = true;
1197
+ else if (a === "--allow-live-model")
1198
+ args.allowLiveModel = true;
1199
+ else if (a === "--json")
1200
+ args.json = true;
1201
+ else {
1202
+ writeErr(`setup_doctor: error: unrecognized arguments: ${a}\n`);
1203
+ return 2;
1204
+ }
1205
+ }
1206
+ if (args.yes && !args.install) {
1207
+ writeErr("setup_doctor: error: --yes requires --install\n");
1208
+ return 2;
1209
+ }
1210
+ if (args.dryRun && !args.install) {
1211
+ writeErr("setup_doctor: error: --dry-run requires --install\n");
1212
+ return 2;
1213
+ }
1214
+ const runtimes = args.runtime.length > 0 ? args.runtime : RUNTIMES;
1215
+ const sourceEnv = { ...env };
1216
+ const home = resolvePath(expanduser(args.home ?? os.homedir()));
1217
+ const report = buildReport({
1218
+ installRoot: args.installRoot,
1219
+ home,
1220
+ env: sourceEnv,
1221
+ runtimes,
1222
+ runSmoke: args.smoke,
1223
+ liveModelAllowed: args.allowLiveModel,
1224
+ });
1225
+ let installer = null;
1226
+ if (args.install) {
1227
+ installer = buildInstallerPlan(report, {
1228
+ home,
1229
+ env: sourceEnv,
1230
+ runtimes,
1231
+ confirmed: args.yes,
1232
+ dryRun: args.dryRun,
1233
+ });
1234
+ if (args.yes) {
1235
+ applyInstallerPlan(installer);
1236
+ installer.afterDoctor = buildReport({
1237
+ installRoot: args.installRoot,
1238
+ home,
1239
+ env: sourceEnv,
1240
+ runtimes,
1241
+ runSmoke: args.smoke,
1242
+ liveModelAllowed: args.allowLiveModel,
1243
+ });
1244
+ }
1245
+ }
1246
+ if (args.json) {
1247
+ const payload = installer !== null ? { doctor: report, installer: publicInstaller(installer) } : report;
1248
+ out(pyJsonIndent(payload));
1249
+ }
1250
+ else {
1251
+ out(renderHuman(report));
1252
+ if (installer !== null) {
1253
+ out("");
1254
+ out(renderInstaller(installer));
1255
+ }
1256
+ }
1257
+ if (installer === null)
1258
+ return report.ok ? 0 : 1;
1259
+ if (installer.summary.failed || installer.summary.blocked)
1260
+ return 1;
1261
+ if (installer.summary.pending && !args.dryRun && !args.yes)
1262
+ return 1;
1263
+ if (installer.afterDoctor !== null && installer.afterDoctor !== undefined && !installer.afterDoctor.ok)
1264
+ return 1;
1265
+ if (!report.ok && !(installer.summary.pending || installer.summary.applied))
1266
+ return 1;
1267
+ return 0;
1268
+ }
1269
+ //# sourceMappingURL=doctor.js.map