@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 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.scopes.includes("studio:read"))
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) => {