@toloka-platform/sdk 0.2.0-preview.3 → 0.2.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.
Files changed (75) hide show
  1. package/README.md +5 -6
  2. package/dist/cli/toloka.js +136 -136
  3. package/dist/cli/toloka.js.jsc +0 -0
  4. package/dist/cli/toloka.js.jsc.sha256 +1 -1
  5. package/dist/cli/toloka.js.sha256 +1 -1
  6. package/docs/cli-v2.md +3 -2
  7. package/docs/cli.md +35 -7
  8. package/docs/getting-started.md +4 -2
  9. package/docs/limitations.md +2 -2
  10. package/openapi/gen/models/baseModel.ts +1 -0
  11. package/openapi/gen/models/currentUser.ts +2 -0
  12. package/openapi/gen/models/currentUserTeamsItem.ts +6 -0
  13. package/openapi/gen/models/gistingTrainingConfiguration.ts +6 -2
  14. package/openapi/gen/models/gistingTrainingConfigurationBatchSize.ts +2 -0
  15. package/openapi/gen/models/gistingTrainingConfigurationGistTokens.ts +2 -0
  16. package/openapi/gen/models/gistingTrainingConfigurationInput.ts +6 -2
  17. package/openapi/gen/models/gistingTrainingConfigurationInputBatchSize.ts +2 -0
  18. package/openapi/gen/models/gistingTrainingConfigurationInputGistTokens.ts +2 -0
  19. package/openapi/gen/models/index.ts +9 -4
  20. package/openapi/gen/models/sftTrainingConfiguration.ts +6 -3
  21. package/openapi/gen/models/sftTrainingConfigurationBatchSize.ts +2 -0
  22. package/openapi/gen/models/sftTrainingConfigurationInput.ts +6 -3
  23. package/openapi/gen/models/sftTrainingConfigurationInputBatchSize.ts +2 -0
  24. package/openapi/gen/models/sftTrainingConfigurationInputLoraRank.ts +2 -0
  25. package/openapi/gen/models/sftTrainingConfigurationLoraRank.ts +2 -0
  26. package/package.json +2 -2
  27. package/skills/catalog.json +11 -11
  28. package/skills/toloka-build/SKILL.md +29 -18
  29. package/skills/toloka-build/references/node-semantics.md +7 -7
  30. package/skills/toloka-build/references/workflow-and-cli.md +15 -12
  31. package/skills/toloka-fine-tuning/SKILL.md +145 -62
  32. package/skills/toloka-get-started/SKILL.md +37 -21
  33. package/skills/toloka-gisting/SKILL.md +156 -66
  34. package/skills/toloka-report-issue/references/report-template.md +1 -1
  35. package/src/cli/analytics.ts +50 -23
  36. package/src/cli/auth.ts +31 -12
  37. package/src/cli/auto-update.ts +12 -6
  38. package/src/cli/check.ts +76 -29
  39. package/src/cli/cli-entry.ts +3 -2
  40. package/src/cli/cli-error.ts +43 -0
  41. package/src/cli/commands/dataset.ts +13 -3
  42. package/src/cli/commands/models.ts +6 -7
  43. package/src/cli/commands/pipeline.ts +6 -7
  44. package/src/cli/commands/project.ts +9 -16
  45. package/src/cli/commands/report.ts +6 -2
  46. package/src/cli/commands/secret.ts +5 -4
  47. package/src/cli/commands/source.ts +4 -4
  48. package/src/cli/commands/status.ts +2 -3
  49. package/src/cli/commands/training.ts +50 -17
  50. package/src/cli/config.ts +2 -2
  51. package/src/cli/context.ts +55 -17
  52. package/src/cli/credentials.ts +12 -4
  53. package/src/cli/data.ts +96 -34
  54. package/src/cli/delivery-orders.ts +18 -1
  55. package/src/cli/file-system.ts +6 -2
  56. package/src/cli/format-error.ts +45 -2
  57. package/src/cli/init.ts +13 -7
  58. package/src/cli/models.ts +43 -12
  59. package/src/cli/program.ts +5 -56
  60. package/src/cli/project-context.ts +117 -32
  61. package/src/cli/project.ts +80 -28
  62. package/src/cli/report.ts +24 -9
  63. package/src/cli/runs.ts +33 -8
  64. package/src/cli/settings.ts +13 -5
  65. package/src/cli/skill.ts +61 -22
  66. package/src/cli/source.ts +3 -2
  67. package/src/cli/terminal-io.ts +13 -6
  68. package/src/cli/trainings.ts +129 -35
  69. package/src/cli/version.ts +2 -2
  70. package/src/client.ts +3 -9
  71. package/src/transport.ts +13 -1
  72. package/openapi/gen/models/gistingTrainingConfigurationInputRegime.ts +0 -2
  73. package/openapi/gen/models/gistingTrainingConfigurationRegime.ts +0 -2
  74. package/openapi/gen/models/sftTrainingConfigurationInputRegime.ts +0 -2
  75. package/openapi/gen/models/sftTrainingConfigurationRegime.ts +0 -2
package/src/cli/check.ts CHANGED
@@ -6,7 +6,6 @@ import { mkdtemp, readFile, rm } from "node:fs/promises";
6
6
  import { tmpdir } from "node:os";
7
7
  import { dirname, join } from "node:path";
8
8
 
9
- import { assert } from "es-toolkit";
10
9
  import {
11
10
  createBundledTaskUiTypechecker,
12
11
  findPostprocessKeyCollisions,
@@ -27,6 +26,8 @@ import {
27
26
  import { resolveCliEntry } from "./cli-entry";
28
27
  import { API_KEY_ENVIRONMENT_NAMES } from "./environment";
29
28
  import { loadProjectSource, type ProjectSource } from "./source";
29
+ import { assertCli, REPORT_HINT, TolokaCliError } from "./cli-error";
30
+ import type { TolokaCliErrorOptions } from "./cli-error";
30
31
 
31
32
  export type CheckStatus = "passed" | "failed" | "missing" | "partial" | "infrastructure_error";
32
33
  export type CheckKind =
@@ -118,10 +119,10 @@ export type CheckProjectResult = {
118
119
 
119
120
  export type FreshCheckFailurePhase = "spawn" | "execute" | "read_result" | "parse_result";
120
121
 
121
- export class FreshCheckProcessError extends Error {
122
+ export class FreshCheckProcessError extends TolokaCliError {
122
123
  readonly phase: FreshCheckFailurePhase;
123
124
 
124
- constructor(phase: FreshCheckFailurePhase, message: string, options?: ErrorOptions) {
125
+ constructor(phase: FreshCheckFailurePhase, message: string, options: TolokaCliErrorOptions) {
125
126
  super(message, options);
126
127
  this.name = "FreshCheckProcessError";
127
128
  this.phase = phase;
@@ -358,13 +359,15 @@ const filterInventory = (
358
359
  pipeline.sourceDirectory.split("/").at(-1) === pipelineSelector,
359
360
  );
360
361
  if (pipelineSelector !== undefined) {
361
- assert(
362
+ assertCli(
362
363
  pipelines.length > 0,
363
364
  `Unknown pipeline ${JSON.stringify(pipelineSelector)}. Available: ${inventory.map((item) => item.sourceDirectory).join(", ")}`,
365
+ { hint: { description: "Pass one of the listed source directories." } },
364
366
  );
365
- assert(
367
+ assertCli(
366
368
  pipelines.length === 1,
367
369
  `Pipeline selector ${JSON.stringify(pipelineSelector)} is ambiguous: ${pipelines.map((item) => item.sourceDirectory).join(", ")}`,
370
+ { hint: { description: "Pass the full source directory of the pipeline you mean." } },
368
371
  );
369
372
  }
370
373
  if (nodeSelector === undefined) return { pipelines };
@@ -373,11 +376,12 @@ const filterInventory = (
373
376
  .filter((node) => node.id === nodeSelector || node.name === nodeSelector)
374
377
  .map((node) => ({ pipeline, node })),
375
378
  );
376
- assert(
379
+ assertCli(
377
380
  matches.length === 1,
378
381
  matches.length === 0
379
382
  ? `Unknown checkable node ${JSON.stringify(nodeSelector)}.`
380
383
  : `Node selector ${JSON.stringify(nodeSelector)} is ambiguous across pipelines.`,
384
+ { hint: { description: "Pass the pipeline selector together with the node id." } },
381
385
  );
382
386
  return { pipelines: [matches[0]!.pipeline], nodeId: matches[0]!.node.id };
383
387
  };
@@ -789,28 +793,47 @@ const isCheckStatus = (value: unknown): value is CheckStatus =>
789
793
  value === "infrastructure_error";
790
794
 
791
795
  const parseFreshCheckDiagnostic = (value: unknown): CheckDiagnostic => {
792
- assert(typeof value === "object" && value !== null, "Fresh check returned invalid diagnostics");
796
+ assertCli(
797
+ typeof value === "object" && value !== null,
798
+ "Fresh check returned invalid diagnostics",
799
+ { hint: REPORT_HINT },
800
+ );
793
801
  const message = Reflect.get(value, "message");
794
802
  const severity = Reflect.get(value, "severity");
795
803
  const setting = Reflect.get(value, "setting");
796
804
  const line = Reflect.get(value, "line");
797
805
  const column = Reflect.get(value, "column");
798
806
  const code = Reflect.get(value, "code");
799
- assert(typeof message === "string", "Fresh check diagnostic has no message");
800
- assert(
807
+ assertCli(
808
+ typeof message === "string",
809
+ "Fresh check diagnostic has no message",
810
+ { hint: REPORT_HINT },
811
+ );
812
+ assertCli(
801
813
  severity === undefined || severity === "error" || severity === "warning",
802
814
  "Fresh check diagnostic has invalid severity",
815
+ { hint: REPORT_HINT },
803
816
  );
804
- assert(
817
+ assertCli(
805
818
  setting === undefined || typeof setting === "string",
806
819
  "Fresh check diagnostic has invalid setting",
820
+ { hint: REPORT_HINT },
821
+ );
822
+ assertCli(
823
+ line === undefined || Number.isInteger(line),
824
+ "Fresh check diagnostic has invalid line",
825
+ { hint: REPORT_HINT },
807
826
  );
808
- assert(line === undefined || Number.isInteger(line), "Fresh check diagnostic has invalid line");
809
- assert(
827
+ assertCli(
810
828
  column === undefined || Number.isInteger(column),
811
829
  "Fresh check diagnostic has invalid column",
830
+ { hint: REPORT_HINT },
831
+ );
832
+ assertCli(
833
+ code === undefined || typeof code === "string",
834
+ "Fresh check diagnostic has invalid code",
835
+ { hint: REPORT_HINT },
812
836
  );
813
- assert(code === undefined || typeof code === "string", "Fresh check diagnostic has invalid code");
814
837
  return {
815
838
  message,
816
839
  ...(severity === undefined ? {} : { severity }),
@@ -822,7 +845,11 @@ const parseFreshCheckDiagnostic = (value: unknown): CheckDiagnostic => {
822
845
  };
823
846
 
824
847
  const parseFreshCheckResultItem = (value: unknown): CheckResult => {
825
- assert(typeof value === "object" && value !== null, "Fresh check returned an invalid result");
848
+ assertCli(
849
+ typeof value === "object" && value !== null,
850
+ "Fresh check returned an invalid result",
851
+ { hint: REPORT_HINT },
852
+ );
826
853
  const scope = Reflect.get(value, "scope");
827
854
  const kind = Reflect.get(value, "kind");
828
855
  const status = Reflect.get(value, "status");
@@ -831,25 +858,33 @@ const parseFreshCheckResultItem = (value: unknown): CheckResult => {
831
858
  const pipeline = Reflect.get(value, "pipelineFingerprint");
832
859
  const nodeId = Reflect.get(value, "nodeId");
833
860
  const truncated = Reflect.get(value, "truncatedDiagnosticCount");
834
- assert(isCheckScope(scope), "Fresh check returned an invalid scope");
835
- assert(isCheckKind(kind), "Fresh check returned an invalid kind");
836
- assert(isCheckStatus(status), "Fresh check returned an invalid status");
837
- assert(Array.isArray(diagnostics), "Fresh check returned invalid diagnostics");
838
- assert(
861
+ assertCli(isCheckScope(scope), "Fresh check returned an invalid scope", { hint: REPORT_HINT });
862
+ assertCli(isCheckKind(kind), "Fresh check returned an invalid kind", { hint: REPORT_HINT });
863
+ assertCli(isCheckStatus(status), "Fresh check returned an invalid status", { hint: REPORT_HINT });
864
+ assertCli(
865
+ Array.isArray(diagnostics),
866
+ "Fresh check returned invalid diagnostics",
867
+ { hint: REPORT_HINT },
868
+ );
869
+ assertCli(
839
870
  sourceDirectory === undefined || typeof sourceDirectory === "string",
840
871
  "Fresh check returned an invalid source directory",
872
+ { hint: REPORT_HINT },
841
873
  );
842
- assert(
874
+ assertCli(
843
875
  pipeline === undefined || typeof pipeline === "string",
844
876
  "Fresh check returned an invalid pipeline fingerprint",
877
+ { hint: REPORT_HINT },
845
878
  );
846
- assert(
879
+ assertCli(
847
880
  nodeId === undefined || typeof nodeId === "string",
848
881
  "Fresh check returned an invalid node id",
882
+ { hint: REPORT_HINT },
849
883
  );
850
- assert(
884
+ assertCli(
851
885
  truncated === undefined || (Number.isInteger(truncated) && Number(truncated) >= 0),
852
886
  "Fresh check returned an invalid truncated count",
887
+ { hint: REPORT_HINT },
853
888
  );
854
889
  return {
855
890
  scope,
@@ -864,14 +899,19 @@ const parseFreshCheckResultItem = (value: unknown): CheckResult => {
864
899
  };
865
900
 
866
901
  const parseFreshCheckResult = (value: unknown, json: boolean): CheckProjectResult => {
867
- assert(typeof value === "object" && value !== null, "Fresh check returned invalid JSON");
902
+ assertCli(
903
+ typeof value === "object" && value !== null,
904
+ "Fresh check returned invalid JSON",
905
+ { hint: REPORT_HINT },
906
+ );
868
907
  const exitCode = Reflect.get(value, "exitCode");
869
908
  const rawResults = Reflect.get(value, "results");
870
- assert(
909
+ assertCli(
871
910
  exitCode === 0 || exitCode === 1 || exitCode === 2,
872
911
  "Fresh check returned an invalid exit code",
912
+ { hint: REPORT_HINT },
873
913
  );
874
- assert(Array.isArray(rawResults), "Fresh check returned no results");
914
+ assertCli(Array.isArray(rawResults), "Fresh check returned no results", { hint: REPORT_HINT });
875
915
  const results = rawResults.map(parseFreshCheckResultItem);
876
916
  return {
877
917
  exitCode,
@@ -899,13 +939,16 @@ export const readFreshCheckResult = async (
899
939
  output.length === 0
900
940
  ? `Local check process exited ${run.exitCode} before producing results`
901
941
  : `Local check process failed before producing results:\n${output}`,
902
- { cause: error },
942
+ {
943
+ cause: error,
944
+ hint: { description: "Fix the errors printed above, then re-run toloka check." },
945
+ },
903
946
  );
904
947
  }
905
948
  throw new FreshCheckProcessError(
906
949
  "read_result",
907
950
  "Local check process completed without producing a result file",
908
- { cause: error },
951
+ { cause: error, hint: REPORT_HINT },
909
952
  );
910
953
  }
911
954
 
@@ -915,6 +958,7 @@ export const readFreshCheckResult = async (
915
958
  } catch (error) {
916
959
  throw new FreshCheckProcessError("parse_result", "Local check process returned invalid JSON", {
917
960
  cause: error,
961
+ hint: REPORT_HINT,
918
962
  });
919
963
  }
920
964
 
@@ -924,7 +968,7 @@ export const readFreshCheckResult = async (
924
968
  throw new FreshCheckProcessError(
925
969
  "parse_result",
926
970
  `Local check process returned an invalid result: ${error instanceof Error ? error.message : String(error)}`,
927
- { cause: error },
971
+ { cause: error, hint: REPORT_HINT },
928
972
  );
929
973
  }
930
974
  };
@@ -958,7 +1002,10 @@ export const runFreshCheck = async (options: CheckProjectOptions): Promise<Check
958
1002
  throw new FreshCheckProcessError(
959
1003
  "spawn",
960
1004
  `Unable to start the local check process: ${error instanceof Error ? error.message : String(error)}`,
961
- { cause: error },
1005
+ {
1006
+ cause: error,
1007
+ hint: { description: "Check that bun is installed and on PATH, then re-run toloka check." },
1008
+ },
962
1009
  );
963
1010
  }
964
1011
  return await readFreshCheckResult(
@@ -1,6 +1,6 @@
1
- import { assert } from "es-toolkit";
2
1
  import { existsSync, realpathSync } from "node:fs";
3
2
  import { dirname, join } from "node:path";
3
+ import { assertCli } from "./cli-error";
4
4
 
5
5
  const CLI_ENTRY = join("bin", "toloka.js");
6
6
 
@@ -33,9 +33,10 @@ export const resolveCliEntry = (runtimeEntry: string | undefined = process.argv[
33
33
  return entry;
34
34
  }
35
35
  const workingDirectoryEntry = findCliEntryAbove(process.cwd());
36
- assert(
36
+ assertCli(
37
37
  workingDirectoryEntry !== undefined,
38
38
  `Cannot locate the toloka CLI entry point. An installed @toloka-platform/sdk ships ${CLI_ENTRY} in a directory above its runtime entry, but none exists above ${runtimeEntry ?? "the unset process.argv[1]"} or above ${process.cwd()}. Reinstall @toloka-platform/sdk.`,
39
+ { hint: { disabled: true } },
39
40
  );
40
41
  return workingDirectoryEntry;
41
42
  };
@@ -0,0 +1,43 @@
1
+ export type CliErrorHint =
2
+ | { readonly description: string; readonly disabled?: never }
3
+ | { readonly disabled: true; readonly description?: never };
4
+
5
+ export type TolokaCliErrorOptions = {
6
+ readonly hint: CliErrorHint;
7
+ readonly cause?: unknown;
8
+ };
9
+
10
+ export class TolokaCliError extends Error {
11
+ readonly hint: CliErrorHint;
12
+
13
+ constructor(message: string, options: TolokaCliErrorOptions) {
14
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
15
+ this.name = "TolokaCliError";
16
+ this.hint = options.hint;
17
+ }
18
+ }
19
+
20
+ export function assertCli(
21
+ condition: unknown,
22
+ message: string,
23
+ options: TolokaCliErrorOptions,
24
+ ): asserts condition {
25
+ if (!condition) throw new TolokaCliError(message, options);
26
+ }
27
+
28
+ export const formatHint = (hint: CliErrorHint): string | undefined =>
29
+ "disabled" in hint ? undefined : `Hint: ${hint.description}`;
30
+
31
+ export const REPORT_HINT: CliErrorHint = {
32
+ description:
33
+ "This looks like a defect in the CLI or the platform. Write a report as the toloka-report-issue skill describes, then send it with toloka report <report.md>.",
34
+ };
35
+
36
+ export const RETRY_OR_REPORT_HINT: CliErrorHint = {
37
+ description:
38
+ "Retry the command; if it keeps failing, write a report as the toloka-report-issue skill describes and send it with toloka report <report.md>.",
39
+ };
40
+
41
+ export const REINSTALL_HINT: CliErrorHint = {
42
+ description: "Reinstall @toloka-platform/sdk to restore the bundled files.",
43
+ };
@@ -1,14 +1,14 @@
1
- import { assert } from "es-toolkit";
2
1
  import { resolve } from "node:path";
3
2
  import type { Command } from "commander";
4
3
  import { positiveInteger, resolveContext, type CommandHost } from "../context";
5
4
  import {
5
+ createDataset,
6
6
  generateDatasetFieldDeclarations,
7
7
  listProjectDatasets,
8
8
  loadDataset,
9
9
  uploadDataset,
10
10
  } from "../data";
11
- import { MISSING_PROJECT_ID_MESSAGE } from "../project-context";
11
+ import { assertProjectSelected } from "../project-context";
12
12
 
13
13
  export const registerDatasetCommands = (program: Command, host: CommandHost): void => {
14
14
  const { context, globals } = host;
@@ -20,10 +20,20 @@ export const registerDatasetCommands = (program: Command, host: CommandHost): vo
20
20
  .description("list live datasets in the selected project")
21
21
  .action(async () => {
22
22
  const { client, projectId } = await resolveContext(globals(), context);
23
- assert(projectId, MISSING_PROJECT_ID_MESSAGE);
23
+ assertProjectSelected(projectId);
24
24
  const result = await listProjectDatasets(client, { projectId });
25
25
  context.io.stdout(result.output);
26
26
  });
27
+ dataset
28
+ .command("create")
29
+ .argument("<name>", "dataset name")
30
+ .description("create an empty dataset in the selected project")
31
+ .action(async (name: string) => {
32
+ const { client, projectId } = await resolveContext(globals(), context);
33
+ assertProjectSelected(projectId);
34
+ const result = await createDataset(client, { projectId, name });
35
+ context.io.stdout(result.output);
36
+ });
27
37
  dataset
28
38
  .command("fields")
29
39
  .argument("<name>", "dataset name or id")
@@ -1,4 +1,3 @@
1
- import { assert } from "es-toolkit";
2
1
  import type { Command } from "commander";
3
2
  import { resolveContext, type CommandHost } from "../context";
4
3
  import {
@@ -12,7 +11,7 @@ import {
12
11
  listModelVersions,
13
12
  parseBaseModelId,
14
13
  } from "../models";
15
- import { MISSING_PROJECT_ID_MESSAGE } from "../project-context";
14
+ import { assertProjectSelected } from "../project-context";
16
15
 
17
16
  export const registerModelCommands = (program: Command, host: CommandHost): void => {
18
17
  const { context, globals } = host;
@@ -47,7 +46,7 @@ export const registerModelCommands = (program: Command, host: CommandHost): void
47
46
  .description("list Models owned by the selected project")
48
47
  .action(async (options: { json: boolean }) => {
49
48
  const { client, projectId } = await resolveContext(globals(), context);
50
- assert(projectId, MISSING_PROJECT_ID_MESSAGE);
49
+ assertProjectSelected(projectId);
51
50
  await listModels(client, { projectId, json: options.json }, context.io);
52
51
  });
53
52
  model
@@ -57,7 +56,7 @@ export const registerModelCommands = (program: Command, host: CommandHost): void
57
56
  .description("show one project Model")
58
57
  .action(async (nameOrId: string, options: { json: boolean }) => {
59
58
  const { client, projectId } = await resolveContext(globals(), context);
60
- assert(projectId, MISSING_PROJECT_ID_MESSAGE);
59
+ assertProjectSelected(projectId);
61
60
  await getModel(client, { projectId, nameOrId, json: options.json }, context.io);
62
61
  });
63
62
  model
@@ -67,7 +66,7 @@ export const registerModelCommands = (program: Command, host: CommandHost): void
67
66
  .description("list immutable versions of a model")
68
67
  .action(async (nameOrId: string, options: { json: boolean }) => {
69
68
  const { client, projectId } = await resolveContext(globals(), context);
70
- assert(projectId, MISSING_PROJECT_ID_MESSAGE);
69
+ assertProjectSelected(projectId);
71
70
  await listModelVersions(client, { projectId, nameOrId, json: options.json }, context.io);
72
71
  });
73
72
  model
@@ -80,7 +79,7 @@ export const registerModelCommands = (program: Command, host: CommandHost): void
80
79
  .action(
81
80
  async (name: string, options: { base: string; description?: string; json: boolean }) => {
82
81
  const { client, projectId } = await resolveContext(globals(), context);
83
- assert(projectId, MISSING_PROJECT_ID_MESSAGE);
82
+ assertProjectSelected(projectId);
84
83
  await createModel(
85
84
  client,
86
85
  {
@@ -101,7 +100,7 @@ export const registerModelCommands = (program: Command, host: CommandHost): void
101
100
  .description("archive a project Model")
102
101
  .action(async (nameOrId: string, options: { json: boolean }) => {
103
102
  const { client, projectId } = await resolveContext(globals(), context);
104
- assert(projectId, MISSING_PROJECT_ID_MESSAGE);
103
+ assertProjectSelected(projectId);
105
104
  await archiveModel(client, { projectId, nameOrId, json: options.json }, context.io);
106
105
  });
107
106
 
@@ -1,8 +1,7 @@
1
- import { assert } from "es-toolkit";
2
1
  import type { Command } from "commander";
3
2
  import { estimatePipelineCost } from "../cost";
4
3
  import { exitCodeForOutcome, positiveInteger, resolveContext, type CommandHost } from "../context";
5
- import { MISSING_PROJECT_ID_MESSAGE } from "../project-context";
4
+ import { assertProjectSelected } from "../project-context";
6
5
  import {
7
6
  cancelPipelineRun,
8
7
  listLivePipelines,
@@ -25,7 +24,7 @@ export const registerPipelineCommands = (program: Command, host: CommandHost): v
25
24
  .description("list live pipelines in the selected project")
26
25
  .action(async (options: { limit?: number }) => {
27
26
  const { client, projectId, state } = await resolveContext(globals(), context);
28
- assert(projectId, MISSING_PROJECT_ID_MESSAGE);
27
+ assertProjectSelected(projectId);
29
28
  const result = await listLivePipelines({
30
29
  client,
31
30
  projectId,
@@ -42,7 +41,7 @@ export const registerPipelineCommands = (program: Command, host: CommandHost): v
42
41
  .description("start a run on a deployed pipeline")
43
42
  .action(async (name: string | undefined, options: { wait: boolean; timeout?: number }) => {
44
43
  const { client, state, projectId } = await resolveContext(globals(), context);
45
- assert(projectId, MISSING_PROJECT_ID_MESSAGE);
44
+ assertProjectSelected(projectId);
46
45
  const result = await startPipelineRun(client, {
47
46
  projectId,
48
47
  state,
@@ -60,7 +59,7 @@ export const registerPipelineCommands = (program: Command, host: CommandHost): v
60
59
  .description("report the latest run's state and item counts")
61
60
  .action(async (name: string | undefined) => {
62
61
  const { client, state, projectId } = await resolveContext(globals(), context);
63
- assert(projectId, MISSING_PROJECT_ID_MESSAGE);
62
+ assertProjectSelected(projectId);
64
63
  const result = await pipelineRunStatus(client, {
65
64
  projectId,
66
65
  state,
@@ -77,7 +76,7 @@ export const registerPipelineCommands = (program: Command, host: CommandHost): v
77
76
  .description("list recent runs")
78
77
  .action(async (name: string | undefined, options: { limit?: number }) => {
79
78
  const { client, state, projectId } = await resolveContext(globals(), context);
80
- assert(projectId, MISSING_PROJECT_ID_MESSAGE);
79
+ assertProjectSelected(projectId);
81
80
  const result = await listPipelineRuns(client, {
82
81
  projectId,
83
82
  state,
@@ -94,7 +93,7 @@ export const registerPipelineCommands = (program: Command, host: CommandHost): v
94
93
  .description("forecast what a run of this pipeline would bill")
95
94
  .action(async (name: string | undefined, options: { itemLimit?: number }) => {
96
95
  const { client, state, projectId } = await resolveContext(globals(), context);
97
- assert(projectId, MISSING_PROJECT_ID_MESSAGE);
96
+ assertProjectSelected(projectId);
98
97
  const result = await estimatePipelineCost(client, {
99
98
  projectId,
100
99
  state,
@@ -1,4 +1,3 @@
1
- import { assert } from "es-toolkit";
2
1
  import type { Command } from "commander";
3
2
  import { createClient } from "../../client";
4
3
  import {
@@ -11,17 +10,9 @@ import {
11
10
  type CommandHost,
12
11
  } from "../context";
13
12
  import { requireStoredCredentials } from "../credentials";
14
- import {
15
- MISSING_PROJECT_ID_MESSAGE,
16
- addProject,
17
- createProject,
18
- currentProject,
19
- listRemoteProjects,
20
- removeProject,
21
- switchProject,
22
- untrackProjectResource,
23
- } from "../project-context";
13
+ import { addProject, assertProjectSelected, createProject, currentProject, listRemoteProjects, removeProject, switchProject, untrackProjectResource } from "../project-context";
24
14
  import { readTolokaState } from "../../state";
15
+ import { assertCli } from "../cli-error";
25
16
 
26
17
  export const registerProjectCommands = (program: Command, host: CommandHost): void => {
27
18
  const { context, exitCode, globals } = host;
@@ -97,7 +88,7 @@ export const registerProjectCommands = (program: Command, host: CommandHost): vo
97
88
  .description("print the current project URL")
98
89
  .action(async () => {
99
90
  const { baseUrl, client, projectId } = await resolveContext(globals(), context);
100
- assert(projectId, MISSING_PROJECT_ID_MESSAGE);
91
+ assertProjectSelected(projectId);
101
92
  const live = await client.projects.get({ projectId });
102
93
  const url = new URL(
103
94
  `/teams/${encodeURIComponent(live.teamId)}/projects/${encodeURIComponent(live.id)}`,
@@ -125,9 +116,10 @@ export const registerProjectCommands = (program: Command, host: CommandHost): vo
125
116
  .option("--confirm", "confirm untracking in a non-interactive environment", false)
126
117
  .description("remove a pipeline or dataset from local tracking without deleting it")
127
118
  .action(async (type: string, name: string, options: { confirm: boolean }) => {
128
- assert(
119
+ assertCli(
129
120
  type === "pipeline" || type === "dataset",
130
121
  "Resource type must be pipeline or dataset.",
122
+ { hint: { description: "Run toloka project untrack pipeline <name> or toloka project untrack dataset <name>." } },
131
123
  );
132
124
  const sourceRoot = await resolveSourceRoot(globals(), context.io);
133
125
  await untrackProjectResource({
@@ -144,9 +136,10 @@ export const registerProjectCommands = (program: Command, host: CommandHost): vo
144
136
  .option("--name <name>", "platform project name")
145
137
  .option("--team-id <id>", "team that owns the project")
146
138
  .description("create a live project and select it");
147
- if (context.internalUser === true) {
148
- create.option("--delivery-order-id <id>", "Sunday delivery order id (internal users only)");
149
- }
139
+ create.option(
140
+ "--delivery-order-id <id>",
141
+ "Sunday delivery order id (required when the owning team is internal)",
142
+ );
150
143
  create.action(
151
144
  async (
152
145
  alias: string | undefined,
@@ -1,8 +1,8 @@
1
- import { assert } from "es-toolkit";
2
1
  import type { Command } from "commander";
3
2
  import { authoredByOption, resolveInitRoot, type CommandHost } from "../context";
4
3
  import { PRODUCTION_BASE_URL, resolveInternalBaseUrl } from "../config";
5
4
  import { submitIssueReport, type IssueReportAuthor } from "../report";
5
+ import { assertCli } from "../cli-error";
6
6
 
7
7
  export const registerReportCommand = (program: Command, host: CommandHost): void => {
8
8
  const { context, exitCode, globals } = host;
@@ -17,7 +17,11 @@ export const registerReportCommand = (program: Command, host: CommandHost): void
17
17
  markdownPath: string,
18
18
  options: { authoredBy: IssueReportAuthor; confirm: boolean },
19
19
  ) => {
20
- assert(context.analytics, "Issue reporting requires CLI analytics.");
20
+ assertCli(
21
+ context.analytics,
22
+ "Issue reporting requires CLI analytics.",
23
+ { hint: { description: "Set TOLOKA_TELEMETRY=1 to enable analytics, then re-run toloka report." } },
24
+ );
21
25
  const sourceRoot = resolveInitRoot(globals());
22
26
  const baseUrl =
23
27
  globals().baseUrl ??
@@ -1,8 +1,8 @@
1
- import { assert } from "es-toolkit";
2
1
  import type { Command } from "commander";
3
2
  import { resolveContext, type CommandHost } from "../context";
4
3
  import { setProjectSecret } from "../data";
5
- import { MISSING_PROJECT_ID_MESSAGE } from "../project-context";
4
+ import { assertProjectSelected } from "../project-context";
5
+ import { assertCli } from "../cli-error";
6
6
 
7
7
  export const registerSecretCommands = (program: Command, host: CommandHost): void => {
8
8
  const { context, exitCode, globals } = host;
@@ -17,15 +17,16 @@ export const registerSecretCommands = (program: Command, host: CommandHost): voi
17
17
  .description("set a secret value without writing it to disk")
18
18
  .action(async (name: string, options: { fromEnv?: string }) => {
19
19
  const { client, projectId } = await resolveContext(globals(), context);
20
- assert(projectId, MISSING_PROJECT_ID_MESSAGE);
20
+ assertProjectSelected(projectId);
21
21
 
22
22
  const value =
23
23
  options.fromEnv === undefined
24
24
  ? (await context.io.readStdin()).replace(/\r?\n$/u, "")
25
25
  : context.environment[options.fromEnv];
26
- assert(
26
+ assertCli(
27
27
  value !== undefined,
28
28
  `Missing secret value. Set ${options.fromEnv ?? "the named environment variable"} or pipe it to stdin.`,
29
+ { hint: { disabled: true } },
29
30
  );
30
31
 
31
32
  try {
@@ -1,4 +1,3 @@
1
- import { assert } from "es-toolkit";
2
1
  import { writeFile } from "node:fs/promises";
3
2
  import type { Command } from "commander";
4
3
  import {
@@ -12,8 +11,9 @@ import {
12
11
  import { resolveContext, resolveInitRoot, resolveSourceRoot, type CommandHost } from "../context";
13
12
  import { initProject } from "../init";
14
13
  import { applyProject, importProject, planProject, validateProject } from "../project";
15
- import { MISSING_PROJECT_ID_MESSAGE, resolveProjectContext } from "../project-context";
14
+ import { assertProjectSelected, resolveProjectContext } from "../project-context";
16
15
  import { readTolokaState } from "../../state";
16
+ import { assertCli, REPORT_HINT } from "../cli-error";
17
17
 
18
18
  const runChecksInterruptibly = async (
19
19
  options: CheckProjectOptions,
@@ -77,7 +77,7 @@ export const registerSourceCommands = (program: Command, host: CommandHost): voi
77
77
  projectId: selectedProjectId,
78
78
  } = await resolveContext(globals(), context);
79
79
  const id = projectId ?? selectedProjectId;
80
- assert(id, MISSING_PROJECT_ID_MESSAGE);
80
+ assertProjectSelected(id);
81
81
  const result = await importProject(client, {
82
82
  sourceRoot,
83
83
  projectId: id,
@@ -192,7 +192,7 @@ export const registerSourceCommands = (program: Command, host: CommandHost): voi
192
192
  exitCode.value = checked.exitCode;
193
193
  return;
194
194
  }
195
- assert(checked.source, "Passing checks must return project source");
195
+ assertCli(checked.source, "Passing checks must return project source", { hint: REPORT_HINT });
196
196
  const { client, projectId } = await resolveContext(globals(), context);
197
197
  const result = await applyProject(client, {
198
198
  sourceRoot,
@@ -1,8 +1,7 @@
1
- import { assert } from "es-toolkit";
2
1
  import type { Command } from "commander";
3
2
  import { resolveContext, type CommandHost } from "../context";
4
3
  import { projectStatus } from "../project";
5
- import { MISSING_PROJECT_ID_MESSAGE } from "../project-context";
4
+ import { assertProjectSelected } from "../project-context";
6
5
 
7
6
  export const registerStatusCommand = (program: Command, host: CommandHost): void => {
8
7
  const { context, globals } = host;
@@ -11,7 +10,7 @@ export const registerStatusCommand = (program: Command, host: CommandHost): void
11
10
  .description("report project health: live counts and pending source changes")
12
11
  .action(async () => {
13
12
  const { sourceRoot, client, projectId } = await resolveContext(globals(), context);
14
- assert(projectId, MISSING_PROJECT_ID_MESSAGE);
13
+ assertProjectSelected(projectId);
15
14
  const result = await projectStatus(client, { sourceRoot, projectId });
16
15
  context.io.stdout(result.output);
17
16
  });