@kortyx/cli 0.12.0 → 0.13.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/CHANGELOG.md +23 -0
- package/README.md +14 -0
- package/dist/index.js +192 -1
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.13.0](https://github.com/kortyx-io/kortyx/compare/cli-v0.12.0...cli-v0.13.0) (2026-10-07)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **api:** add typed security and tenant database extensions ([#260](https://github.com/kortyx-io/kortyx/issues/260)) ([47e4928](https://github.com/kortyx-io/kortyx/commit/47e49286f12e4470b3a327e609a65c99678b359f))
|
|
9
|
+
* **evals:** diagnose setup and guide first workflow runs ([#275](https://github.com/kortyx-io/kortyx/issues/275)) ([01dbf80](https://github.com/kortyx-io/kortyx/commit/01dbf803e65eec03187de9eaa316e4294f4f8dc5))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* **security:** remediate production dependency risks ([#267](https://github.com/kortyx-io/kortyx/issues/267)) ([319daa1](https://github.com/kortyx-io/kortyx/commit/319daa1716a8684c332842bc6b6e9bb9474219e7))
|
|
15
|
+
* **studio:** adopt native Drizzle migrations safely ([98238a7](https://github.com/kortyx-io/kortyx/commit/98238a770a900017dd4956f1afc56fddba21d371))
|
|
16
|
+
* **studio:** preserve legacy updater access ([#283](https://github.com/kortyx-io/kortyx/issues/283)) ([fb733d8](https://github.com/kortyx-io/kortyx/commit/fb733d848ac7b6e42851de810aa2f039abb535b2))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Dependencies
|
|
20
|
+
|
|
21
|
+
* The following workspace dependencies were updated
|
|
22
|
+
* dependencies
|
|
23
|
+
* @kortyx/agent bumped to 0.30.0
|
|
24
|
+
* @kortyx/telemetry-contracts bumped to 0.14.0
|
|
25
|
+
|
|
3
26
|
## [0.12.0](https://github.com/kortyx-io/kortyx/compare/cli-v0.11.4...cli-v0.12.0) (2026-10-03)
|
|
4
27
|
|
|
5
28
|
|
package/README.md
CHANGED
|
@@ -447,3 +447,17 @@ Studio draws discovered call/return links before traffic exists. The **Observed
|
|
|
447
447
|
`kortyx topology push` discovers shared tool definitions attached via `useTool({tool, input})` and `useReason({tools})` through local imports, custom hooks, and statically bound factories. Discovery does not execute nodes, tool factories or MCP discovery. The configured entry is still imported to obtain the workflow registry.
|
|
448
448
|
|
|
449
449
|
Published node capabilities contain names, descriptions, calling mode, safe input-field summaries and discovery freshness. Dynamic attachments produce an unresolved warning rather than an empty-tools claim. Studio merges real observed tools and shows execution outcomes/durations separately from cached reuse. `--dry-run --json` exposes the discovered attachments and status without publishing.
|
|
450
|
+
|
|
451
|
+
## Diagnose eval setup
|
|
452
|
+
|
|
453
|
+
```sh
|
|
454
|
+
kortyx studio evals doctor --connection staging --target catalog --suite catalog-smoke
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Checks Studio access, execution permission, target/environment, authenticated
|
|
458
|
+
consumer manifest/suites and advertised judge compatibility. Discovery GET only;
|
|
459
|
+
no workflows, model calls or saved runs are started. Consumer GET wrappers may
|
|
460
|
+
authenticate a test actor. Failures include actionable remedies. Use `--judge app`
|
|
461
|
+
for a code judge or `--json` for a versioned report; failures exit 1. A successful
|
|
462
|
+
check still needs a representative run to verify tool/provider access and saved
|
|
463
|
+
results. See the [first eval guide](https://kortyx.io/docs/studio/first-eval).
|
package/dist/index.js
CHANGED
|
@@ -257,6 +257,7 @@ services:
|
|
|
257
257
|
db-init:
|
|
258
258
|
image: \${KORTYX_API_IMAGE_REF:-\${KORTYX_API_IMAGE:-ghcr.io/kortyx-io/kortyx-api}:\${KORTYX_STUDIO_IMAGE_TAG:-latest}}
|
|
259
259
|
pull_policy: \${KORTYX_STUDIO_PULL_POLICY:-always}
|
|
260
|
+
user: "1000:1000"
|
|
260
261
|
environment:
|
|
261
262
|
<<: [*api-env, *bootstrap-keys]
|
|
262
263
|
command: >
|
|
@@ -270,6 +271,7 @@ services:
|
|
|
270
271
|
api:
|
|
271
272
|
image: \${KORTYX_API_IMAGE_REF:-\${KORTYX_API_IMAGE:-ghcr.io/kortyx-io/kortyx-api}:\${KORTYX_STUDIO_IMAGE_TAG:-latest}}
|
|
272
273
|
pull_policy: \${KORTYX_STUDIO_PULL_POLICY:-always}
|
|
274
|
+
user: "1000:1000"
|
|
273
275
|
environment:
|
|
274
276
|
<<: *api-env
|
|
275
277
|
NODE_ENV: production
|
|
@@ -329,6 +331,9 @@ services:
|
|
|
329
331
|
image: \${KORTYX_API_IMAGE_REF:-\${KORTYX_API_IMAGE:-ghcr.io/kortyx-io/kortyx-api}:\${KORTYX_STUDIO_IMAGE_TAG:-latest}}
|
|
330
332
|
pull_policy: \${KORTYX_STUDIO_PULL_POLICY:-always}
|
|
331
333
|
restart: unless-stopped
|
|
334
|
+
# The updater alone needs root for the host Docker socket and ownership-safe
|
|
335
|
+
# writes to the bind-mounted Studio state directory.
|
|
336
|
+
user: "0:0"
|
|
332
337
|
command: ["node", "apps/api/dist/updater.js", "serve", "\${KORTYX_STUDIO_STATE_DIR}"]
|
|
333
338
|
volumes:
|
|
334
339
|
- type: bind
|
|
@@ -832,7 +837,7 @@ var createConnectionsCommand = (log = console.log, request = fetch) => {
|
|
|
832
837
|
"/v1/studio/context",
|
|
833
838
|
import_telemetry_contracts2.StudioContextResponseSchema
|
|
834
839
|
);
|
|
835
|
-
if (!context.apiKey
|
|
840
|
+
if (!context.apiKey?.scopes.includes("studio:read"))
|
|
836
841
|
throw new StudioReadError(
|
|
837
842
|
"missing_scope",
|
|
838
843
|
"The API key lacks studio:read permission."
|
|
@@ -1490,6 +1495,162 @@ var StudioEvalClient = class extends StudioApiTransport {
|
|
|
1490
1495
|
}
|
|
1491
1496
|
};
|
|
1492
1497
|
|
|
1498
|
+
// src/studio/eval-doctor.ts
|
|
1499
|
+
var advice = {
|
|
1500
|
+
environment_forbidden: {
|
|
1501
|
+
message: "Target environment is not allowed in this Studio project.",
|
|
1502
|
+
remedy: "Allow the target environment in the project, or correct the target's environment label."
|
|
1503
|
+
},
|
|
1504
|
+
environment_unavailable: {
|
|
1505
|
+
message: "Studio could not check the target environment.",
|
|
1506
|
+
remedy: "Inspect Studio API database connectivity and private server logs."
|
|
1507
|
+
},
|
|
1508
|
+
endpoint_not_found: {
|
|
1509
|
+
message: "Application endpoint returned HTTP 404.",
|
|
1510
|
+
remedy: "Ensure the eval route is mounted and enabled in the deployed application; check the target URL and reverse proxy route."
|
|
1511
|
+
},
|
|
1512
|
+
endpoint_unauthorized: {
|
|
1513
|
+
message: "Application endpoint rejected discovery credentials.",
|
|
1514
|
+
remedy: "Match the consumer handler service key to the Studio target. If the app authenticates a test actor during discovery, also check that actor's credentials and permissions."
|
|
1515
|
+
},
|
|
1516
|
+
endpoint_http_error: {
|
|
1517
|
+
message: "Application endpoint returned an unsuccessful HTTP status.",
|
|
1518
|
+
remedy: "Inspect the consumer and proxy logs. Check deployment configuration and any app-owned test-identity initialization."
|
|
1519
|
+
},
|
|
1520
|
+
endpoint_unreachable: {
|
|
1521
|
+
message: "Studio API could not fetch the application manifest.",
|
|
1522
|
+
remedy: "Check API-to-consumer networking, DNS, TLS and timeouts. From Docker Desktop use host.docker.internal for a host app; redirects are not followed."
|
|
1523
|
+
},
|
|
1524
|
+
manifest_invalid: {
|
|
1525
|
+
message: "Application returned an empty, oversized or incompatible manifest.",
|
|
1526
|
+
remedy: "Mount createEvalRouteHandler on the exact target URL and use compatible SDK/Studio releases. Check whether a proxy returned HTML instead of JSON."
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
function buildEvalDoctorReport(data, options) {
|
|
1530
|
+
const checks = [
|
|
1531
|
+
{
|
|
1532
|
+
id: "studio_access",
|
|
1533
|
+
status: "passed",
|
|
1534
|
+
message: "Authenticated Studio discovery (studio:read)."
|
|
1535
|
+
},
|
|
1536
|
+
{
|
|
1537
|
+
id: "execution_permission",
|
|
1538
|
+
status: data.canRun ? "passed" : "failed",
|
|
1539
|
+
message: data.canRun ? "Studio key has eval:run." : "Studio key lacks eval:run.",
|
|
1540
|
+
...!data.canRun ? {
|
|
1541
|
+
remedy: "Grant eval:run to this project key. For local bootstrap rerun with KORTYX_STUDIO_ENABLE_EVALS=1 and the existing stored key."
|
|
1542
|
+
} : {}
|
|
1543
|
+
}
|
|
1544
|
+
];
|
|
1545
|
+
const targets = data.targets.filter(
|
|
1546
|
+
(target) => (!options.target || target.id === options.target) && (!options.environment || target.environment === options.environment)
|
|
1547
|
+
);
|
|
1548
|
+
checks.push({
|
|
1549
|
+
id: "target_selection",
|
|
1550
|
+
status: targets.length ? "passed" : "failed",
|
|
1551
|
+
message: targets.length ? `${targets.length} matching application target(s).` : "No application target matches this connection and selection.",
|
|
1552
|
+
...!targets.length ? {
|
|
1553
|
+
remedy: "Register the target with this key's organization/project and environment. Mount KORTYX_EVAL_TARGETS_FILE on the Studio API, restart it, and check --target/--environment and the connection's default environment."
|
|
1554
|
+
} : {}
|
|
1555
|
+
});
|
|
1556
|
+
for (const target of targets) {
|
|
1557
|
+
const prefix = `${target.id} (${target.environment})`;
|
|
1558
|
+
const diagnostic = target.diagnostic;
|
|
1559
|
+
const environmentFailed = diagnostic?.code === "environment_forbidden" || diagnostic?.code === "environment_unavailable";
|
|
1560
|
+
checks.push({
|
|
1561
|
+
id: `${target.id}:environment`,
|
|
1562
|
+
status: environmentFailed ? "failed" : target.manifest || diagnostic ? "passed" : "skipped",
|
|
1563
|
+
message: `${prefix}: ${environmentFailed ? advice[diagnostic.code].message : target.manifest || diagnostic ? "target environment allowed." : "environment check unavailable on this API."}`,
|
|
1564
|
+
...environmentFailed ? { remedy: advice[diagnostic.code].remedy } : {}
|
|
1565
|
+
});
|
|
1566
|
+
if (!target.manifest) {
|
|
1567
|
+
const guidance = diagnostic && !environmentFailed ? advice[diagnostic.code] : void 0;
|
|
1568
|
+
checks.push({
|
|
1569
|
+
id: `${target.id}:manifest`,
|
|
1570
|
+
status: environmentFailed ? "skipped" : "failed",
|
|
1571
|
+
...diagnostic ? { code: diagnostic.code } : {},
|
|
1572
|
+
message: `${prefix}: ${environmentFailed ? "discovery skipped because the environment check failed." : guidance?.message ?? "consumer unavailable; this API did not supply a failure category."}${diagnostic?.httpStatus ? ` (HTTP ${diagnostic.httpStatus})` : ""}`,
|
|
1573
|
+
...!environmentFailed ? {
|
|
1574
|
+
remedy: guidance?.remedy ?? "Check the deployed consumer route, matching service key and API-to-consumer reachability. Upgrade the Studio API for detailed discovery diagnostics."
|
|
1575
|
+
} : {}
|
|
1576
|
+
});
|
|
1577
|
+
checks.push({
|
|
1578
|
+
id: `${target.id}:judge`,
|
|
1579
|
+
status: "skipped",
|
|
1580
|
+
message: `${prefix}: judge compatibility cannot be checked without a manifest.`
|
|
1581
|
+
});
|
|
1582
|
+
continue;
|
|
1583
|
+
}
|
|
1584
|
+
const suites = target.manifest.suites;
|
|
1585
|
+
const selectedSuite = options.suite ? suites.find((suite) => suite.id === options.suite) : void 0;
|
|
1586
|
+
const suitesReady = options.suite ? Boolean(selectedSuite) : suites.length > 0;
|
|
1587
|
+
checks.push({
|
|
1588
|
+
id: `${target.id}:manifest`,
|
|
1589
|
+
status: suitesReady ? "passed" : "failed",
|
|
1590
|
+
message: `${prefix}: authenticated manifest valid; ${suites.length} registered suite(s).${options.suite ? ` Requested suite ${options.suite} ${selectedSuite ? "found" : "missing"}.` : ""}`,
|
|
1591
|
+
...!suitesReady ? {
|
|
1592
|
+
remedy: "Pass the intended suite to createEvals({ suites: [...] }), deploy the application and refresh discovery. Suites are fetched from the consumer; topology publication does not register them."
|
|
1593
|
+
} : {}
|
|
1594
|
+
});
|
|
1595
|
+
const judgeReady = options.judge === "studio" ? Boolean(data.studioJudge && target.manifest.studioJudging) : Boolean(target.manifest.judge);
|
|
1596
|
+
checks.push({
|
|
1597
|
+
id: `${target.id}:judge`,
|
|
1598
|
+
status: judgeReady ? "passed" : "failed",
|
|
1599
|
+
message: `${prefix}: ${options.judge} judge ${judgeReady ? "configured and advertised" : "unavailable"}.`,
|
|
1600
|
+
...!judgeReady ? {
|
|
1601
|
+
remedy: options.judge === "app" ? "Provide a code judge to createEvals in the consumer, or explicitly select --judge studio." : !data.studioJudge ? "Configure KORTYX_EVAL_JUDGE_MODEL and KORTYX_EVAL_JUDGE_API_KEY on the Studio API and restart it, or explicitly select an available --judge app." : "Update the consumer SDK to advertise Studio judging support, or select an available --judge app."
|
|
1602
|
+
} : {}
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
return {
|
|
1606
|
+
schemaVersion: 1,
|
|
1607
|
+
status: checks.some((check) => check.status === "failed") ? "failed" : "passed",
|
|
1608
|
+
checks
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
function evalDoctorFailure(error) {
|
|
1612
|
+
const safe = error instanceof StudioReadError ? error : null;
|
|
1613
|
+
const connectionError = safe && [
|
|
1614
|
+
"invalid_config",
|
|
1615
|
+
"invalid_connection",
|
|
1616
|
+
"not_configured",
|
|
1617
|
+
"unknown_connection",
|
|
1618
|
+
"invalid_key_env",
|
|
1619
|
+
"missing_key",
|
|
1620
|
+
"invalid_key",
|
|
1621
|
+
"invalid_url",
|
|
1622
|
+
"incompatible_studio_api",
|
|
1623
|
+
"schema_mismatch",
|
|
1624
|
+
"connection_failed"
|
|
1625
|
+
].includes(safe.code);
|
|
1626
|
+
return {
|
|
1627
|
+
schemaVersion: 1,
|
|
1628
|
+
status: "failed",
|
|
1629
|
+
checks: [
|
|
1630
|
+
{
|
|
1631
|
+
id: "studio_access",
|
|
1632
|
+
status: "failed",
|
|
1633
|
+
...safe ? { code: safe.code } : {},
|
|
1634
|
+
message: connectionError ? safe.message : safe?.status === 401 ? "Studio rejected the project key (HTTP 401)." : safe?.status === 403 ? "Studio discovery requires studio:read (HTTP 403)." : safe?.status === 404 ? "Studio eval discovery endpoint was not found (HTTP 404)." : "Could not resolve the Studio connection or read a compatible discovery response.",
|
|
1635
|
+
remedy: "Check the selected connection, its API URL and project key, network access, and compatible CLI/Studio releases. No consumer or provider credentials belong in this CLI connection."
|
|
1636
|
+
}
|
|
1637
|
+
]
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
function formatEvalDoctorReport(report) {
|
|
1641
|
+
const symbols = { passed: "\u2713", failed: "\u2717", skipped: "\u2013" };
|
|
1642
|
+
return [
|
|
1643
|
+
"Kortyx Evals \xB7 Setup check",
|
|
1644
|
+
...report.checks.flatMap((check) => [
|
|
1645
|
+
` ${symbols[check.status]} ${check.message}`,
|
|
1646
|
+
...check.remedy ? [` \u2192 ${check.remedy}`] : []
|
|
1647
|
+
]),
|
|
1648
|
+
"",
|
|
1649
|
+
report.status === "passed" ? "Configuration checks passed. Run one representative suite to verify the test identity, tools, model access and saved results." : "Setup checks failed. Resolve the failures and run doctor again.",
|
|
1650
|
+
"No workflow or judge calls were started. Consumer GET discovery may run app-owned authentication logic."
|
|
1651
|
+
].join("\n").replace(/\p{Cc}/gu, (character) => character === "\n" ? character : "");
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1493
1654
|
// src/studio/read-output.ts
|
|
1494
1655
|
var parseStudioTarget = (input, entity) => {
|
|
1495
1656
|
if (/^[a-z][a-z0-9+.-]*:/i.test(input)) {
|
|
@@ -1709,6 +1870,36 @@ function registerStudioEvalCommands(studio, log, request = fetch) {
|
|
|
1709
1870
|
"--environment <name>",
|
|
1710
1871
|
"Filter targets; defaults to the connection environment."
|
|
1711
1872
|
);
|
|
1873
|
+
selectionOptions(
|
|
1874
|
+
evals.command("doctor").description(
|
|
1875
|
+
"Check deployment wiring without starting workflows or model calls."
|
|
1876
|
+
)
|
|
1877
|
+
).option("--suite <id>", "Check that a specific suite is registered.").option(
|
|
1878
|
+
"--judge <location>",
|
|
1879
|
+
"Judge to check: studio (default) or app.",
|
|
1880
|
+
(value) => {
|
|
1881
|
+
if (value !== "studio" && value !== "app")
|
|
1882
|
+
throw new import_commander3.InvalidArgumentError("Expected studio or app.");
|
|
1883
|
+
return value;
|
|
1884
|
+
},
|
|
1885
|
+
"studio"
|
|
1886
|
+
).action(async (options) => {
|
|
1887
|
+
let report;
|
|
1888
|
+
try {
|
|
1889
|
+
const { connection, client } = await clientFor(options);
|
|
1890
|
+
report = buildEvalDoctorReport(await client.targets(), {
|
|
1891
|
+
...options,
|
|
1892
|
+
environment: options.environment ?? connection.environment,
|
|
1893
|
+
judge: options.judge ?? "studio"
|
|
1894
|
+
});
|
|
1895
|
+
} catch (error) {
|
|
1896
|
+
report = evalDoctorFailure(error);
|
|
1897
|
+
}
|
|
1898
|
+
log(
|
|
1899
|
+
options.json ? JSON.stringify(report) : formatEvalDoctorReport(report)
|
|
1900
|
+
);
|
|
1901
|
+
if (report.status === "failed") process.exitCode = 1;
|
|
1902
|
+
});
|
|
1712
1903
|
selectionOptions(
|
|
1713
1904
|
suites.command("list").description("List targets, suites, revisions and case IDs.")
|
|
1714
1905
|
).action(async (options) => {
|