@kortyx/cli 0.14.0 → 0.15.1

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,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.15.1](https://github.com/kortyx-io/kortyx/compare/cli-v0.15.0...cli-v0.15.1) (2026-10-08)
4
+
5
+
6
+ ### Dependencies
7
+
8
+ * The following workspace dependencies were updated
9
+ * dependencies
10
+ * @kortyx/agent bumped to 0.32.0
11
+
12
+ ## [0.15.0](https://github.com/kortyx-io/kortyx/compare/cli-v0.14.0...cli-v0.15.0) (2026-10-08)
13
+
14
+
15
+ ### Features
16
+
17
+ * **telemetry:** preserve complete private error diagnostics ([#290](https://github.com/kortyx-io/kortyx/issues/290)) ([e1c5a88](https://github.com/kortyx-io/kortyx/commit/e1c5a88c494016fd84a1af40579412fcfde31271))
18
+
19
+
20
+ ### Dependencies
21
+
22
+ * The following workspace dependencies were updated
23
+ * dependencies
24
+ * @kortyx/agent bumped to 0.31.1
25
+ * @kortyx/core bumped to 0.12.0
26
+ * @kortyx/telemetry-contracts bumped to 0.15.0
27
+
3
28
  ## [0.14.0](https://github.com/kortyx-io/kortyx/compare/cli-v0.13.0...cli-v0.14.0) (2026-10-07)
4
29
 
5
30
 
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
 
25
25
  // src/index.ts
26
- var import_promises5 = require("fs/promises");
26
+ var import_promises6 = require("fs/promises");
27
27
  var import_node_module = __toESM(require("module"));
28
28
  var import_node_os3 = require("os");
29
29
  var import_node_path6 = require("path");
@@ -148,7 +148,7 @@ var StudioApiTransport = class {
148
148
  }
149
149
  if (!response.ok) {
150
150
  await response.body?.cancel();
151
- const message = response.status === 401 ? "Invalid, expired, or revoked Studio read key." : response.status === 403 ? "The API key lacks studio:read or the operation's required permission (eval:run for execution)." : response.status === 404 ? "Entity not found in this connection's project. Check the URL and selected connection." : `Studio API returned HTTP ${response.status}.`;
151
+ const message = response.status === 401 ? "Invalid, expired, or revoked Studio read key." : response.status === 403 ? "The API key lacks studio:read or the operation's required permission (diagnostics:read for diagnostics; eval:run for execution)." : response.status === 404 ? "Entity not found in this connection's project. Check the URL and selected connection." : `Studio API returned HTTP ${response.status}.`;
152
152
  throw new StudioReadError(
153
153
  "api_error",
154
154
  message,
@@ -208,7 +208,7 @@ var StudioApiTransport = class {
208
208
  };
209
209
  var StudioReadClient = class extends StudioApiTransport {
210
210
  async get(path, schema, query = {}) {
211
- if (!/^\/v1\/studio\/(?:context|catalogs|workflows|(?:runs|sessions|interrupts)(?:\/[^/?#]+)?)$/.test(
211
+ if (!/^\/v1\/studio\/(?:context|catalogs|workflows|(?:runs|sessions|interrupts)(?:\/[^/?#]+)?|diagnostics\/[a-f0-9-]{36})$/.test(
212
212
  path
213
213
  ) || /(?:^|\/)(?:\.|\.\.|%2e(?:%2e)?)(?:\/|$)/i.test(path)) {
214
214
  throw new StudioReadError(
@@ -236,6 +236,7 @@ x-api-env: &api-env
236
236
  x-bootstrap-keys: &bootstrap-keys
237
237
  KORTYX_TELEMETRY_API_KEY: \${KORTYX_TELEMETRY_API_KEY}
238
238
  KORTYX_STUDIO_API_KEY: \${KORTYX_STUDIO_API_KEY}
239
+ KORTYX_STUDIO_ENABLE_DIAGNOSTICS: \${KORTYX_STUDIO_ENABLE_DIAGNOSTICS:-0}
239
240
  KORTYX_STUDIO_ENABLE_EVALS: \${KORTYX_STUDIO_ENABLE_EVALS:-0}
240
241
  KORTYX_STUDIO_ENABLE_REVIEWS: \${KORTYX_STUDIO_ENABLE_REVIEWS:-0}
241
242
 
@@ -2572,6 +2573,8 @@ var resetStudio = async (home, confirm, runtime) => {
2572
2573
  };
2573
2574
 
2574
2575
  // src/studio/read-command.ts
2576
+ var import_node_crypto2 = require("crypto");
2577
+ var import_promises5 = require("fs/promises");
2575
2578
  var import_telemetry_contracts5 = require("@kortyx/telemetry-contracts");
2576
2579
  var import_commander4 = require("commander");
2577
2580
 
@@ -2935,6 +2938,80 @@ var registerStudioReadCommands = (studio, log, request = fetch) => {
2935
2938
  )
2936
2939
  };
2937
2940
  };
2941
+ const diagnostics = studio.command("diagnostics").description("Inspect or download a retained private error diagnostic.");
2942
+ for (const action of ["get", "download"]) {
2943
+ common(
2944
+ diagnostics.command(action).argument("<diagnostic-id>").option(
2945
+ "--environment <name>",
2946
+ "Diagnostic environment (or connection default)."
2947
+ ).option(
2948
+ "--output <path>",
2949
+ "Write a diagnostic JSON file without overwriting an existing file."
2950
+ )
2951
+ ).action(async (id, options) => {
2952
+ if (!/^[a-f0-9-]{36}$/i.test(id))
2953
+ throw new StudioReadError(
2954
+ "invalid_diagnostic_id",
2955
+ "Expected a diagnostic UUID."
2956
+ );
2957
+ const { connection, client } = await clientFor(options);
2958
+ const environment = options.environment ?? connection.environment;
2959
+ if (!environment)
2960
+ throw new StudioReadError(
2961
+ "environment_required",
2962
+ "Provide --environment for diagnostic retrieval."
2963
+ );
2964
+ const value = await client.get(
2965
+ `/v1/studio/diagnostics/${id}`,
2966
+ import_telemetry_contracts5.DiagnosticResponseSchema,
2967
+ {
2968
+ env: environment,
2969
+ ...action === "download" ? { download: "1" } : {}
2970
+ }
2971
+ );
2972
+ if (action === "get") {
2973
+ const { content, ...summary2 } = value;
2974
+ print(
2975
+ {
2976
+ ...summary2,
2977
+ capture: {
2978
+ status: content?.capture.status ?? value.manifest.captureStatus,
2979
+ omissions: content?.capture.omissions.length ?? 0,
2980
+ redactions: content?.capture.redactions.length ?? 0
2981
+ },
2982
+ ...options.includeContent ? { content } : {},
2983
+ note: "Private diagnostic access requires diagnostics:read. Use download for the complete redacted artifact."
2984
+ },
2985
+ options
2986
+ );
2987
+ return;
2988
+ }
2989
+ if (value.state !== "available" || !value.content)
2990
+ throw new StudioReadError(
2991
+ "diagnostic_incomplete",
2992
+ `Diagnostic is ${value.state}; received ${value.receivedParts} of ${value.manifest.partCount} parts.`
2993
+ );
2994
+ const bytes = JSON.stringify(value.content);
2995
+ if ((0, import_node_crypto2.createHash)("sha256").update(bytes).digest("hex") !== value.contentChecksum || Buffer.byteLength(bytes) !== value.contentByteLength)
2996
+ throw new StudioReadError(
2997
+ "diagnostic_integrity_failed",
2998
+ "Diagnostic download failed integrity verification."
2999
+ );
3000
+ if (options.output) {
3001
+ await (0, import_promises5.writeFile)(options.output, bytes, { flag: "wx", mode: 384 });
3002
+ print(
3003
+ {
3004
+ diagnosticId: id,
3005
+ state: value.state,
3006
+ file: options.output,
3007
+ bytes: value.contentByteLength,
3008
+ checksum: value.contentChecksum
3009
+ },
3010
+ options
3011
+ );
3012
+ } else log(bytes);
3013
+ });
3014
+ }
2938
3015
  const inspect = async (input, options, entity) => {
2939
3016
  const target = parseStudioTarget(input, entity);
2940
3017
  const { connection, client } = await clientFor(options, target.url);
@@ -3323,7 +3400,7 @@ var registerStudioReadCommands = (studio, log, request = fetch) => {
3323
3400
  };
3324
3401
 
3325
3402
  // src/studio/runtime.ts
3326
- var import_node_crypto2 = require("crypto");
3403
+ var import_node_crypto3 = require("crypto");
3327
3404
  var import_node_net = require("net");
3328
3405
  var stringOutput = (value) => typeof value === "string" ? value : "";
3329
3406
  var isCommandNotFound = (error) => error instanceof Error && ("code" in error && error.code === "ENOENT" || error.message.includes("ENOENT"));
@@ -3362,7 +3439,7 @@ var defaultStudioRuntime = {
3362
3439
  run,
3363
3440
  portAvailable,
3364
3441
  now: () => (/* @__PURE__ */ new Date()).toISOString(),
3365
- random: (bytes) => (0, import_node_crypto2.randomBytes)(bytes).toString("base64url"),
3442
+ random: (bytes) => (0, import_node_crypto3.randomBytes)(bytes).toString("base64url"),
3366
3443
  request: fetch,
3367
3444
  log: (message = "") => console.log(message),
3368
3445
  error: (message = "") => console.error(message)
@@ -3898,7 +3975,7 @@ var readPackageName = async (cwd) => {
3898
3975
  let current = cwd;
3899
3976
  for (; ; ) {
3900
3977
  try {
3901
- const raw = await (0, import_promises5.readFile)((0, import_node_path6.join)(current, "package.json"), "utf8");
3978
+ const raw = await (0, import_promises6.readFile)((0, import_node_path6.join)(current, "package.json"), "utf8");
3902
3979
  const parsed = JSON.parse(raw);
3903
3980
  if (isRecord(parsed) && typeof parsed.name === "string") {
3904
3981
  return parsed.name;
@@ -3915,7 +3992,7 @@ var loadEnvFiles = async (cwd) => {
3915
3992
  const path = (0, import_node_path6.join)(cwd, filename);
3916
3993
  let raw;
3917
3994
  try {
3918
- raw = await (0, import_promises5.readFile)(path, "utf8");
3995
+ raw = await (0, import_promises6.readFile)(path, "utf8");
3919
3996
  } catch {
3920
3997
  continue;
3921
3998
  }
@@ -3955,12 +4032,12 @@ var resolveOptions = async (options) => {
3955
4032
  };
3956
4033
  };
3957
4034
  var createFrameworkMarkerStubs = async () => {
3958
- const root = await (0, import_promises5.mkdtemp)((0, import_node_path6.join)((0, import_node_os3.tmpdir)(), "kortyx-cli-stubs-"));
4035
+ const root = await (0, import_promises6.mkdtemp)((0, import_node_path6.join)((0, import_node_os3.tmpdir)(), "kortyx-cli-stubs-"));
3959
4036
  for (const moduleName of ["server-only", "client-only"]) {
3960
4037
  const directory = (0, import_node_path6.join)(root, moduleName);
3961
- await (0, import_promises5.mkdir)(directory, { recursive: true });
3962
- await (0, import_promises5.writeFile)((0, import_node_path6.join)(directory, "index.js"), "", "utf8");
3963
- await (0, import_promises5.writeFile)(
4038
+ await (0, import_promises6.mkdir)(directory, { recursive: true });
4039
+ await (0, import_promises6.writeFile)((0, import_node_path6.join)(directory, "index.js"), "", "utf8");
4040
+ await (0, import_promises6.writeFile)(
3964
4041
  (0, import_node_path6.join)(directory, "package.json"),
3965
4042
  JSON.stringify({ name: moduleName, version: "0.0.0", main: "index.js" }),
3966
4043
  "utf8"
@@ -3975,7 +4052,7 @@ var createFrameworkMarkerStubs = async () => {
3975
4052
  if (previousNodePath === void 0) delete process.env.NODE_PATH;
3976
4053
  else process.env.NODE_PATH = previousNodePath;
3977
4054
  moduleWithPrivateInit._initPaths?.();
3978
- await (0, import_promises5.rm)(root, { recursive: true, force: true });
4055
+ await (0, import_promises6.rm)(root, { recursive: true, force: true });
3979
4056
  }
3980
4057
  };
3981
4058
  };
@@ -4017,7 +4094,7 @@ var findNearestFile = async (startDirectory, filename) => {
4017
4094
  for (; ; ) {
4018
4095
  const candidate = (0, import_node_path6.join)(current, filename);
4019
4096
  try {
4020
- await (0, import_promises5.access)(candidate);
4097
+ await (0, import_promises6.access)(candidate);
4021
4098
  return candidate;
4022
4099
  } catch {
4023
4100
  const parent = (0, import_node_path6.dirname)(current);
@@ -4029,7 +4106,7 @@ var findNearestFile = async (startDirectory, filename) => {
4029
4106
  var registerTsconfigPaths = async (tsconfigPath) => {
4030
4107
  const parsed = import_typescript2.default.parseConfigFileTextToJson(
4031
4108
  tsconfigPath,
4032
- await (0, import_promises5.readFile)(tsconfigPath, "utf8")
4109
+ await (0, import_promises6.readFile)(tsconfigPath, "utf8")
4033
4110
  );
4034
4111
  if (parsed.error)
4035
4112
  throw new Error(`Invalid TypeScript configuration: ${tsconfigPath}`);