@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.
- package/README.md +13 -7
- package/dist/build/bundle.d.ts +11 -3
- package/dist/build/bundle.d.ts.map +1 -1
- package/dist/build/bundle.js +17 -3
- package/dist/build/bundle.js.map +1 -1
- 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 +212 -40
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +14 -2
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +356 -20
- 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 +7 -5
- 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 +35 -15
- 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/init/augment-scaffolder.js +1 -1
- package/dist/init/augment-scaffolder.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/python-augment.js +1 -1
- package/dist/init/python-augment.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 +20 -2
- 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/presets/anthropic.json +11 -3
- package/dist/sdk-skills/kindgi-authoring-agents/SKILL.md +30 -2
- 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 +40 -15
- package/dist/sdk-skills/kindgi-authoring-tools/SKILL.md +66 -2
- 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 +25 -3
- 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 +45 -6
- 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 +6 -12
- package/dist/templates/python/README.md.tmpl +4 -4
- package/dist/templates/sample/README.md.tmpl +5 -11
- 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
|
@@ -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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
404
|
-
|
|
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, ...
|
|
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:
|
|
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:
|
|
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
|
|
465
|
-
|
|
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' });
|