@kindgi/cli 0.1.4-rc.5 → 0.1.5-rc.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 (329) hide show
  1. package/README.md +16 -10
  2. package/dist/build/defaults.d.ts +3 -1
  3. package/dist/build/defaults.d.ts.map +1 -1
  4. package/dist/build/defaults.js +44 -1
  5. package/dist/build/defaults.js.map +1 -1
  6. package/dist/build/java-image.d.ts +31 -0
  7. package/dist/build/java-image.d.ts.map +1 -0
  8. package/dist/build/java-image.js +146 -0
  9. package/dist/build/java-image.js.map +1 -0
  10. package/dist/build/pack-root.d.ts +1 -31
  11. package/dist/build/pack-root.d.ts.map +1 -1
  12. package/dist/build/pack-root.js +22 -2
  13. package/dist/build/pack-root.js.map +1 -1
  14. package/dist/build/runners.d.ts +44 -0
  15. package/dist/build/runners.d.ts.map +1 -1
  16. package/dist/build/scala-image.d.ts +21 -0
  17. package/dist/build/scala-image.d.ts.map +1 -0
  18. package/dist/build/scala-image.js +150 -0
  19. package/dist/build/scala-image.js.map +1 -0
  20. package/dist/cli-pin.d.ts +16 -0
  21. package/dist/cli-pin.d.ts.map +1 -0
  22. package/dist/cli-pin.js +66 -0
  23. package/dist/cli-pin.js.map +1 -0
  24. package/dist/commands/agents.d.ts.map +1 -1
  25. package/dist/commands/agents.js +9 -8
  26. package/dist/commands/agents.js.map +1 -1
  27. package/dist/commands/approvals.d.ts.map +1 -1
  28. package/dist/commands/approvals.js +30 -4
  29. package/dist/commands/approvals.js.map +1 -1
  30. package/dist/commands/artifacts.d.ts.map +1 -1
  31. package/dist/commands/artifacts.js +100 -37
  32. package/dist/commands/artifacts.js.map +1 -1
  33. package/dist/commands/auth.js +2 -2
  34. package/dist/commands/blocks.d.ts.map +1 -1
  35. package/dist/commands/blocks.js +5 -4
  36. package/dist/commands/blocks.js.map +1 -1
  37. package/dist/commands/build.d.ts +10 -2
  38. package/dist/commands/build.d.ts.map +1 -1
  39. package/dist/commands/build.js +109 -16
  40. package/dist/commands/build.js.map +1 -1
  41. package/dist/commands/capabilities.d.ts.map +1 -1
  42. package/dist/commands/capabilities.js +37 -10
  43. package/dist/commands/capabilities.js.map +1 -1
  44. package/dist/commands/console.d.ts +21 -0
  45. package/dist/commands/console.d.ts.map +1 -0
  46. package/dist/commands/console.js +87 -0
  47. package/dist/commands/console.js.map +1 -0
  48. package/dist/commands/conversations.d.ts.map +1 -1
  49. package/dist/commands/conversations.js +13 -2
  50. package/dist/commands/conversations.js.map +1 -1
  51. package/dist/commands/deploy.d.ts.map +1 -1
  52. package/dist/commands/deploy.js +2 -2
  53. package/dist/commands/deploy.js.map +1 -1
  54. package/dist/commands/dev.d.ts +11 -0
  55. package/dist/commands/dev.d.ts.map +1 -1
  56. package/dist/commands/dev.js +211 -39
  57. package/dist/commands/dev.js.map +1 -1
  58. package/dist/commands/doctor.d.ts +18 -5
  59. package/dist/commands/doctor.d.ts.map +1 -1
  60. package/dist/commands/doctor.js +421 -21
  61. package/dist/commands/doctor.js.map +1 -1
  62. package/dist/commands/env-scoped.d.ts +44 -0
  63. package/dist/commands/env-scoped.d.ts.map +1 -0
  64. package/dist/commands/env-scoped.js +222 -0
  65. package/dist/commands/env-scoped.js.map +1 -0
  66. package/dist/commands/env.d.ts.map +1 -1
  67. package/dist/commands/env.js +85 -123
  68. package/dist/commands/env.js.map +1 -1
  69. package/dist/commands/eval-runs.d.ts +9 -0
  70. package/dist/commands/eval-runs.d.ts.map +1 -1
  71. package/dist/commands/eval-runs.js +20 -19
  72. package/dist/commands/eval-runs.js.map +1 -1
  73. package/dist/commands/eval-suites.d.ts.map +1 -1
  74. package/dist/commands/eval-suites.js +16 -8
  75. package/dist/commands/eval-suites.js.map +1 -1
  76. package/dist/commands/exports.d.ts +3 -0
  77. package/dist/commands/exports.d.ts.map +1 -0
  78. package/dist/commands/exports.js +82 -0
  79. package/dist/commands/exports.js.map +1 -0
  80. package/dist/commands/feedback.d.ts.map +1 -1
  81. package/dist/commands/feedback.js +6 -5
  82. package/dist/commands/feedback.js.map +1 -1
  83. package/dist/commands/flows.d.ts.map +1 -1
  84. package/dist/commands/flows.js +2 -1
  85. package/dist/commands/flows.js.map +1 -1
  86. package/dist/commands/gate-policies.d.ts.map +1 -1
  87. package/dist/commands/gate-policies.js +2 -1
  88. package/dist/commands/gate-policies.js.map +1 -1
  89. package/dist/commands/guardrails.d.ts.map +1 -1
  90. package/dist/commands/guardrails.js +3 -2
  91. package/dist/commands/guardrails.js.map +1 -1
  92. package/dist/commands/helpers.js +5 -5
  93. package/dist/commands/helpers.js.map +1 -1
  94. package/dist/commands/index.d.ts.map +1 -1
  95. package/dist/commands/index.js +14 -0
  96. package/dist/commands/index.js.map +1 -1
  97. package/dist/commands/init.d.ts +1 -1
  98. package/dist/commands/init.d.ts.map +1 -1
  99. package/dist/commands/init.js +254 -18
  100. package/dist/commands/init.js.map +1 -1
  101. package/dist/commands/judge-classes.d.ts.map +1 -1
  102. package/dist/commands/judge-classes.js +11 -10
  103. package/dist/commands/judge-classes.js.map +1 -1
  104. package/dist/commands/judgments.d.ts.map +1 -1
  105. package/dist/commands/judgments.js +7 -6
  106. package/dist/commands/judgments.js.map +1 -1
  107. package/dist/commands/key.js +4 -4
  108. package/dist/commands/memory.d.ts.map +1 -1
  109. package/dist/commands/memory.js +277 -25
  110. package/dist/commands/memory.js.map +1 -1
  111. package/dist/commands/people.d.ts +3 -0
  112. package/dist/commands/people.d.ts.map +1 -0
  113. package/dist/commands/people.js +166 -0
  114. package/dist/commands/people.js.map +1 -0
  115. package/dist/commands/proposals.d.ts.map +1 -1
  116. package/dist/commands/proposals.js +390 -69
  117. package/dist/commands/proposals.js.map +1 -1
  118. package/dist/commands/provenance.d.ts.map +1 -1
  119. package/dist/commands/provenance.js +6 -7
  120. package/dist/commands/provenance.js.map +1 -1
  121. package/dist/commands/providers.d.ts.map +1 -1
  122. package/dist/commands/providers.js +46 -7
  123. package/dist/commands/providers.js.map +1 -1
  124. package/dist/commands/reviewers.d.ts.map +1 -1
  125. package/dist/commands/reviewers.js +3 -2
  126. package/dist/commands/reviewers.js.map +1 -1
  127. package/dist/commands/runs.d.ts.map +1 -1
  128. package/dist/commands/runs.js +24 -14
  129. package/dist/commands/runs.js.map +1 -1
  130. package/dist/commands/schedules.d.ts +3 -0
  131. package/dist/commands/schedules.d.ts.map +1 -0
  132. package/dist/commands/schedules.js +275 -0
  133. package/dist/commands/schedules.js.map +1 -0
  134. package/dist/commands/secrets.d.ts.map +1 -1
  135. package/dist/commands/secrets.js +10 -11
  136. package/dist/commands/secrets.js.map +1 -1
  137. package/dist/commands/service-accounts.d.ts +3 -0
  138. package/dist/commands/service-accounts.d.ts.map +1 -0
  139. package/dist/commands/service-accounts.js +175 -0
  140. package/dist/commands/service-accounts.js.map +1 -0
  141. package/dist/commands/skills.d.ts +1 -1
  142. package/dist/commands/skills.d.ts.map +1 -1
  143. package/dist/commands/skills.js +11 -4
  144. package/dist/commands/skills.js.map +1 -1
  145. package/dist/commands/sso.d.ts +6 -0
  146. package/dist/commands/sso.d.ts.map +1 -0
  147. package/dist/commands/sso.js +318 -0
  148. package/dist/commands/sso.js.map +1 -0
  149. package/dist/commands/tokens.d.ts +11 -0
  150. package/dist/commands/tokens.d.ts.map +1 -1
  151. package/dist/commands/tokens.js +144 -11
  152. package/dist/commands/tokens.js.map +1 -1
  153. package/dist/commands/tools.d.ts.map +1 -1
  154. package/dist/commands/tools.js +4 -3
  155. package/dist/commands/tools.js.map +1 -1
  156. package/dist/commands/unwired.d.ts.map +1 -1
  157. package/dist/commands/unwired.js +1 -30
  158. package/dist/commands/unwired.js.map +1 -1
  159. package/dist/commands/upgrade.d.ts +11 -0
  160. package/dist/commands/upgrade.d.ts.map +1 -0
  161. package/dist/commands/upgrade.js +160 -0
  162. package/dist/commands/upgrade.js.map +1 -0
  163. package/dist/context.d.ts +7 -0
  164. package/dist/context.d.ts.map +1 -1
  165. package/dist/context.js +1 -0
  166. package/dist/context.js.map +1 -1
  167. package/dist/dev/defaults.d.ts +28 -11
  168. package/dist/dev/defaults.d.ts.map +1 -1
  169. package/dist/dev/defaults.js +114 -35
  170. package/dist/dev/defaults.js.map +1 -1
  171. package/dist/dev/google-credentials.d.ts +50 -0
  172. package/dist/dev/google-credentials.d.ts.map +1 -0
  173. package/dist/dev/google-credentials.js +157 -0
  174. package/dist/dev/google-credentials.js.map +1 -0
  175. package/dist/dev/java-builder.d.ts +56 -0
  176. package/dist/dev/java-builder.d.ts.map +1 -0
  177. package/dist/dev/java-builder.js +204 -0
  178. package/dist/dev/java-builder.js.map +1 -0
  179. package/dist/dev/jvm-run-files.d.ts +29 -0
  180. package/dist/dev/jvm-run-files.d.ts.map +1 -0
  181. package/dist/dev/jvm-run-files.js +94 -0
  182. package/dist/dev/jvm-run-files.js.map +1 -0
  183. package/dist/dev/lines.d.ts +13 -0
  184. package/dist/dev/lines.d.ts.map +1 -0
  185. package/dist/dev/lines.js +24 -0
  186. package/dist/dev/lines.js.map +1 -0
  187. package/dist/dev/log-view.d.ts +116 -0
  188. package/dist/dev/log-view.d.ts.map +1 -0
  189. package/dist/dev/log-view.js +233 -0
  190. package/dist/dev/log-view.js.map +1 -0
  191. package/dist/dev/pack-code.d.ts +79 -4
  192. package/dist/dev/pack-code.d.ts.map +1 -1
  193. package/dist/dev/pack-code.js +262 -3
  194. package/dist/dev/pack-code.js.map +1 -1
  195. package/dist/dev/pack-env.d.ts.map +1 -1
  196. package/dist/dev/pack-env.js +4 -0
  197. package/dist/dev/pack-env.js.map +1 -1
  198. package/dist/dev/pack-service.d.ts +11 -1
  199. package/dist/dev/pack-service.d.ts.map +1 -1
  200. package/dist/dev/pack-service.js +56 -20
  201. package/dist/dev/pack-service.js.map +1 -1
  202. package/dist/dev/paths.d.ts +6 -0
  203. package/dist/dev/paths.d.ts.map +1 -1
  204. package/dist/dev/paths.js +8 -0
  205. package/dist/dev/paths.js.map +1 -1
  206. package/dist/dev/project-database.js +1 -1
  207. package/dist/dev/project-database.js.map +1 -1
  208. package/dist/dev/runners.d.ts +35 -9
  209. package/dist/dev/runners.d.ts.map +1 -1
  210. package/dist/dev/runtime-container.d.ts +10 -2
  211. package/dist/dev/runtime-container.d.ts.map +1 -1
  212. package/dist/dev/runtime-container.js +48 -20
  213. package/dist/dev/runtime-container.js.map +1 -1
  214. package/dist/dev/runtime-env.d.ts +10 -0
  215. package/dist/dev/runtime-env.d.ts.map +1 -1
  216. package/dist/dev/runtime-env.js +8 -4
  217. package/dist/dev/runtime-env.js.map +1 -1
  218. package/dist/dev/runtime-image.d.ts +1 -1
  219. package/dist/dev/runtime-image.js +1 -1
  220. package/dist/dev/scala-builder.d.ts +72 -0
  221. package/dist/dev/scala-builder.d.ts.map +1 -0
  222. package/dist/dev/scala-builder.js +347 -0
  223. package/dist/dev/scala-builder.js.map +1 -0
  224. package/dist/env/project-env.d.ts.map +1 -1
  225. package/dist/env/project-env.js +3 -1
  226. package/dist/env/project-env.js.map +1 -1
  227. package/dist/errors.d.ts +9 -0
  228. package/dist/errors.d.ts.map +1 -1
  229. package/dist/errors.js +15 -0
  230. package/dist/errors.js.map +1 -1
  231. package/dist/help.d.ts +14 -0
  232. package/dist/help.d.ts.map +1 -1
  233. package/dist/help.js +55 -0
  234. package/dist/help.js.map +1 -1
  235. package/dist/init/dependency-specs.d.ts +26 -0
  236. package/dist/init/dependency-specs.d.ts.map +1 -1
  237. package/dist/init/dependency-specs.js +38 -0
  238. package/dist/init/dependency-specs.js.map +1 -1
  239. package/dist/init/java-augment.d.ts +24 -0
  240. package/dist/init/java-augment.d.ts.map +1 -0
  241. package/dist/init/java-augment.js +198 -0
  242. package/dist/init/java-augment.js.map +1 -0
  243. package/dist/init/mode-detect.d.ts +2 -2
  244. package/dist/init/mode-detect.d.ts.map +1 -1
  245. package/dist/init/mode-detect.js +12 -1
  246. package/dist/init/mode-detect.js.map +1 -1
  247. package/dist/init/scala-augment.d.ts +23 -0
  248. package/dist/init/scala-augment.d.ts.map +1 -0
  249. package/dist/init/scala-augment.js +213 -0
  250. package/dist/init/scala-augment.js.map +1 -0
  251. package/dist/init/template-files.d.ts +40 -2
  252. package/dist/init/template-files.d.ts.map +1 -1
  253. package/dist/init/template-files.js +56 -5
  254. package/dist/init/template-files.js.map +1 -1
  255. package/dist/main.d.ts +3 -0
  256. package/dist/main.d.ts.map +1 -1
  257. package/dist/main.js +30 -4
  258. package/dist/main.js.map +1 -1
  259. package/dist/open-url.d.ts +15 -0
  260. package/dist/open-url.d.ts.map +1 -0
  261. package/dist/open-url.js +41 -0
  262. package/dist/open-url.js.map +1 -0
  263. package/dist/package-manager.d.ts +10 -3
  264. package/dist/package-manager.d.ts.map +1 -1
  265. package/dist/package-manager.js +19 -1
  266. package/dist/package-manager.js.map +1 -1
  267. package/dist/providers/preset-loader.d.ts.map +1 -1
  268. package/dist/providers/preset-loader.js +9 -1
  269. package/dist/providers/preset-loader.js.map +1 -1
  270. package/dist/providers/presets/anthropic.json +35 -4
  271. package/dist/providers/presets/gemini-api.json +3 -0
  272. package/dist/providers/presets/gemini.json +3 -0
  273. package/dist/providers/presets/groq.json +1 -0
  274. package/dist/providers/presets/openai.json +36 -4
  275. package/dist/providers/presets/openrouter.json +6 -2
  276. package/dist/sdk-skills/kindgi-authoring-agents/SKILL.md +28 -4
  277. package/dist/sdk-skills/kindgi-authoring-flows/SKILL.md +1 -1
  278. package/dist/sdk-skills/kindgi-authoring-guardrails/SKILL.md +47 -3
  279. package/dist/sdk-skills/kindgi-authoring-mcp-servers/SKILL.md +7 -5
  280. package/dist/sdk-skills/kindgi-authoring-providers/SKILL.md +81 -52
  281. package/dist/sdk-skills/kindgi-authoring-tools/SKILL.md +27 -1
  282. package/dist/sdk-skills/kindgi-framework-feedback/SKILL.md +5 -4
  283. package/dist/sdk-skills/kindgi-getting-started/SKILL.md +1 -1
  284. package/dist/sdk-skills/kindgi-java-authoring-agents/SKILL.md +220 -0
  285. package/dist/sdk-skills/kindgi-java-authoring-flows/SKILL.md +390 -0
  286. package/dist/sdk-skills/kindgi-java-authoring-guardrails/SKILL.md +209 -0
  287. package/dist/sdk-skills/kindgi-java-authoring-tools/SKILL.md +334 -0
  288. package/dist/sdk-skills/kindgi-java-getting-started/SKILL.md +270 -0
  289. package/dist/sdk-skills/kindgi-python-authoring-agents/SKILL.md +23 -5
  290. package/dist/sdk-skills/kindgi-python-authoring-flows/SKILL.md +1 -1
  291. package/dist/sdk-skills/kindgi-python-authoring-guardrails/SKILL.md +10 -3
  292. package/dist/sdk-skills/kindgi-python-authoring-tools/SKILL.md +36 -5
  293. package/dist/sdk-skills/kindgi-python-getting-started/SKILL.md +2 -2
  294. package/dist/sdk-skills/kindgi-scala-authoring-agents/SKILL.md +217 -0
  295. package/dist/sdk-skills/kindgi-scala-authoring-flows/SKILL.md +357 -0
  296. package/dist/sdk-skills/kindgi-scala-authoring-guardrails/SKILL.md +199 -0
  297. package/dist/sdk-skills/kindgi-scala-authoring-tools/SKILL.md +302 -0
  298. package/dist/sdk-skills/kindgi-scala-getting-started/SKILL.md +302 -0
  299. package/dist/templates/java/.mvn/wrapper/maven-wrapper.properties +3 -0
  300. package/dist/templates/java/AGENTS.md +31 -0
  301. package/dist/templates/java/README.md.tmpl +69 -0
  302. package/dist/templates/java/gitignore +9 -0
  303. package/dist/templates/java/kindgi.config.json.tmpl +8 -0
  304. package/dist/templates/java/kindgiw +33 -0
  305. package/dist/templates/java/kindgiw.cmd +28 -0
  306. package/dist/templates/java/mvnw +295 -0
  307. package/dist/templates/java/pom.xml.tmpl +65 -0
  308. package/dist/templates/java/src/main/java/__PACKAGE__/agents/EchoAgent.java.tmpl +27 -0
  309. package/dist/templates/java/src/main/java/__PACKAGE__/flows/EchoFlow.java.tmpl +18 -0
  310. package/dist/templates/java/src/main/java/__PACKAGE__/guardrails/ResponseNotEmpty.java.tmpl +28 -0
  311. package/dist/templates/java/src/main/java/__PACKAGE__/tools/Echo.java.tmpl +22 -0
  312. package/dist/templates/java/src/main/java/__PACKAGE__/tools/Greet.java.tmpl +23 -0
  313. package/dist/templates/java/src/test/java/__PACKAGE__/ToolsTest.java.tmpl +30 -0
  314. package/dist/templates/minimal/README.md.tmpl +8 -14
  315. package/dist/templates/python/README.md.tmpl +6 -6
  316. package/dist/templates/sample/README.md.tmpl +7 -13
  317. package/dist/templates/scala/AGENTS.md +32 -0
  318. package/dist/templates/scala/README.md.tmpl +75 -0
  319. package/dist/templates/scala/build.sbt.tmpl +16 -0
  320. package/dist/templates/scala/gitignore +13 -0
  321. package/dist/templates/scala/kindgi.config.json.tmpl +8 -0
  322. package/dist/templates/scala/project/build.properties +1 -0
  323. package/dist/templates/scala/src/main/scala/__PACKAGE__/agents/EchoAgent.scala.tmpl +23 -0
  324. package/dist/templates/scala/src/main/scala/__PACKAGE__/flows/EchoFlow.scala.tmpl +16 -0
  325. package/dist/templates/scala/src/main/scala/__PACKAGE__/guardrails/ResponseNotEmpty.scala.tmpl +21 -0
  326. package/dist/templates/scala/src/main/scala/__PACKAGE__/tools/Echo.scala.tmpl +16 -0
  327. package/dist/templates/scala/src/main/scala/__PACKAGE__/tools/Greet.scala.tmpl +17 -0
  328. package/dist/templates/scala/src/test/scala/__PACKAGE__/ToolsSuite.scala.tmpl +23 -0
  329. package/package.json +13 -12
@@ -12,7 +12,9 @@
12
12
  * runtime `kindgi dev` runs, and a provider registered there. Outside a
13
13
  * project the project's checks are skipped, saying why; a check that
14
14
  * needs another (the registry needs Docker) is skipped when that one
15
- * fails. `skip` is never a failure.
15
+ * fails. `skip` is never a failure, and neither is `warn`: it works now,
16
+ * but the person should know (a provider whose agents land on a model its
17
+ * preset no longer lists, or not on the preset's default).
16
18
  *
17
19
  * Under `kindgi-cli` (the PyPI build, `KINDGI_CLI_INSTALL=pypi`), Node is
18
20
  * the one the wheel brings and npm isn't needed, so neither is a failure,
@@ -24,13 +26,17 @@ import { readFile, readdir, stat } from 'node:fs/promises';
24
26
  import { dirname, join, resolve } from 'node:path';
25
27
  import { findKindgiConfig } from '@kindgi/handler-runtime';
26
28
  import { LOCAL_ENV_NAME, displayEnvPath, readPackEnv } from '@kindgi/secrets-dotenv';
29
+ import { DEV_GOOGLE_CREDENTIALS_VAR, VERTEX_PROVIDER_ID, resolveDevGoogleCredentials, vertexCredentialsHint, } from '../dev/google-credentials.js';
30
+ import { javaMajor } from '../dev/pack-code.js';
27
31
  import { docker } from '../dev/runtime-container.js';
28
32
  import { DEFAULT_RUNTIME_IMAGE, registryOf } from '../dev/runtime-image.js';
29
33
  import { checkDocker, checkImageAccess, credentialHelperHint } from '../dev/runtime-registry.js';
34
+ import { extractKindgiError } from '../errors.js';
30
35
  import { renderJson } from '../output.js';
31
36
  import { binCommand, cliInstall, defaultDetectIo, publishedCliSpec, pythonBinRunner, usablePackageManager, } from '../package-manager.js';
32
37
  import { loadProviderPresets } from '../providers/preset-loader.js';
33
38
  import { CLI_VERSION } from '../version-info.js';
39
+ import { probeConsole } from './console.js';
34
40
  /**
35
41
  * The provider `kindgi dev` falls back to, with canned replies
36
42
  * (`DEV_ECHO_PROVIDER_ID` in `@kindgi/dev-echo-provider`): not a model.
@@ -40,11 +46,16 @@ const DEV_ECHO_PROVIDER_ID = 'dev-echo';
40
46
  export const MIN_NODE = '22.12.0';
41
47
  /** The Python a Python project needs (`requires-python` in its pyproject.toml). */
42
48
  export const MIN_PYTHON = '3.11.0';
49
+ /** The JDK a Java or Scala project needs (kindgi-pack's baseline). */
50
+ export const MIN_JAVA = 17;
43
51
  const TITLES = {
44
52
  node: 'Node.js',
45
53
  npm: 'npm',
46
54
  python: 'Python',
47
55
  uv: 'uv',
56
+ java: 'Java',
57
+ maven: 'Maven',
58
+ sbt: 'sbt',
48
59
  docker: 'Docker',
49
60
  registry: 'Runtime image',
50
61
  project: 'Project',
@@ -52,6 +63,8 @@ const TITLES = {
52
63
  'model-key': 'Model key',
53
64
  runtime: 'Runtime',
54
65
  provider: 'Provider',
66
+ 'console-sign-in': 'Console sign-in',
67
+ erasures: 'Erasures',
55
68
  };
56
69
  export const doctorCommand = {
57
70
  kind: 'leaf',
@@ -81,24 +94,32 @@ export async function runDoctor(ctx, dir) {
81
94
  const run = seam.docker ?? docker;
82
95
  const checks = [];
83
96
  const config = await findKindgiConfig(dir);
84
- const language = config === undefined ? undefined : config.format === 'pyproject' ? 'python' : 'node';
97
+ const language = config === undefined
98
+ ? undefined
99
+ : config.format === 'pyproject'
100
+ ? 'python'
101
+ : config.format === 'json'
102
+ ? await jvmLanguageOf(config.path)
103
+ : 'node';
85
104
  const pypi = cliInstall(ctx.env) === 'pypi';
86
105
  const kindgi = await kindgiCommand(dir, language, pypi, tool);
87
106
  checks.push(...(await nodeChecks(seam.nodeVersion ?? process.versions.node, tool, pypi)));
88
107
  checks.push(...(await pythonChecks(tool, language)));
108
+ checks.push(...(await jvmChecks(tool, language, dir, ctx.env)));
89
109
  const dockerCheck = await checkDockerRunning(run);
90
110
  checks.push(dockerCheck);
91
111
  checks.push(dockerCheck.status === 'pass'
92
112
  ? await registryCheck(run, seam.image ?? DEFAULT_RUNTIME_IMAGE, kindgi)
93
113
  : skip('registry', 'Not checked: it needs Docker running.'));
114
+ checks.push(await consoleSignInCheck(ctx));
94
115
  if (config === undefined || language === undefined) {
95
116
  checks.push({
96
117
  id: 'project',
97
118
  status: 'skip',
98
- message: `No Kindgi project in ${dir}: no kindgi.config.ts, and no pyproject.toml with [tool.kindgi].`,
119
+ message: `No Kindgi project in ${dir}: no kindgi.config.ts, no pyproject.toml with [tool.kindgi], and no kindgi.config.json.`,
99
120
  fix: createProjectFix(kindgi, pypi),
100
121
  });
101
- for (const id of ['dependencies', 'model-key', 'runtime', 'provider']) {
122
+ for (const id of ['dependencies', 'model-key', 'runtime', 'provider', 'erasures']) {
102
123
  checks.push(skip(id, 'Not checked: it needs a project.'));
103
124
  }
104
125
  return report(checks, null);
@@ -110,34 +131,56 @@ export async function runDoctor(ctx, dir) {
110
131
  const runtime = await runtimeCheck(ctx, rc, kindgi);
111
132
  checks.push(runtime.check);
112
133
  checks.push(runtime.check.status === 'pass' && runtime.url !== undefined && rc.token !== undefined
113
- ? await providerCheck(ctx, runtime.url, rc.token, kindgi, seam)
134
+ ? await providerCheck(ctx, runtime.url, rc.token, kindgi, seam, dir)
114
135
  : skip('provider', `Not checked: it needs the runtime running (${kindgi('dev')}).`));
115
- return report(checks, { dir, language });
136
+ checks.push(runtime.check.status === 'pass' && runtime.url !== undefined
137
+ ? await erasuresCheck(ctx, runtime.url)
138
+ : skip('erasures', `Not checked: it needs the runtime running (${kindgi('dev')}).`));
139
+ return report(checks, { dir, language }, runtime.consoleUrl);
116
140
  }
117
141
  /** The report as ✓/✗ lines, each failure with its fix. */
118
142
  export function doctorText(report) {
119
143
  const lines = [`kindgi doctor (CLI ${report.cliVersion})`];
120
144
  for (const check of report.checks) {
121
- const mark = check.status === 'pass' ? '✓' : check.status === 'fail' ? '✗' : '–';
145
+ const mark = MARKS[check.status];
122
146
  lines.push(` ${mark} ${TITLES[check.id]}: ${check.message}`);
147
+ for (const detail of check.details ?? [])
148
+ lines.push(` ✗ ${detail}`);
123
149
  if (check.fix !== undefined && check.status !== 'pass')
124
150
  lines.push(` Fix: ${check.fix}`);
125
151
  }
126
152
  const failed = report.checks.filter((c) => c.status === 'fail').length;
127
- lines.push(failed === 0
128
- ? 'Everything checked is ready.'
129
- : `${failed} ${failed === 1 ? 'problem' : 'problems'} to fix.`);
153
+ const warned = report.checks.filter((c) => c.status === 'warn').length;
154
+ const warnings = `${warned} ${warned === 1 ? 'warning' : 'warnings'}`;
155
+ lines.push(failed > 0
156
+ ? `${failed} ${failed === 1 ? 'problem' : 'problems'} to fix${warned > 0 ? `, and ${warnings}` : ''}.`
157
+ : warned > 0
158
+ ? `Everything checked is ready, with ${warnings}.`
159
+ : 'Everything checked is ready.');
130
160
  return `${lines.join('\n')}\n`;
131
161
  }
132
- function report(checks, project) {
162
+ const MARKS = {
163
+ pass: '✓',
164
+ warn: '!',
165
+ fail: '✗',
166
+ skip: '–',
167
+ };
168
+ function report(checks, project, consoleUrl) {
133
169
  return {
134
170
  ok: checks.every((c) => c.status !== 'fail'),
135
171
  cliVersion: CLI_VERSION,
136
172
  project,
173
+ ...(consoleUrl !== undefined && { consoleUrl }),
137
174
  checks,
138
175
  };
139
176
  }
140
177
  const pass = (id, message) => ({ id, status: 'pass', message });
178
+ const warn = (id, message, fix) => ({
179
+ id,
180
+ status: 'warn',
181
+ message,
182
+ fix,
183
+ });
141
184
  const fail = (id, message, fix) => ({
142
185
  id,
143
186
  status: 'fail',
@@ -164,7 +207,7 @@ async function nodeChecks(version, tool, pypi) {
164
207
  function createProjectFix(kindgi, pypi) {
165
208
  return pypi
166
209
  ? `Create one: ${kindgi('init', '<name>', '--template=python')}, then run doctor in its folder.`
167
- : `Create one: ${kindgi('init', '<name>')} (TypeScript; add --template=python for Python), then run doctor in its folder.`;
210
+ : `Create one: ${kindgi('init', '<name>')} (TypeScript; add --template=python for Python, --template=java for Java), then run doctor in its folder.`;
168
211
  }
169
212
  function nodeCheck(version, pypi) {
170
213
  return atLeast(version, MIN_NODE)
@@ -190,7 +233,7 @@ async function pythonChecks(tool, language) {
190
233
  const pythonFix = `Install Python ${MIN_PYTHON.replace(/\.0$/, '')} or later (https://www.python.org/downloads, or: uv python install 3.12).`;
191
234
  const uvFix = 'Install uv: curl -LsSf https://astral.sh/uv/install.sh | sh (or: brew install uv).';
192
235
  if (language !== 'python') {
193
- const why = language === 'node' ? 'a TypeScript project' : 'no project yet';
236
+ const why = projectKind(language);
194
237
  return [
195
238
  skip('python', `Not needed (${why}); ${python !== undefined ? `Python ${python} is installed` : "Python isn't installed"}, for a Python project (kindgi init --template=python).`),
196
239
  skip('uv', `Not needed (${why}); ${uvVersion !== undefined ? `uv ${uvVersion} is installed` : "uv isn't installed"}, for a Python project.`),
@@ -207,6 +250,95 @@ async function pythonChecks(tool, language) {
207
250
  : fail('uv', "uv isn't installed: a Kindgi Python project installs and runs with it.", uvFix),
208
251
  ];
209
252
  }
253
+ /** Why a language's tools aren't needed: the project's kind, or no project. */
254
+ function projectKind(language) {
255
+ return language === 'node'
256
+ ? 'a TypeScript project'
257
+ : language === 'python'
258
+ ? 'a Python project'
259
+ : language === 'java'
260
+ ? 'a Java project'
261
+ : language === 'scala'
262
+ ? 'a Scala project'
263
+ : 'no project yet';
264
+ }
265
+ /** A `kindgi.config.json`'s language: `scala` when it says so, else `java`. */
266
+ async function jvmLanguageOf(path) {
267
+ try {
268
+ return JSON.parse(await readFile(path, 'utf8')).language === 'scala'
269
+ ? 'scala'
270
+ : 'java';
271
+ }
272
+ catch {
273
+ return 'java';
274
+ }
275
+ }
276
+ /**
277
+ * A JDK 17+, and Maven or sbt: needed by a Java project (the JDK, Maven) or a
278
+ * Scala one (the JDK, sbt); elsewhere, what's installed (for choosing a
279
+ * template). The JDK is `JAVA_HOME`'s, else the `java` on PATH, as `kindgi
280
+ * dev` finds it; Maven is the project's wrapper (`mvnw`), else `mvn`; sbt is
281
+ * the `sbt` on PATH.
282
+ */
283
+ async function jvmChecks(tool, language, dir, env) {
284
+ return [
285
+ await jdkCheck(tool, language, env),
286
+ await mavenCheck(tool, language, dir),
287
+ await sbtCheck(tool, language),
288
+ ];
289
+ }
290
+ async function jdkCheck(tool, language, env) {
291
+ const home = env.JAVA_HOME !== undefined && env.JAVA_HOME !== '' ? env.JAVA_HOME : undefined;
292
+ const got = await tool(home === undefined ? 'java' : join(home, 'bin', 'java'), ['-version']);
293
+ const version = got.code === null ? undefined : /version "([^"]+)"/.exec(`${got.stderr}\n${got.stdout}`)?.[1];
294
+ if (language !== 'java' && language !== 'scala')
295
+ return jdkNotNeeded(language, version);
296
+ const javaFix = `Install a JDK ${MIN_JAVA} or later (e.g. Eclipse Temurin, https://adoptium.net) and set JAVA_HOME to it.`;
297
+ const where = home === undefined ? 'the java on your PATH' : `JAVA_HOME, ${home}`;
298
+ if (version === undefined)
299
+ return fail('java', `No JDK: ${where} doesn't run.`, javaFix);
300
+ const name = language === 'java' ? 'Java' : 'Scala';
301
+ return (javaMajor(version) ?? 0) >= MIN_JAVA
302
+ ? pass('java', `Java ${version} (${where}).`)
303
+ : fail('java', `Java ${version} (${where}); a Kindgi ${name} project needs ${MIN_JAVA} or later.`, javaFix);
304
+ }
305
+ /** The JDK outside a JVM project: what's installed, for choosing a template. */
306
+ function jdkNotNeeded(language, version) {
307
+ const installed = version !== undefined ? `Java ${version} is installed` : "Java isn't installed";
308
+ return skip('java', `Not needed (${projectKind(language)}); ${installed}, for a Java or Scala project (kindgi init --template=java or scala).`);
309
+ }
310
+ async function mavenCheck(tool, language, dir) {
311
+ const wrapper = await isFile(join(dir, 'mvnw'));
312
+ const mvn = wrapper ? undefined : await tool('mvn', ['--version']);
313
+ const mvnVersion = mvn?.code === 0 ? /Apache Maven (\S+)/.exec(mvn.stdout)?.[1] : undefined;
314
+ if (language !== 'java') {
315
+ return skip('maven', `Not needed (${projectKind(language)}); ${mvnVersion !== undefined ? `Maven ${mvnVersion} is installed` : "Maven isn't installed"}, for a Java project.`);
316
+ }
317
+ if (wrapper)
318
+ return pass('maven', 'The project has the Maven wrapper (mvnw).');
319
+ return mvnVersion !== undefined
320
+ ? pass('maven', `Maven ${mvnVersion}.`)
321
+ : fail('maven', "Maven isn't installed, and the project has no Maven wrapper (mvnw).", 'Add the Maven wrapper to the project (mvn wrapper:wrapper), or install Maven 3.9 or later (https://maven.apache.org/install.html).');
322
+ }
323
+ async function sbtCheck(tool, language) {
324
+ // `--script-version` answers from the launcher script, without starting a JVM.
325
+ const got = await tool('sbt', ['--script-version']);
326
+ const version = got.code === 0 ? /(\d+\.\d+\.\d+\S*)/.exec(got.stdout)?.[1] : undefined;
327
+ if (language !== 'scala') {
328
+ return skip('sbt', `Not needed (${projectKind(language)}); ${version !== undefined ? `sbt ${version} is installed` : "sbt isn't installed"}, for a Scala project.`);
329
+ }
330
+ return version !== undefined
331
+ ? pass('sbt', `sbt ${version} (the launcher; the project's own sbt is project/build.properties').`)
332
+ : fail('sbt', "sbt isn't installed: a Kindgi Scala project builds with it.", 'Install sbt 1.10 or later (https://www.scala-sbt.org/download).');
333
+ }
334
+ async function isFile(path) {
335
+ try {
336
+ return (await stat(path)).isFile();
337
+ }
338
+ catch {
339
+ return false;
340
+ }
341
+ }
210
342
  async function pythonVersion(tool) {
211
343
  for (const command of ['python3', 'python']) {
212
344
  const got = await tool(command, ['--version']);
@@ -267,13 +399,53 @@ async function readKindgirc(dir) {
267
399
  function projectCheck(dir, language, rc) {
268
400
  const kind = language === 'python'
269
401
  ? 'A Python project (pyproject.toml)'
270
- : 'A TypeScript project (kindgi.config.ts)';
402
+ : language === 'java'
403
+ ? 'A Java project (kindgi.config.json)'
404
+ : language === 'scala'
405
+ ? 'A Scala project (kindgi.config.json)'
406
+ : 'A TypeScript project (kindgi.config.ts)';
271
407
  if (rc.malformed !== undefined) {
272
408
  return fail('project', `${kind} in ${dir}, but its .kindgirc.json isn't valid JSON (${rc.malformed}).`, 'Delete .kindgirc.json: kindgi dev writes a new one on its next start.');
273
409
  }
274
410
  return pass('project', `${kind} in ${dir}; ${rc.exists ? 'kindgi dev has run here (.kindgirc.json)' : "kindgi dev hasn't run here yet"}.`);
275
411
  }
412
+ /**
413
+ * A Scala project's build declares kindgi-pack-scala: in `build.sbt`, or a
414
+ * `project/*.scala` file it reads its dependencies from.
415
+ */
416
+ async function scalaDependenciesCheck(dir) {
417
+ let build;
418
+ try {
419
+ build = await readFile(join(dir, 'build.sbt'), 'utf8');
420
+ }
421
+ catch {
422
+ return fail('dependencies', 'No build.sbt: a Kindgi Scala project builds with sbt.', 'Create one with the kindgi-pack-scala dependency (kindgi init --template=scala writes one).');
423
+ }
424
+ const projectDir = join(dir, 'project');
425
+ const projectFiles = await readdir(projectDir).catch(() => []);
426
+ const sources = [build];
427
+ for (const name of projectFiles.filter((f) => f.endsWith('.scala'))) {
428
+ sources.push(await readFile(join(projectDir, name), 'utf8').catch(() => ''));
429
+ }
430
+ return sources.some((text) => /"com\.kindgi"\s*%%\s*"kindgi-pack-scala"/.test(text))
431
+ ? pass('dependencies', 'The build has "com.kindgi" %% "kindgi-pack-scala" (sbt downloads it on the first build).')
432
+ : fail('dependencies', 'The build doesn\'t have "com.kindgi" %% "kindgi-pack-scala".', 'Add "com.kindgi" %% "kindgi-pack-scala" % "<version>" to libraryDependencies in build.sbt.');
433
+ }
276
434
  async function dependenciesCheck(dir, language, tool) {
435
+ if (language === 'java') {
436
+ let pom;
437
+ try {
438
+ pom = await readFile(join(dir, 'pom.xml'), 'utf8');
439
+ }
440
+ catch {
441
+ return fail('dependencies', 'No pom.xml: a Kindgi Java project builds with Maven.', 'Create one with the com.kindgi:kindgi-pack dependency (kindgi init --template=java writes one).');
442
+ }
443
+ return /<groupId>\s*com\.kindgi\s*<\/groupId>\s*<artifactId>\s*kindgi-pack\s*<\/artifactId>/.test(pom)
444
+ ? pass('dependencies', 'pom.xml has com.kindgi:kindgi-pack (Maven downloads it on the first build).')
445
+ : fail('dependencies', "pom.xml doesn't have com.kindgi:kindgi-pack.", 'Add the com.kindgi:kindgi-pack dependency to pom.xml.');
446
+ }
447
+ if (language === 'scala')
448
+ return scalaDependenciesCheck(dir);
277
449
  if (language === 'python') {
278
450
  return (await pythonPackageInstalled(dir))
279
451
  ? pass('dependencies', 'The kindgi package is installed in .venv.')
@@ -341,6 +513,32 @@ async function modelKeyCheck(dir, seam, hostEnv, kindgi) {
341
513
  const others = names.slice(1);
342
514
  return fail('model-key', `No model key in ${files} (looked for ${names.join(', ')})${inShell !== undefined ? `; ${inShell} is set in your shell, but kindgi dev reads keys from the project's env files` : ''}.`, `With kindgi dev running, set one LLM provider's key: ${kindgi('secrets', 'set', want, '--env=local', '--scope=tenant')}${others.length > 0 ? `, or the same with ${orList(others)}` : ''} (it prompts without echoing; or pipe it in with --from-stdin). Never paste a key into a chat.`);
343
515
  }
516
+ // ---------- erasures ----------
517
+ /**
518
+ * Whether the runtime can replay erasures after a backup restore: it
519
+ * needs the erasure ledger key, which `kindgi dev` doesn't set (fine for
520
+ * development), so this check never fails: it passes, or says why not.
521
+ */
522
+ async function erasuresCheck(ctx, url) {
523
+ let said;
524
+ try {
525
+ const res = await ctx.fetch(`${url.replace(/\/+$/, '')}/ready`, {
526
+ method: 'GET',
527
+ signal: AbortSignal.timeout(5000),
528
+ });
529
+ said = (await res.json()).erasures;
530
+ }
531
+ catch {
532
+ said = undefined;
533
+ }
534
+ if (said === 'replayable') {
535
+ return pass('erasures', 'Erasures can be replayed after a backup restore: the runtime has the erasure ledger key.');
536
+ }
537
+ if (said === 'unreplayable') {
538
+ return skip('erasures', "Erasures run, but a replay after a backup restore can't find whom they erased: the runtime has no KINDGI_ERASURE_LEDGER_KEY. Fine for development; set it where you run in production.");
539
+ }
540
+ return skip('erasures', "Not checked: this runtime doesn't say (it's from before 0.1.5).");
541
+ }
344
542
  // ---------- the runtime ----------
345
543
  async function runtimeCheck(ctx, rc, kindgi) {
346
544
  const start = `Start it: ${kindgi('dev')} (it keeps running; stop it with Ctrl+C).`;
@@ -353,8 +551,20 @@ async function runtimeCheck(ctx, rc, kindgi) {
353
551
  const url = `${rc.apiUrl.replace(/\/+$/, '')}/health`;
354
552
  try {
355
553
  const res = await ctx.fetch(url, { method: 'GET', signal: AbortSignal.timeout(5000) });
356
- if (res.ok)
357
- return { check: pass('runtime', `The runtime answers at ${rc.apiUrl}.`), url: rc.apiUrl };
554
+ if (res.ok) {
555
+ // Where to open it: the console, when the runtime serves one (T374).
556
+ const consoleProbe = await probeConsole(ctx.fetch, rc.apiUrl);
557
+ return consoleProbe.kind === 'served'
558
+ ? {
559
+ check: pass('runtime', `The runtime answers at ${rc.apiUrl}; its console is at ${consoleProbe.url}.`),
560
+ url: rc.apiUrl,
561
+ consoleUrl: consoleProbe.url,
562
+ }
563
+ : {
564
+ check: pass('runtime', `The runtime answers at ${rc.apiUrl}${consoleProbe.kind === 'not-served' ? ' (it serves no console)' : ''}.`),
565
+ url: rc.apiUrl,
566
+ };
567
+ }
358
568
  return {
359
569
  check: fail('runtime', `Something answers at ${rc.apiUrl}, but not as a healthy Kindgi runtime (HTTP ${res.status}).`, `${restart}; if another program holds that port, kindgi dev picks a free one.`),
360
570
  };
@@ -370,17 +580,105 @@ async function runtimeCheck(ctx, rc, kindgi) {
370
580
  };
371
581
  }
372
582
  }
373
- async function providerCheck(ctx, apiUrl, token, kindgi, seam) {
583
+ /**
584
+ * Whether anyone can sign in to the console of the runtime the CLI points
585
+ * at (`--url`, `KINDGI_API_URL`, `kindgi auth login`). Since 0.1.5,
586
+ * signing in with an API token is off by default outside `kindgi dev`: a
587
+ * deployment that relied on it, with no identity provider, has no way in.
588
+ */
589
+ async function consoleSignInCheck(ctx) {
590
+ const apiUrl = ctx.config.apiUrl?.replace(/\/+$/, '');
591
+ if (apiUrl === undefined) {
592
+ return skip('console-sign-in', 'Not checked: no runtime to ask (set KINDGI_API_URL, or run kindgi auth login).');
593
+ }
594
+ const TOKEN_ON = 'Set KINDGI_CONSOLE_TOKEN_SIGN_IN=on on the runtime and restart it, to keep signing in to the console with an API token';
595
+ let methods;
596
+ try {
597
+ const res = await ctx.fetch(`${apiUrl}/v1/auth/sign-in-options`, {
598
+ method: 'GET',
599
+ signal: AbortSignal.timeout(5000),
600
+ });
601
+ if (!res.ok) {
602
+ return skip('console-sign-in', `Not checked: the runtime at ${apiUrl} doesn't say how people sign in (older than 0.1.5).`);
603
+ }
604
+ methods = (await res.json()).methods;
605
+ }
606
+ catch {
607
+ return skip('console-sign-in', `Not checked: nothing answers at ${apiUrl}.`);
608
+ }
609
+ if (methods === undefined) {
610
+ return skip('console-sign-in', `Not checked: the runtime at ${apiUrl} doesn't say how people sign in (older than 0.1.5).`);
611
+ }
612
+ if (methods.apiToken === true) {
613
+ return pass('console-sign-in', methods.identityProviders === true
614
+ ? 'People can sign in to the console with an API token or an identity provider.'
615
+ : 'People can sign in to the console with an API token.');
616
+ }
617
+ if (methods.identityProviders !== true) {
618
+ return warn('console-sign-in', `Nobody can sign in to the console at ${apiUrl}: signing in with an API token is off (the default outside kindgi dev since 0.1.5), and no identity provider is set up.`, `${TOKEN_ON}; or set up sign-in with your identity provider (KINDGI_AUTH_SECRET_PATH, then kindgi sso providers start).`);
619
+ }
620
+ const token = ctx.config.token;
621
+ if (token === undefined) {
622
+ return pass('console-sign-in', 'People sign in to the console with an identity provider.');
623
+ }
624
+ try {
625
+ const res = await ctx.fetch(`${apiUrl}/v1/auth/providers`, {
626
+ method: 'GET',
627
+ headers: { authorization: `Bearer ${token}` },
628
+ signal: AbortSignal.timeout(5000),
629
+ });
630
+ if (res.ok) {
631
+ const providers = (await res.json()).data ?? [];
632
+ if (providers.length === 0) {
633
+ return warn('console-sign-in', `Sign-in with an identity provider is on at ${apiUrl}, but none is registered, and signing in with an API token is off: nobody can sign in to the console.`, `Register one (kindgi sso providers start <id> --idp=google|entra|okta|keycloak); or ${TOKEN_ON.charAt(0).toLowerCase()}${TOKEN_ON.slice(1)}.`);
634
+ }
635
+ }
636
+ }
637
+ catch {
638
+ // The providers couldn't be listed: the sign-in options already said it's on.
639
+ }
640
+ return pass('console-sign-in', 'People sign in to the console with an identity provider.');
641
+ }
642
+ async function providerCheck(ctx, apiUrl, token, kindgi, seam, dir) {
374
643
  try {
375
644
  const page = await ctx.clientFor(apiUrl, token).providers.list();
376
- const ids = page.data.map((p) => p.id ?? '?');
645
+ const listed = page.data;
646
+ const ids = listed.map((p) => p.id ?? '?');
377
647
  const models = ids.filter((id) => id !== DEV_ECHO_PROVIDER_ID);
378
648
  const presets = (await keyedPresets(seam)).map((p) => p.name);
379
649
  const register = presets.length > 1
380
650
  ? `Register the provider whose key you set: ${kindgi('providers', 'register', '--preset=<preset>')}, where <preset> is ${orList(presets)} (see Model key).`
381
651
  : `Register one: ${kindgi('providers', 'register', `--preset=${presets[0] ?? 'anthropic'}`)} (its key must be set first; see Model key).`;
382
652
  if (models.length > 0) {
383
- return pass('provider', `${models.length === 1 ? 'A provider is' : `${models.length} providers are`} registered: ${models.join(', ')}.`);
653
+ const registered = `${models.length === 1 ? 'A provider is' : `${models.length} providers are`} registered: ${models.join(', ')}.`;
654
+ const allPresets = await (seam.presets ?? (() => loadProviderPresets()))();
655
+ const stale = staleDefaults(listed, allPresets, kindgi);
656
+ const google = await vertexCredentials(ids, dir, ctx.env);
657
+ const notes = [...stale, ...(google === undefined ? [] : [google])];
658
+ const broken = await configIssues(ctx.clientFor(apiUrl, token), models);
659
+ if (broken !== undefined && broken.size > 0) {
660
+ const details = [...broken].flatMap(([id, issues]) => issues.map((i) => `${id}: ${i.path}: ${i.message}`));
661
+ const fix = [...broken.keys()]
662
+ .map((id) => reRegister(id, presetFor(id, allPresets), kindgi))
663
+ .join(' ');
664
+ const names = [...broken.keys()].join(', ');
665
+ if (broken.size === models.length) {
666
+ return {
667
+ ...fail('provider', `No usable provider: the runtime can't build ${broken.size === 1 ? names : `any of ${names}`} from ${broken.size === 1 ? 'its' : 'their'} registration, so an agent has no model to call.`, fix),
668
+ details,
669
+ };
670
+ }
671
+ return {
672
+ ...warn('provider', [
673
+ `${registered} The runtime can't build ${names} from ${broken.size === 1 ? 'its' : 'their'} registration, so agents only get the others.`,
674
+ ...notes.map((s) => s.message),
675
+ ].join(' '), [fix, ...notes.map((s) => s.fix)].join(' ')),
676
+ details,
677
+ };
678
+ }
679
+ return notes.length === 0
680
+ ? pass('provider', registered)
681
+ : warn('provider', [registered, ...notes.map((s) => s.message)].join(' '), notes.map((s) => s.fix).join(' '));
384
682
  }
385
683
  return ids.length > 0
386
684
  ? fail('provider', `Only ${DEV_ECHO_PROVIDER_ID} is registered: agents get its canned replies, not a model's.`, register)
@@ -390,6 +688,107 @@ async function providerCheck(ctx, apiUrl, token, kindgi, seam) {
390
688
  return fail('provider', `Couldn't list the providers: ${err.message}`, 'Run doctor again once kindgi dev has finished starting; if it persists, restart kindgi dev.');
391
689
  }
392
690
  }
691
+ /** A registered provider as `providers.list` answers it; a runtime before 0.1.4 sends no `defaultModel`. */
692
+ /**
693
+ * A Vertex provider (the `gemini` preset) with no Google credentials in
694
+ * `kindgi dev`: `KINDGI_DEV_GOOGLE_CREDENTIALS` unset, `off`, or not
695
+ * usable. `undefined` when there's no Vertex provider, or it has them.
696
+ */
697
+ async function vertexCredentials(ids, dir, hostEnv) {
698
+ const vertex = ids.filter((id) => id === VERTEX_PROVIDER_ID);
699
+ if (vertex.length === 0)
700
+ return undefined;
701
+ const env = await readPackEnv({ packDir: dir, envName: LOCAL_ENV_NAME });
702
+ const setting = hostEnv[DEV_GOOGLE_CREDENTIALS_VAR] ?? env.values[DEV_GOOGLE_CREDENTIALS_VAR];
703
+ const resolved = resolveDevGoogleCredentials(setting, hostEnv);
704
+ if (resolved.kind === 'ok' && resolved.credentials !== undefined)
705
+ return undefined;
706
+ return resolved.kind === 'error'
707
+ ? { message: resolved.message, fix: vertexCredentialsHint(vertex, hostEnv) }
708
+ : {
709
+ message: `${vertex.join(', ')} is Vertex AI, and kindgi dev gives the runtime no Google credentials (${DEV_GOOGLE_CREDENTIALS_VAR} is ${setting === undefined || setting.trim() === '' ? 'unset' : 'off'}).`,
710
+ fix: vertexCredentialsHint(vertex, hostEnv),
711
+ };
712
+ }
713
+ /**
714
+ * Registrations made from a preset (the preset's provider id) whose
715
+ * agents that name no model land where the preset no longer would send
716
+ * them: on a model the preset dropped, or, for a registration with no
717
+ * default model, not on the preset's default.
718
+ */
719
+ function staleDefaults(listed, presets, kindgi) {
720
+ const byId = new Map(Object.values(presets).map((p) => [p.metadata.id, p]));
721
+ return listed.flatMap((p) => {
722
+ const preset = p.id === undefined ? undefined : byId.get(p.id);
723
+ const names = (p.models ?? []).map((m) => m.name);
724
+ if (preset === undefined || names.length === 0)
725
+ return [];
726
+ // As the router breaks a tie: the provider's default model, else the first by name.
727
+ const lands = p.defaultModel !== undefined && names.includes(p.defaultModel)
728
+ ? p.defaultModel
729
+ : [...names].sort((a, b) => a.localeCompare(b))[0];
730
+ const register = kindgi('providers', 'register', `--preset=${preset.name}`, ...(preset.adapterConfig ?? []).map((s) => `--${s.key}=<${s.key}>`));
731
+ if (!preset.metadata.models.some((m) => m.name === lands)) {
732
+ return [
733
+ {
734
+ message: `On ${p.id}, an agent that names no model gets ${lands}, which the ${preset.name} preset no longer lists.`,
735
+ fix: `${reRegisterSteps(p.id, register, kindgi)} Or name a model on your agents (preferredModel).`,
736
+ },
737
+ ];
738
+ }
739
+ const wanted = preset.metadata.defaultModel;
740
+ if (p.defaultModel === undefined &&
741
+ wanted !== undefined &&
742
+ wanted !== lands &&
743
+ names.includes(wanted)) {
744
+ return [
745
+ {
746
+ message: `On ${p.id}, an agent that names no model gets ${lands}, the first by name: ${p.id} has no default model, and the ${preset.name} preset's is ${wanted}.`,
747
+ fix: `${reRegisterSteps(p.id, register, kindgi)} A runtime older than 0.1.4 doesn't keep a default model: there, name a model on your agents (preferredModel).`,
748
+ },
749
+ ];
750
+ }
751
+ return [];
752
+ });
753
+ }
754
+ /**
755
+ * Each provider's configuration problems by its adapter's own static check
756
+ * (`GET /v1/providers/{id}/check`, T362): only the providers with any.
757
+ * `undefined` when the runtime has no such route (older than 0.1.5): the
758
+ * check is skipped. A provider the check can't read (gone meanwhile) is
759
+ * left out.
760
+ */
761
+ async function configIssues(client, ids) {
762
+ const out = new Map();
763
+ for (const id of ids) {
764
+ try {
765
+ const result = await client.providers.check(id);
766
+ if (result.issues.length > 0)
767
+ out.set(id, result.issues);
768
+ }
769
+ catch (err) {
770
+ const wire = extractKindgiError(err);
771
+ if (wire?.code === 'not-found' && wire.serverCode === 'route-not-found')
772
+ return undefined;
773
+ }
774
+ }
775
+ return out;
776
+ }
777
+ /** The preset a provider id was registered from, if one has that id. */
778
+ function presetFor(id, presets) {
779
+ return Object.values(presets).find((p) => p.metadata.id === id);
780
+ }
781
+ /** How to fix a registration: unregister it, then register it again with the setting fixed. */
782
+ function reRegister(id, preset, kindgi) {
783
+ const register = preset !== undefined
784
+ ? kindgi('providers', 'register', `--preset=${preset.name}`, ...(preset.adapterConfig ?? []).map((s) => `--${s.key}=<${s.key}>`))
785
+ : kindgi('providers', 'register', '--spec=@<file>');
786
+ return `${reRegisterSteps(id, register, kindgi)}${preset === undefined ? ' (its spec with the setting fixed)' : ''}`;
787
+ }
788
+ /** A provider's id is taken while it's registered: unregister it first. */
789
+ function reRegisterSteps(id, register, kindgi) {
790
+ return `Unregister ${id} (${kindgi('providers', 'unregister', id)}), then register it again: ${register}.`;
791
+ }
393
792
  /**
394
793
  * In a TypeScript project, its own CLI (`pnpm exec kindgi …`,
395
794
  * `npx --no kindgi …`); in a Python project or outside one, the
@@ -397,8 +796,9 @@ async function providerCheck(ctx, apiUrl, token, kindgi, seam) {
397
796
  */
398
797
  async function kindgiCommand(dir, language, pypi, tool) {
399
798
  if (pypi) {
400
- // The PyPI build runs from the project's own environment; outside one, uvx.
401
- if (language === undefined) {
799
+ // The PyPI build runs from the project's own environment; outside one (or in a
800
+ // Java or Scala project, which has none), uvx.
801
+ if (language === undefined || language === 'java' || language === 'scala') {
402
802
  return (...args) => ['uvx', '--from', 'kindgi-cli', 'kindgi', ...args].join(' ');
403
803
  }
404
804
  const runner = await pythonBinRunner(dir, { KINDGI_CLI_INSTALL: 'pypi' });