@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.
- package/README.md +16 -10
- package/dist/build/defaults.d.ts +3 -1
- package/dist/build/defaults.d.ts.map +1 -1
- package/dist/build/defaults.js +44 -1
- package/dist/build/defaults.js.map +1 -1
- package/dist/build/java-image.d.ts +31 -0
- package/dist/build/java-image.d.ts.map +1 -0
- package/dist/build/java-image.js +146 -0
- package/dist/build/java-image.js.map +1 -0
- package/dist/build/pack-root.d.ts +1 -31
- package/dist/build/pack-root.d.ts.map +1 -1
- package/dist/build/pack-root.js +22 -2
- package/dist/build/pack-root.js.map +1 -1
- package/dist/build/runners.d.ts +44 -0
- package/dist/build/runners.d.ts.map +1 -1
- package/dist/build/scala-image.d.ts +21 -0
- package/dist/build/scala-image.d.ts.map +1 -0
- package/dist/build/scala-image.js +150 -0
- package/dist/build/scala-image.js.map +1 -0
- package/dist/cli-pin.d.ts +16 -0
- package/dist/cli-pin.d.ts.map +1 -0
- package/dist/cli-pin.js +66 -0
- package/dist/cli-pin.js.map +1 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +9 -8
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/approvals.d.ts.map +1 -1
- package/dist/commands/approvals.js +30 -4
- package/dist/commands/approvals.js.map +1 -1
- package/dist/commands/artifacts.d.ts.map +1 -1
- package/dist/commands/artifacts.js +100 -37
- package/dist/commands/artifacts.js.map +1 -1
- package/dist/commands/auth.js +2 -2
- package/dist/commands/blocks.d.ts.map +1 -1
- package/dist/commands/blocks.js +5 -4
- package/dist/commands/blocks.js.map +1 -1
- package/dist/commands/build.d.ts +10 -2
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +109 -16
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/capabilities.d.ts.map +1 -1
- package/dist/commands/capabilities.js +37 -10
- package/dist/commands/capabilities.js.map +1 -1
- package/dist/commands/console.d.ts +21 -0
- package/dist/commands/console.d.ts.map +1 -0
- package/dist/commands/console.js +87 -0
- package/dist/commands/console.js.map +1 -0
- package/dist/commands/conversations.d.ts.map +1 -1
- package/dist/commands/conversations.js +13 -2
- package/dist/commands/conversations.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +2 -2
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts +11 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +211 -39
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +18 -5
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +421 -21
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/env-scoped.d.ts +44 -0
- package/dist/commands/env-scoped.d.ts.map +1 -0
- package/dist/commands/env-scoped.js +222 -0
- package/dist/commands/env-scoped.js.map +1 -0
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +85 -123
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/eval-runs.d.ts +9 -0
- package/dist/commands/eval-runs.d.ts.map +1 -1
- package/dist/commands/eval-runs.js +20 -19
- package/dist/commands/eval-runs.js.map +1 -1
- package/dist/commands/eval-suites.d.ts.map +1 -1
- package/dist/commands/eval-suites.js +16 -8
- package/dist/commands/eval-suites.js.map +1 -1
- package/dist/commands/exports.d.ts +3 -0
- package/dist/commands/exports.d.ts.map +1 -0
- package/dist/commands/exports.js +82 -0
- package/dist/commands/exports.js.map +1 -0
- package/dist/commands/feedback.d.ts.map +1 -1
- package/dist/commands/feedback.js +6 -5
- package/dist/commands/feedback.js.map +1 -1
- package/dist/commands/flows.d.ts.map +1 -1
- package/dist/commands/flows.js +2 -1
- package/dist/commands/flows.js.map +1 -1
- package/dist/commands/gate-policies.d.ts.map +1 -1
- package/dist/commands/gate-policies.js +2 -1
- package/dist/commands/gate-policies.js.map +1 -1
- package/dist/commands/guardrails.d.ts.map +1 -1
- package/dist/commands/guardrails.js +3 -2
- package/dist/commands/guardrails.js.map +1 -1
- package/dist/commands/helpers.js +5 -5
- package/dist/commands/helpers.js.map +1 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +14 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +254 -18
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/judge-classes.d.ts.map +1 -1
- package/dist/commands/judge-classes.js +11 -10
- package/dist/commands/judge-classes.js.map +1 -1
- package/dist/commands/judgments.d.ts.map +1 -1
- package/dist/commands/judgments.js +7 -6
- package/dist/commands/judgments.js.map +1 -1
- package/dist/commands/key.js +4 -4
- package/dist/commands/memory.d.ts.map +1 -1
- package/dist/commands/memory.js +277 -25
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/people.d.ts +3 -0
- package/dist/commands/people.d.ts.map +1 -0
- package/dist/commands/people.js +166 -0
- package/dist/commands/people.js.map +1 -0
- package/dist/commands/proposals.d.ts.map +1 -1
- package/dist/commands/proposals.js +390 -69
- package/dist/commands/proposals.js.map +1 -1
- package/dist/commands/provenance.d.ts.map +1 -1
- package/dist/commands/provenance.js +6 -7
- package/dist/commands/provenance.js.map +1 -1
- package/dist/commands/providers.d.ts.map +1 -1
- package/dist/commands/providers.js +46 -7
- package/dist/commands/providers.js.map +1 -1
- package/dist/commands/reviewers.d.ts.map +1 -1
- package/dist/commands/reviewers.js +3 -2
- package/dist/commands/reviewers.js.map +1 -1
- package/dist/commands/runs.d.ts.map +1 -1
- package/dist/commands/runs.js +24 -14
- package/dist/commands/runs.js.map +1 -1
- package/dist/commands/schedules.d.ts +3 -0
- package/dist/commands/schedules.d.ts.map +1 -0
- package/dist/commands/schedules.js +275 -0
- package/dist/commands/schedules.js.map +1 -0
- package/dist/commands/secrets.d.ts.map +1 -1
- package/dist/commands/secrets.js +10 -11
- package/dist/commands/secrets.js.map +1 -1
- package/dist/commands/service-accounts.d.ts +3 -0
- package/dist/commands/service-accounts.d.ts.map +1 -0
- package/dist/commands/service-accounts.js +175 -0
- package/dist/commands/service-accounts.js.map +1 -0
- package/dist/commands/skills.d.ts +1 -1
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +11 -4
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/sso.d.ts +6 -0
- package/dist/commands/sso.d.ts.map +1 -0
- package/dist/commands/sso.js +318 -0
- package/dist/commands/sso.js.map +1 -0
- package/dist/commands/tokens.d.ts +11 -0
- package/dist/commands/tokens.d.ts.map +1 -1
- package/dist/commands/tokens.js +144 -11
- package/dist/commands/tokens.js.map +1 -1
- package/dist/commands/tools.d.ts.map +1 -1
- package/dist/commands/tools.js +4 -3
- package/dist/commands/tools.js.map +1 -1
- package/dist/commands/unwired.d.ts.map +1 -1
- package/dist/commands/unwired.js +1 -30
- package/dist/commands/unwired.js.map +1 -1
- package/dist/commands/upgrade.d.ts +11 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +160 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/context.d.ts +7 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +1 -0
- package/dist/context.js.map +1 -1
- package/dist/dev/defaults.d.ts +28 -11
- package/dist/dev/defaults.d.ts.map +1 -1
- package/dist/dev/defaults.js +114 -35
- package/dist/dev/defaults.js.map +1 -1
- package/dist/dev/google-credentials.d.ts +50 -0
- package/dist/dev/google-credentials.d.ts.map +1 -0
- package/dist/dev/google-credentials.js +157 -0
- package/dist/dev/google-credentials.js.map +1 -0
- package/dist/dev/java-builder.d.ts +56 -0
- package/dist/dev/java-builder.d.ts.map +1 -0
- package/dist/dev/java-builder.js +204 -0
- package/dist/dev/java-builder.js.map +1 -0
- package/dist/dev/jvm-run-files.d.ts +29 -0
- package/dist/dev/jvm-run-files.d.ts.map +1 -0
- package/dist/dev/jvm-run-files.js +94 -0
- package/dist/dev/jvm-run-files.js.map +1 -0
- package/dist/dev/lines.d.ts +13 -0
- package/dist/dev/lines.d.ts.map +1 -0
- package/dist/dev/lines.js +24 -0
- package/dist/dev/lines.js.map +1 -0
- package/dist/dev/log-view.d.ts +116 -0
- package/dist/dev/log-view.d.ts.map +1 -0
- package/dist/dev/log-view.js +233 -0
- package/dist/dev/log-view.js.map +1 -0
- package/dist/dev/pack-code.d.ts +79 -4
- package/dist/dev/pack-code.d.ts.map +1 -1
- package/dist/dev/pack-code.js +262 -3
- package/dist/dev/pack-code.js.map +1 -1
- package/dist/dev/pack-env.d.ts.map +1 -1
- package/dist/dev/pack-env.js +4 -0
- package/dist/dev/pack-env.js.map +1 -1
- package/dist/dev/pack-service.d.ts +11 -1
- package/dist/dev/pack-service.d.ts.map +1 -1
- package/dist/dev/pack-service.js +56 -20
- package/dist/dev/pack-service.js.map +1 -1
- package/dist/dev/paths.d.ts +6 -0
- package/dist/dev/paths.d.ts.map +1 -1
- package/dist/dev/paths.js +8 -0
- package/dist/dev/paths.js.map +1 -1
- package/dist/dev/project-database.js +1 -1
- package/dist/dev/project-database.js.map +1 -1
- package/dist/dev/runners.d.ts +35 -9
- package/dist/dev/runners.d.ts.map +1 -1
- package/dist/dev/runtime-container.d.ts +10 -2
- package/dist/dev/runtime-container.d.ts.map +1 -1
- package/dist/dev/runtime-container.js +48 -20
- package/dist/dev/runtime-container.js.map +1 -1
- package/dist/dev/runtime-env.d.ts +10 -0
- package/dist/dev/runtime-env.d.ts.map +1 -1
- package/dist/dev/runtime-env.js +8 -4
- package/dist/dev/runtime-env.js.map +1 -1
- package/dist/dev/runtime-image.d.ts +1 -1
- package/dist/dev/runtime-image.js +1 -1
- package/dist/dev/scala-builder.d.ts +72 -0
- package/dist/dev/scala-builder.d.ts.map +1 -0
- package/dist/dev/scala-builder.js +347 -0
- package/dist/dev/scala-builder.js.map +1 -0
- package/dist/env/project-env.d.ts.map +1 -1
- package/dist/env/project-env.js +3 -1
- package/dist/env/project-env.js.map +1 -1
- package/dist/errors.d.ts +9 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +15 -0
- package/dist/errors.js.map +1 -1
- package/dist/help.d.ts +14 -0
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +55 -0
- package/dist/help.js.map +1 -1
- package/dist/init/dependency-specs.d.ts +26 -0
- package/dist/init/dependency-specs.d.ts.map +1 -1
- package/dist/init/dependency-specs.js +38 -0
- package/dist/init/dependency-specs.js.map +1 -1
- package/dist/init/java-augment.d.ts +24 -0
- package/dist/init/java-augment.d.ts.map +1 -0
- package/dist/init/java-augment.js +198 -0
- package/dist/init/java-augment.js.map +1 -0
- package/dist/init/mode-detect.d.ts +2 -2
- package/dist/init/mode-detect.d.ts.map +1 -1
- package/dist/init/mode-detect.js +12 -1
- package/dist/init/mode-detect.js.map +1 -1
- package/dist/init/scala-augment.d.ts +23 -0
- package/dist/init/scala-augment.d.ts.map +1 -0
- package/dist/init/scala-augment.js +213 -0
- package/dist/init/scala-augment.js.map +1 -0
- package/dist/init/template-files.d.ts +40 -2
- package/dist/init/template-files.d.ts.map +1 -1
- package/dist/init/template-files.js +56 -5
- package/dist/init/template-files.js.map +1 -1
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +30 -4
- package/dist/main.js.map +1 -1
- package/dist/open-url.d.ts +15 -0
- package/dist/open-url.d.ts.map +1 -0
- package/dist/open-url.js +41 -0
- package/dist/open-url.js.map +1 -0
- package/dist/package-manager.d.ts +10 -3
- package/dist/package-manager.d.ts.map +1 -1
- package/dist/package-manager.js +19 -1
- package/dist/package-manager.js.map +1 -1
- package/dist/providers/preset-loader.d.ts.map +1 -1
- package/dist/providers/preset-loader.js +9 -1
- package/dist/providers/preset-loader.js.map +1 -1
- package/dist/providers/presets/anthropic.json +35 -4
- package/dist/providers/presets/gemini-api.json +3 -0
- package/dist/providers/presets/gemini.json +3 -0
- package/dist/providers/presets/groq.json +1 -0
- package/dist/providers/presets/openai.json +36 -4
- package/dist/providers/presets/openrouter.json +6 -2
- package/dist/sdk-skills/kindgi-authoring-agents/SKILL.md +28 -4
- package/dist/sdk-skills/kindgi-authoring-flows/SKILL.md +1 -1
- package/dist/sdk-skills/kindgi-authoring-guardrails/SKILL.md +47 -3
- package/dist/sdk-skills/kindgi-authoring-mcp-servers/SKILL.md +7 -5
- package/dist/sdk-skills/kindgi-authoring-providers/SKILL.md +81 -52
- package/dist/sdk-skills/kindgi-authoring-tools/SKILL.md +27 -1
- package/dist/sdk-skills/kindgi-framework-feedback/SKILL.md +5 -4
- package/dist/sdk-skills/kindgi-getting-started/SKILL.md +1 -1
- package/dist/sdk-skills/kindgi-java-authoring-agents/SKILL.md +220 -0
- package/dist/sdk-skills/kindgi-java-authoring-flows/SKILL.md +390 -0
- package/dist/sdk-skills/kindgi-java-authoring-guardrails/SKILL.md +209 -0
- package/dist/sdk-skills/kindgi-java-authoring-tools/SKILL.md +334 -0
- package/dist/sdk-skills/kindgi-java-getting-started/SKILL.md +270 -0
- package/dist/sdk-skills/kindgi-python-authoring-agents/SKILL.md +23 -5
- package/dist/sdk-skills/kindgi-python-authoring-flows/SKILL.md +1 -1
- package/dist/sdk-skills/kindgi-python-authoring-guardrails/SKILL.md +10 -3
- package/dist/sdk-skills/kindgi-python-authoring-tools/SKILL.md +36 -5
- package/dist/sdk-skills/kindgi-python-getting-started/SKILL.md +2 -2
- package/dist/sdk-skills/kindgi-scala-authoring-agents/SKILL.md +217 -0
- package/dist/sdk-skills/kindgi-scala-authoring-flows/SKILL.md +357 -0
- package/dist/sdk-skills/kindgi-scala-authoring-guardrails/SKILL.md +199 -0
- package/dist/sdk-skills/kindgi-scala-authoring-tools/SKILL.md +302 -0
- package/dist/sdk-skills/kindgi-scala-getting-started/SKILL.md +302 -0
- package/dist/templates/java/.mvn/wrapper/maven-wrapper.properties +3 -0
- package/dist/templates/java/AGENTS.md +31 -0
- package/dist/templates/java/README.md.tmpl +69 -0
- package/dist/templates/java/gitignore +9 -0
- package/dist/templates/java/kindgi.config.json.tmpl +8 -0
- package/dist/templates/java/kindgiw +33 -0
- package/dist/templates/java/kindgiw.cmd +28 -0
- package/dist/templates/java/mvnw +295 -0
- package/dist/templates/java/pom.xml.tmpl +65 -0
- package/dist/templates/java/src/main/java/__PACKAGE__/agents/EchoAgent.java.tmpl +27 -0
- package/dist/templates/java/src/main/java/__PACKAGE__/flows/EchoFlow.java.tmpl +18 -0
- package/dist/templates/java/src/main/java/__PACKAGE__/guardrails/ResponseNotEmpty.java.tmpl +28 -0
- package/dist/templates/java/src/main/java/__PACKAGE__/tools/Echo.java.tmpl +22 -0
- package/dist/templates/java/src/main/java/__PACKAGE__/tools/Greet.java.tmpl +23 -0
- package/dist/templates/java/src/test/java/__PACKAGE__/ToolsTest.java.tmpl +30 -0
- package/dist/templates/minimal/README.md.tmpl +8 -14
- package/dist/templates/python/README.md.tmpl +6 -6
- package/dist/templates/sample/README.md.tmpl +7 -13
- package/dist/templates/scala/AGENTS.md +32 -0
- package/dist/templates/scala/README.md.tmpl +75 -0
- package/dist/templates/scala/build.sbt.tmpl +16 -0
- package/dist/templates/scala/gitignore +13 -0
- package/dist/templates/scala/kindgi.config.json.tmpl +8 -0
- package/dist/templates/scala/project/build.properties +1 -0
- package/dist/templates/scala/src/main/scala/__PACKAGE__/agents/EchoAgent.scala.tmpl +23 -0
- package/dist/templates/scala/src/main/scala/__PACKAGE__/flows/EchoFlow.scala.tmpl +16 -0
- package/dist/templates/scala/src/main/scala/__PACKAGE__/guardrails/ResponseNotEmpty.scala.tmpl +21 -0
- package/dist/templates/scala/src/main/scala/__PACKAGE__/tools/Echo.scala.tmpl +16 -0
- package/dist/templates/scala/src/main/scala/__PACKAGE__/tools/Greet.scala.tmpl +17 -0
- package/dist/templates/scala/src/test/scala/__PACKAGE__/ToolsSuite.scala.tmpl +23 -0
- package/package.json +13 -12
package/dist/commands/doctor.js
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
401
|
-
|
|
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' });
|