@kindgi/cli 0.1.4 → 0.1.5

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 (325) hide show
  1. package/README.md +13 -7
  2. package/dist/build/bundle.d.ts +11 -3
  3. package/dist/build/bundle.d.ts.map +1 -1
  4. package/dist/build/bundle.js +17 -3
  5. package/dist/build/bundle.js.map +1 -1
  6. package/dist/build/defaults.d.ts +3 -1
  7. package/dist/build/defaults.d.ts.map +1 -1
  8. package/dist/build/defaults.js +44 -1
  9. package/dist/build/defaults.js.map +1 -1
  10. package/dist/build/java-image.d.ts +31 -0
  11. package/dist/build/java-image.d.ts.map +1 -0
  12. package/dist/build/java-image.js +146 -0
  13. package/dist/build/java-image.js.map +1 -0
  14. package/dist/build/pack-root.d.ts +1 -31
  15. package/dist/build/pack-root.d.ts.map +1 -1
  16. package/dist/build/pack-root.js +22 -2
  17. package/dist/build/pack-root.js.map +1 -1
  18. package/dist/build/runners.d.ts +44 -0
  19. package/dist/build/runners.d.ts.map +1 -1
  20. package/dist/build/scala-image.d.ts +21 -0
  21. package/dist/build/scala-image.d.ts.map +1 -0
  22. package/dist/build/scala-image.js +150 -0
  23. package/dist/build/scala-image.js.map +1 -0
  24. package/dist/cli-pin.d.ts +16 -0
  25. package/dist/cli-pin.d.ts.map +1 -0
  26. package/dist/cli-pin.js +66 -0
  27. package/dist/cli-pin.js.map +1 -0
  28. package/dist/commands/agents.d.ts.map +1 -1
  29. package/dist/commands/agents.js +9 -8
  30. package/dist/commands/agents.js.map +1 -1
  31. package/dist/commands/approvals.d.ts.map +1 -1
  32. package/dist/commands/approvals.js +30 -4
  33. package/dist/commands/approvals.js.map +1 -1
  34. package/dist/commands/artifacts.d.ts.map +1 -1
  35. package/dist/commands/artifacts.js +100 -37
  36. package/dist/commands/artifacts.js.map +1 -1
  37. package/dist/commands/auth.js +2 -2
  38. package/dist/commands/blocks.d.ts.map +1 -1
  39. package/dist/commands/blocks.js +5 -4
  40. package/dist/commands/blocks.js.map +1 -1
  41. package/dist/commands/build.d.ts +10 -2
  42. package/dist/commands/build.d.ts.map +1 -1
  43. package/dist/commands/build.js +109 -16
  44. package/dist/commands/build.js.map +1 -1
  45. package/dist/commands/capabilities.d.ts.map +1 -1
  46. package/dist/commands/capabilities.js +37 -10
  47. package/dist/commands/capabilities.js.map +1 -1
  48. package/dist/commands/console.d.ts +21 -0
  49. package/dist/commands/console.d.ts.map +1 -0
  50. package/dist/commands/console.js +87 -0
  51. package/dist/commands/console.js.map +1 -0
  52. package/dist/commands/conversations.d.ts.map +1 -1
  53. package/dist/commands/conversations.js +13 -2
  54. package/dist/commands/conversations.js.map +1 -1
  55. package/dist/commands/deploy.d.ts.map +1 -1
  56. package/dist/commands/deploy.js +2 -2
  57. package/dist/commands/deploy.js.map +1 -1
  58. package/dist/commands/dev.d.ts +11 -0
  59. package/dist/commands/dev.d.ts.map +1 -1
  60. package/dist/commands/dev.js +212 -40
  61. package/dist/commands/dev.js.map +1 -1
  62. package/dist/commands/doctor.d.ts +14 -2
  63. package/dist/commands/doctor.d.ts.map +1 -1
  64. package/dist/commands/doctor.js +356 -20
  65. package/dist/commands/doctor.js.map +1 -1
  66. package/dist/commands/env-scoped.d.ts +44 -0
  67. package/dist/commands/env-scoped.d.ts.map +1 -0
  68. package/dist/commands/env-scoped.js +222 -0
  69. package/dist/commands/env-scoped.js.map +1 -0
  70. package/dist/commands/env.d.ts.map +1 -1
  71. package/dist/commands/env.js +85 -123
  72. package/dist/commands/env.js.map +1 -1
  73. package/dist/commands/eval-runs.d.ts +9 -0
  74. package/dist/commands/eval-runs.d.ts.map +1 -1
  75. package/dist/commands/eval-runs.js +20 -19
  76. package/dist/commands/eval-runs.js.map +1 -1
  77. package/dist/commands/eval-suites.d.ts.map +1 -1
  78. package/dist/commands/eval-suites.js +16 -8
  79. package/dist/commands/eval-suites.js.map +1 -1
  80. package/dist/commands/exports.d.ts +3 -0
  81. package/dist/commands/exports.d.ts.map +1 -0
  82. package/dist/commands/exports.js +82 -0
  83. package/dist/commands/exports.js.map +1 -0
  84. package/dist/commands/feedback.d.ts.map +1 -1
  85. package/dist/commands/feedback.js +6 -5
  86. package/dist/commands/feedback.js.map +1 -1
  87. package/dist/commands/flows.d.ts.map +1 -1
  88. package/dist/commands/flows.js +2 -1
  89. package/dist/commands/flows.js.map +1 -1
  90. package/dist/commands/gate-policies.d.ts.map +1 -1
  91. package/dist/commands/gate-policies.js +2 -1
  92. package/dist/commands/gate-policies.js.map +1 -1
  93. package/dist/commands/guardrails.d.ts.map +1 -1
  94. package/dist/commands/guardrails.js +3 -2
  95. package/dist/commands/guardrails.js.map +1 -1
  96. package/dist/commands/helpers.js +5 -5
  97. package/dist/commands/helpers.js.map +1 -1
  98. package/dist/commands/index.d.ts.map +1 -1
  99. package/dist/commands/index.js +14 -0
  100. package/dist/commands/index.js.map +1 -1
  101. package/dist/commands/init.d.ts +1 -1
  102. package/dist/commands/init.d.ts.map +1 -1
  103. package/dist/commands/init.js +254 -18
  104. package/dist/commands/init.js.map +1 -1
  105. package/dist/commands/judge-classes.d.ts.map +1 -1
  106. package/dist/commands/judge-classes.js +11 -10
  107. package/dist/commands/judge-classes.js.map +1 -1
  108. package/dist/commands/judgments.d.ts.map +1 -1
  109. package/dist/commands/judgments.js +7 -6
  110. package/dist/commands/judgments.js.map +1 -1
  111. package/dist/commands/key.js +4 -4
  112. package/dist/commands/memory.d.ts.map +1 -1
  113. package/dist/commands/memory.js +277 -25
  114. package/dist/commands/memory.js.map +1 -1
  115. package/dist/commands/people.d.ts +3 -0
  116. package/dist/commands/people.d.ts.map +1 -0
  117. package/dist/commands/people.js +166 -0
  118. package/dist/commands/people.js.map +1 -0
  119. package/dist/commands/proposals.d.ts.map +1 -1
  120. package/dist/commands/proposals.js +390 -69
  121. package/dist/commands/proposals.js.map +1 -1
  122. package/dist/commands/provenance.d.ts.map +1 -1
  123. package/dist/commands/provenance.js +6 -7
  124. package/dist/commands/provenance.js.map +1 -1
  125. package/dist/commands/providers.d.ts.map +1 -1
  126. package/dist/commands/providers.js +7 -5
  127. package/dist/commands/providers.js.map +1 -1
  128. package/dist/commands/reviewers.d.ts.map +1 -1
  129. package/dist/commands/reviewers.js +3 -2
  130. package/dist/commands/reviewers.js.map +1 -1
  131. package/dist/commands/runs.d.ts.map +1 -1
  132. package/dist/commands/runs.js +35 -15
  133. package/dist/commands/runs.js.map +1 -1
  134. package/dist/commands/schedules.d.ts +3 -0
  135. package/dist/commands/schedules.d.ts.map +1 -0
  136. package/dist/commands/schedules.js +275 -0
  137. package/dist/commands/schedules.js.map +1 -0
  138. package/dist/commands/secrets.d.ts.map +1 -1
  139. package/dist/commands/secrets.js +10 -11
  140. package/dist/commands/secrets.js.map +1 -1
  141. package/dist/commands/service-accounts.d.ts +3 -0
  142. package/dist/commands/service-accounts.d.ts.map +1 -0
  143. package/dist/commands/service-accounts.js +175 -0
  144. package/dist/commands/service-accounts.js.map +1 -0
  145. package/dist/commands/skills.d.ts +1 -1
  146. package/dist/commands/skills.d.ts.map +1 -1
  147. package/dist/commands/skills.js +11 -4
  148. package/dist/commands/skills.js.map +1 -1
  149. package/dist/commands/sso.d.ts +6 -0
  150. package/dist/commands/sso.d.ts.map +1 -0
  151. package/dist/commands/sso.js +318 -0
  152. package/dist/commands/sso.js.map +1 -0
  153. package/dist/commands/tokens.d.ts +11 -0
  154. package/dist/commands/tokens.d.ts.map +1 -1
  155. package/dist/commands/tokens.js +144 -11
  156. package/dist/commands/tokens.js.map +1 -1
  157. package/dist/commands/tools.d.ts.map +1 -1
  158. package/dist/commands/tools.js +4 -3
  159. package/dist/commands/tools.js.map +1 -1
  160. package/dist/commands/unwired.d.ts.map +1 -1
  161. package/dist/commands/unwired.js +1 -30
  162. package/dist/commands/unwired.js.map +1 -1
  163. package/dist/commands/upgrade.d.ts +11 -0
  164. package/dist/commands/upgrade.d.ts.map +1 -0
  165. package/dist/commands/upgrade.js +160 -0
  166. package/dist/commands/upgrade.js.map +1 -0
  167. package/dist/context.d.ts +7 -0
  168. package/dist/context.d.ts.map +1 -1
  169. package/dist/context.js +1 -0
  170. package/dist/context.js.map +1 -1
  171. package/dist/dev/defaults.d.ts +28 -11
  172. package/dist/dev/defaults.d.ts.map +1 -1
  173. package/dist/dev/defaults.js +114 -35
  174. package/dist/dev/defaults.js.map +1 -1
  175. package/dist/dev/google-credentials.d.ts +50 -0
  176. package/dist/dev/google-credentials.d.ts.map +1 -0
  177. package/dist/dev/google-credentials.js +157 -0
  178. package/dist/dev/google-credentials.js.map +1 -0
  179. package/dist/dev/java-builder.d.ts +56 -0
  180. package/dist/dev/java-builder.d.ts.map +1 -0
  181. package/dist/dev/java-builder.js +204 -0
  182. package/dist/dev/java-builder.js.map +1 -0
  183. package/dist/dev/jvm-run-files.d.ts +29 -0
  184. package/dist/dev/jvm-run-files.d.ts.map +1 -0
  185. package/dist/dev/jvm-run-files.js +94 -0
  186. package/dist/dev/jvm-run-files.js.map +1 -0
  187. package/dist/dev/lines.d.ts +13 -0
  188. package/dist/dev/lines.d.ts.map +1 -0
  189. package/dist/dev/lines.js +24 -0
  190. package/dist/dev/lines.js.map +1 -0
  191. package/dist/dev/log-view.d.ts +116 -0
  192. package/dist/dev/log-view.d.ts.map +1 -0
  193. package/dist/dev/log-view.js +233 -0
  194. package/dist/dev/log-view.js.map +1 -0
  195. package/dist/dev/pack-code.d.ts +79 -4
  196. package/dist/dev/pack-code.d.ts.map +1 -1
  197. package/dist/dev/pack-code.js +262 -3
  198. package/dist/dev/pack-code.js.map +1 -1
  199. package/dist/dev/pack-env.d.ts.map +1 -1
  200. package/dist/dev/pack-env.js +4 -0
  201. package/dist/dev/pack-env.js.map +1 -1
  202. package/dist/dev/pack-service.d.ts +11 -1
  203. package/dist/dev/pack-service.d.ts.map +1 -1
  204. package/dist/dev/pack-service.js +56 -20
  205. package/dist/dev/pack-service.js.map +1 -1
  206. package/dist/dev/paths.d.ts +6 -0
  207. package/dist/dev/paths.d.ts.map +1 -1
  208. package/dist/dev/paths.js +8 -0
  209. package/dist/dev/paths.js.map +1 -1
  210. package/dist/dev/project-database.js +1 -1
  211. package/dist/dev/project-database.js.map +1 -1
  212. package/dist/dev/runners.d.ts +35 -9
  213. package/dist/dev/runners.d.ts.map +1 -1
  214. package/dist/dev/runtime-container.d.ts +10 -2
  215. package/dist/dev/runtime-container.d.ts.map +1 -1
  216. package/dist/dev/runtime-container.js +48 -20
  217. package/dist/dev/runtime-container.js.map +1 -1
  218. package/dist/dev/runtime-env.d.ts +10 -0
  219. package/dist/dev/runtime-env.d.ts.map +1 -1
  220. package/dist/dev/runtime-env.js +8 -4
  221. package/dist/dev/runtime-env.js.map +1 -1
  222. package/dist/dev/runtime-image.d.ts +1 -1
  223. package/dist/dev/runtime-image.js +1 -1
  224. package/dist/dev/scala-builder.d.ts +72 -0
  225. package/dist/dev/scala-builder.d.ts.map +1 -0
  226. package/dist/dev/scala-builder.js +347 -0
  227. package/dist/dev/scala-builder.js.map +1 -0
  228. package/dist/env/project-env.d.ts.map +1 -1
  229. package/dist/env/project-env.js +3 -1
  230. package/dist/env/project-env.js.map +1 -1
  231. package/dist/errors.d.ts +9 -0
  232. package/dist/errors.d.ts.map +1 -1
  233. package/dist/errors.js +15 -0
  234. package/dist/errors.js.map +1 -1
  235. package/dist/init/augment-scaffolder.js +1 -1
  236. package/dist/init/augment-scaffolder.js.map +1 -1
  237. package/dist/init/dependency-specs.d.ts +26 -0
  238. package/dist/init/dependency-specs.d.ts.map +1 -1
  239. package/dist/init/dependency-specs.js +38 -0
  240. package/dist/init/dependency-specs.js.map +1 -1
  241. package/dist/init/java-augment.d.ts +24 -0
  242. package/dist/init/java-augment.d.ts.map +1 -0
  243. package/dist/init/java-augment.js +198 -0
  244. package/dist/init/java-augment.js.map +1 -0
  245. package/dist/init/mode-detect.d.ts +2 -2
  246. package/dist/init/mode-detect.d.ts.map +1 -1
  247. package/dist/init/mode-detect.js +12 -1
  248. package/dist/init/mode-detect.js.map +1 -1
  249. package/dist/init/python-augment.js +1 -1
  250. package/dist/init/python-augment.js.map +1 -1
  251. package/dist/init/scala-augment.d.ts +23 -0
  252. package/dist/init/scala-augment.d.ts.map +1 -0
  253. package/dist/init/scala-augment.js +213 -0
  254. package/dist/init/scala-augment.js.map +1 -0
  255. package/dist/init/template-files.d.ts +40 -2
  256. package/dist/init/template-files.d.ts.map +1 -1
  257. package/dist/init/template-files.js +56 -5
  258. package/dist/init/template-files.js.map +1 -1
  259. package/dist/main.d.ts +3 -0
  260. package/dist/main.d.ts.map +1 -1
  261. package/dist/main.js +20 -2
  262. package/dist/main.js.map +1 -1
  263. package/dist/open-url.d.ts +15 -0
  264. package/dist/open-url.d.ts.map +1 -0
  265. package/dist/open-url.js +41 -0
  266. package/dist/open-url.js.map +1 -0
  267. package/dist/package-manager.d.ts +10 -3
  268. package/dist/package-manager.d.ts.map +1 -1
  269. package/dist/package-manager.js +19 -1
  270. package/dist/package-manager.js.map +1 -1
  271. package/dist/providers/presets/anthropic.json +11 -3
  272. package/dist/sdk-skills/kindgi-authoring-agents/SKILL.md +30 -2
  273. package/dist/sdk-skills/kindgi-authoring-flows/SKILL.md +1 -1
  274. package/dist/sdk-skills/kindgi-authoring-guardrails/SKILL.md +47 -3
  275. package/dist/sdk-skills/kindgi-authoring-mcp-servers/SKILL.md +7 -5
  276. package/dist/sdk-skills/kindgi-authoring-providers/SKILL.md +40 -15
  277. package/dist/sdk-skills/kindgi-authoring-tools/SKILL.md +66 -2
  278. package/dist/sdk-skills/kindgi-framework-feedback/SKILL.md +5 -4
  279. package/dist/sdk-skills/kindgi-getting-started/SKILL.md +1 -1
  280. package/dist/sdk-skills/kindgi-java-authoring-agents/SKILL.md +220 -0
  281. package/dist/sdk-skills/kindgi-java-authoring-flows/SKILL.md +390 -0
  282. package/dist/sdk-skills/kindgi-java-authoring-guardrails/SKILL.md +209 -0
  283. package/dist/sdk-skills/kindgi-java-authoring-tools/SKILL.md +334 -0
  284. package/dist/sdk-skills/kindgi-java-getting-started/SKILL.md +270 -0
  285. package/dist/sdk-skills/kindgi-python-authoring-agents/SKILL.md +25 -3
  286. package/dist/sdk-skills/kindgi-python-authoring-flows/SKILL.md +1 -1
  287. package/dist/sdk-skills/kindgi-python-authoring-guardrails/SKILL.md +10 -3
  288. package/dist/sdk-skills/kindgi-python-authoring-tools/SKILL.md +45 -6
  289. package/dist/sdk-skills/kindgi-python-getting-started/SKILL.md +2 -2
  290. package/dist/sdk-skills/kindgi-scala-authoring-agents/SKILL.md +217 -0
  291. package/dist/sdk-skills/kindgi-scala-authoring-flows/SKILL.md +357 -0
  292. package/dist/sdk-skills/kindgi-scala-authoring-guardrails/SKILL.md +199 -0
  293. package/dist/sdk-skills/kindgi-scala-authoring-tools/SKILL.md +302 -0
  294. package/dist/sdk-skills/kindgi-scala-getting-started/SKILL.md +302 -0
  295. package/dist/templates/java/.mvn/wrapper/maven-wrapper.properties +3 -0
  296. package/dist/templates/java/AGENTS.md +31 -0
  297. package/dist/templates/java/README.md.tmpl +69 -0
  298. package/dist/templates/java/gitignore +9 -0
  299. package/dist/templates/java/kindgi.config.json.tmpl +8 -0
  300. package/dist/templates/java/kindgiw +33 -0
  301. package/dist/templates/java/kindgiw.cmd +28 -0
  302. package/dist/templates/java/mvnw +295 -0
  303. package/dist/templates/java/pom.xml.tmpl +65 -0
  304. package/dist/templates/java/src/main/java/__PACKAGE__/agents/EchoAgent.java.tmpl +27 -0
  305. package/dist/templates/java/src/main/java/__PACKAGE__/flows/EchoFlow.java.tmpl +18 -0
  306. package/dist/templates/java/src/main/java/__PACKAGE__/guardrails/ResponseNotEmpty.java.tmpl +28 -0
  307. package/dist/templates/java/src/main/java/__PACKAGE__/tools/Echo.java.tmpl +22 -0
  308. package/dist/templates/java/src/main/java/__PACKAGE__/tools/Greet.java.tmpl +23 -0
  309. package/dist/templates/java/src/test/java/__PACKAGE__/ToolsTest.java.tmpl +30 -0
  310. package/dist/templates/minimal/README.md.tmpl +6 -12
  311. package/dist/templates/python/README.md.tmpl +4 -4
  312. package/dist/templates/sample/README.md.tmpl +5 -11
  313. package/dist/templates/scala/AGENTS.md +32 -0
  314. package/dist/templates/scala/README.md.tmpl +75 -0
  315. package/dist/templates/scala/build.sbt.tmpl +16 -0
  316. package/dist/templates/scala/gitignore +13 -0
  317. package/dist/templates/scala/kindgi.config.json.tmpl +8 -0
  318. package/dist/templates/scala/project/build.properties +1 -0
  319. package/dist/templates/scala/src/main/scala/__PACKAGE__/agents/EchoAgent.scala.tmpl +23 -0
  320. package/dist/templates/scala/src/main/scala/__PACKAGE__/flows/EchoFlow.scala.tmpl +16 -0
  321. package/dist/templates/scala/src/main/scala/__PACKAGE__/guardrails/ResponseNotEmpty.scala.tmpl +21 -0
  322. package/dist/templates/scala/src/main/scala/__PACKAGE__/tools/Echo.scala.tmpl +16 -0
  323. package/dist/templates/scala/src/main/scala/__PACKAGE__/tools/Greet.scala.tmpl +17 -0
  324. package/dist/templates/scala/src/test/scala/__PACKAGE__/ToolsSuite.scala.tmpl +23 -0
  325. package/package.json +13 -12
@@ -26,13 +26,17 @@ import { readFile, readdir, stat } from 'node:fs/promises';
26
26
  import { dirname, join, resolve } from 'node:path';
27
27
  import { findKindgiConfig } from '@kindgi/handler-runtime';
28
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';
29
31
  import { docker } from '../dev/runtime-container.js';
30
32
  import { DEFAULT_RUNTIME_IMAGE, registryOf } from '../dev/runtime-image.js';
31
33
  import { checkDocker, checkImageAccess, credentialHelperHint } from '../dev/runtime-registry.js';
34
+ import { extractKindgiError } from '../errors.js';
32
35
  import { renderJson } from '../output.js';
33
36
  import { binCommand, cliInstall, defaultDetectIo, publishedCliSpec, pythonBinRunner, usablePackageManager, } from '../package-manager.js';
34
37
  import { loadProviderPresets } from '../providers/preset-loader.js';
35
38
  import { CLI_VERSION } from '../version-info.js';
39
+ import { probeConsole } from './console.js';
36
40
  /**
37
41
  * The provider `kindgi dev` falls back to, with canned replies
38
42
  * (`DEV_ECHO_PROVIDER_ID` in `@kindgi/dev-echo-provider`): not a model.
@@ -42,11 +46,16 @@ const DEV_ECHO_PROVIDER_ID = 'dev-echo';
42
46
  export const MIN_NODE = '22.12.0';
43
47
  /** The Python a Python project needs (`requires-python` in its pyproject.toml). */
44
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;
45
51
  const TITLES = {
46
52
  node: 'Node.js',
47
53
  npm: 'npm',
48
54
  python: 'Python',
49
55
  uv: 'uv',
56
+ java: 'Java',
57
+ maven: 'Maven',
58
+ sbt: 'sbt',
50
59
  docker: 'Docker',
51
60
  registry: 'Runtime image',
52
61
  project: 'Project',
@@ -54,6 +63,8 @@ const TITLES = {
54
63
  'model-key': 'Model key',
55
64
  runtime: 'Runtime',
56
65
  provider: 'Provider',
66
+ 'console-sign-in': 'Console sign-in',
67
+ erasures: 'Erasures',
57
68
  };
58
69
  export const doctorCommand = {
59
70
  kind: 'leaf',
@@ -83,24 +94,32 @@ export async function runDoctor(ctx, dir) {
83
94
  const run = seam.docker ?? docker;
84
95
  const checks = [];
85
96
  const config = await findKindgiConfig(dir);
86
- 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';
87
104
  const pypi = cliInstall(ctx.env) === 'pypi';
88
105
  const kindgi = await kindgiCommand(dir, language, pypi, tool);
89
106
  checks.push(...(await nodeChecks(seam.nodeVersion ?? process.versions.node, tool, pypi)));
90
107
  checks.push(...(await pythonChecks(tool, language)));
108
+ checks.push(...(await jvmChecks(tool, language, dir, ctx.env)));
91
109
  const dockerCheck = await checkDockerRunning(run);
92
110
  checks.push(dockerCheck);
93
111
  checks.push(dockerCheck.status === 'pass'
94
112
  ? await registryCheck(run, seam.image ?? DEFAULT_RUNTIME_IMAGE, kindgi)
95
113
  : skip('registry', 'Not checked: it needs Docker running.'));
114
+ checks.push(await consoleSignInCheck(ctx));
96
115
  if (config === undefined || language === undefined) {
97
116
  checks.push({
98
117
  id: 'project',
99
118
  status: 'skip',
100
- 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.`,
101
120
  fix: createProjectFix(kindgi, pypi),
102
121
  });
103
- for (const id of ['dependencies', 'model-key', 'runtime', 'provider']) {
122
+ for (const id of ['dependencies', 'model-key', 'runtime', 'provider', 'erasures']) {
104
123
  checks.push(skip(id, 'Not checked: it needs a project.'));
105
124
  }
106
125
  return report(checks, null);
@@ -112,9 +131,12 @@ export async function runDoctor(ctx, dir) {
112
131
  const runtime = await runtimeCheck(ctx, rc, kindgi);
113
132
  checks.push(runtime.check);
114
133
  checks.push(runtime.check.status === 'pass' && runtime.url !== undefined && rc.token !== undefined
115
- ? await providerCheck(ctx, runtime.url, rc.token, kindgi, seam)
134
+ ? await providerCheck(ctx, runtime.url, rc.token, kindgi, seam, dir)
116
135
  : skip('provider', `Not checked: it needs the runtime running (${kindgi('dev')}).`));
117
- 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);
118
140
  }
119
141
  /** The report as ✓/✗ lines, each failure with its fix. */
120
142
  export function doctorText(report) {
@@ -122,6 +144,8 @@ export function doctorText(report) {
122
144
  for (const check of report.checks) {
123
145
  const mark = MARKS[check.status];
124
146
  lines.push(` ${mark} ${TITLES[check.id]}: ${check.message}`);
147
+ for (const detail of check.details ?? [])
148
+ lines.push(` ✗ ${detail}`);
125
149
  if (check.fix !== undefined && check.status !== 'pass')
126
150
  lines.push(` Fix: ${check.fix}`);
127
151
  }
@@ -141,11 +165,12 @@ const MARKS = {
141
165
  fail: '✗',
142
166
  skip: '–',
143
167
  };
144
- function report(checks, project) {
168
+ function report(checks, project, consoleUrl) {
145
169
  return {
146
170
  ok: checks.every((c) => c.status !== 'fail'),
147
171
  cliVersion: CLI_VERSION,
148
172
  project,
173
+ ...(consoleUrl !== undefined && { consoleUrl }),
149
174
  checks,
150
175
  };
151
176
  }
@@ -182,7 +207,7 @@ async function nodeChecks(version, tool, pypi) {
182
207
  function createProjectFix(kindgi, pypi) {
183
208
  return pypi
184
209
  ? `Create one: ${kindgi('init', '<name>', '--template=python')}, then run doctor in its folder.`
185
- : `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.`;
186
211
  }
187
212
  function nodeCheck(version, pypi) {
188
213
  return atLeast(version, MIN_NODE)
@@ -208,7 +233,7 @@ async function pythonChecks(tool, language) {
208
233
  const pythonFix = `Install Python ${MIN_PYTHON.replace(/\.0$/, '')} or later (https://www.python.org/downloads, or: uv python install 3.12).`;
209
234
  const uvFix = 'Install uv: curl -LsSf https://astral.sh/uv/install.sh | sh (or: brew install uv).';
210
235
  if (language !== 'python') {
211
- const why = language === 'node' ? 'a TypeScript project' : 'no project yet';
236
+ const why = projectKind(language);
212
237
  return [
213
238
  skip('python', `Not needed (${why}); ${python !== undefined ? `Python ${python} is installed` : "Python isn't installed"}, for a Python project (kindgi init --template=python).`),
214
239
  skip('uv', `Not needed (${why}); ${uvVersion !== undefined ? `uv ${uvVersion} is installed` : "uv isn't installed"}, for a Python project.`),
@@ -225,6 +250,95 @@ async function pythonChecks(tool, language) {
225
250
  : fail('uv', "uv isn't installed: a Kindgi Python project installs and runs with it.", uvFix),
226
251
  ];
227
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
+ }
228
342
  async function pythonVersion(tool) {
229
343
  for (const command of ['python3', 'python']) {
230
344
  const got = await tool(command, ['--version']);
@@ -245,7 +359,7 @@ async function checkDockerRunning(run) {
245
359
  async function registryCheck(run, image, kindgi) {
246
360
  const access = await checkImageAccess(run, image);
247
361
  const host = registryOf(image);
248
- const login = `${kindgi('auth', 'registry', '--username', '<robot name>', '--password-stdin')} (the robot name and token come from access.kindgi.com; pipe the token in, never paste it into a chat)`;
362
+ const login = `${kindgi('auth', 'registry', '--username', '<robot name>', '--password-stdin')} (request the robot name and token at contact@kindgi.com; pipe the token in, never paste it into a chat)`;
249
363
  switch (access.kind) {
250
364
  case 'ok':
251
365
  return pass('registry', `Docker can pull the runtime image (${image}).`);
@@ -285,13 +399,53 @@ async function readKindgirc(dir) {
285
399
  function projectCheck(dir, language, rc) {
286
400
  const kind = language === 'python'
287
401
  ? 'A Python project (pyproject.toml)'
288
- : '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)';
289
407
  if (rc.malformed !== undefined) {
290
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.');
291
409
  }
292
410
  return pass('project', `${kind} in ${dir}; ${rc.exists ? 'kindgi dev has run here (.kindgirc.json)' : "kindgi dev hasn't run here yet"}.`);
293
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
+ }
294
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);
295
449
  if (language === 'python') {
296
450
  return (await pythonPackageInstalled(dir))
297
451
  ? pass('dependencies', 'The kindgi package is installed in .venv.')
@@ -359,6 +513,32 @@ async function modelKeyCheck(dir, seam, hostEnv, kindgi) {
359
513
  const others = names.slice(1);
360
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.`);
361
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
+ }
362
542
  // ---------- the runtime ----------
363
543
  async function runtimeCheck(ctx, rc, kindgi) {
364
544
  const start = `Start it: ${kindgi('dev')} (it keeps running; stop it with Ctrl+C).`;
@@ -371,8 +551,20 @@ async function runtimeCheck(ctx, rc, kindgi) {
371
551
  const url = `${rc.apiUrl.replace(/\/+$/, '')}/health`;
372
552
  try {
373
553
  const res = await ctx.fetch(url, { method: 'GET', signal: AbortSignal.timeout(5000) });
374
- if (res.ok)
375
- 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
+ }
376
568
  return {
377
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.`),
378
570
  };
@@ -388,7 +580,66 @@ async function runtimeCheck(ctx, rc, kindgi) {
388
580
  };
389
581
  }
390
582
  }
391
- 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) {
392
643
  try {
393
644
  const page = await ctx.clientFor(apiUrl, token).providers.list();
394
645
  const listed = page.data;
@@ -400,10 +651,34 @@ async function providerCheck(ctx, apiUrl, token, kindgi, seam) {
400
651
  : `Register one: ${kindgi('providers', 'register', `--preset=${presets[0] ?? 'anthropic'}`)} (its key must be set first; see Model key).`;
401
652
  if (models.length > 0) {
402
653
  const registered = `${models.length === 1 ? 'A provider is' : `${models.length} providers are`} registered: ${models.join(', ')}.`;
403
- const stale = staleDefaults(listed, await (seam.presets ?? (() => loadProviderPresets()))(), kindgi);
404
- return stale.length === 0
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
405
680
  ? pass('provider', registered)
406
- : warn('provider', [registered, ...stale.map((s) => s.message)].join(' '), stale.map((s) => s.fix).join(' '));
681
+ : warn('provider', [registered, ...notes.map((s) => s.message)].join(' '), notes.map((s) => s.fix).join(' '));
407
682
  }
408
683
  return ids.length > 0
409
684
  ? fail('provider', `Only ${DEV_ECHO_PROVIDER_ID} is registered: agents get its canned replies, not a model's.`, register)
@@ -413,6 +688,28 @@ async function providerCheck(ctx, apiUrl, token, kindgi, seam) {
413
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.');
414
689
  }
415
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
+ }
416
713
  /**
417
714
  * Registrations made from a preset (the preset's provider id) whose
418
715
  * agents that name no model land where the preset no longer would send
@@ -435,7 +732,7 @@ function staleDefaults(listed, presets, kindgi) {
435
732
  return [
436
733
  {
437
734
  message: `On ${p.id}, an agent that names no model gets ${lands}, which the ${preset.name} preset no longer lists.`,
438
- fix: `Re-register ${p.id} for the preset's current models: ${register}. Or name a model on your agents (preferredModel).`,
735
+ fix: `${reRegisterSteps(p.id, register, kindgi)} Or name a model on your agents (preferredModel).`,
439
736
  },
440
737
  ];
441
738
  }
@@ -447,13 +744,51 @@ function staleDefaults(listed, presets, kindgi) {
447
744
  return [
448
745
  {
449
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}.`,
450
- fix: `Re-register ${p.id}: ${register}. A runtime older than 0.1.4 doesn't keep a default model: there, name a model on your agents (preferredModel).`,
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).`,
451
748
  },
452
749
  ];
453
750
  }
454
751
  return [];
455
752
  });
456
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
+ }
457
792
  /**
458
793
  * In a TypeScript project, its own CLI (`pnpm exec kindgi …`,
459
794
  * `npx --no kindgi …`); in a Python project or outside one, the
@@ -461,8 +796,9 @@ function staleDefaults(listed, presets, kindgi) {
461
796
  */
462
797
  async function kindgiCommand(dir, language, pypi, tool) {
463
798
  if (pypi) {
464
- // The PyPI build runs from the project's own environment; outside one, uvx.
465
- 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') {
466
802
  return (...args) => ['uvx', '--from', 'kindgi-cli', 'kindgi', ...args].join(' ');
467
803
  }
468
804
  const runner = await pythonBinRunner(dir, { KINDGI_CLI_INSTALL: 'pypi' });