@toloka-platform/sdk 0.2.0-preview.2 → 0.2.0-preview.4

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 (110) hide show
  1. package/README.md +10 -4
  2. package/dist/cli/toloka.js +162 -154
  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/authoring.md +18 -1
  7. package/docs/cli-v2.md +6 -4
  8. package/docs/cli.md +58 -11
  9. package/docs/getting-started.md +3 -3
  10. package/docs/limitations.md +1 -1
  11. package/docs/testing.md +1 -1
  12. package/docs/troubleshooting.md +1 -1
  13. package/examples/face-image-collection/README.md +2 -2
  14. package/openapi/gen/api.ts +78 -0
  15. package/openapi/gen/models/baseModel.ts +1 -0
  16. package/openapi/gen/models/checkReport.ts +2 -1
  17. package/openapi/gen/models/checkReportAnyOfTwoseven.ts +5 -0
  18. package/openapi/gen/models/checkReportAnyOfTwosevenResultsItem.ts +6 -0
  19. package/openapi/gen/models/checkReportAnyOfTwosevenResultsItemVerdict.ts +2 -0
  20. package/openapi/gen/models/currentUser.ts +3 -0
  21. package/openapi/gen/models/currentUserTeamsItem.ts +6 -0
  22. package/openapi/gen/models/deliveryOrder.ts +9 -0
  23. package/openapi/gen/models/deliveryOrderClientName.ts +2 -0
  24. package/openapi/gen/models/getTrainingRunMetricHistoryParams.ts +4 -0
  25. package/openapi/gen/models/gistingTrainingConfiguration.ts +6 -2
  26. package/openapi/gen/models/gistingTrainingConfigurationBatchSize.ts +2 -0
  27. package/openapi/gen/models/gistingTrainingConfigurationGistTokens.ts +2 -0
  28. package/openapi/gen/models/gistingTrainingConfigurationInput.ts +6 -2
  29. package/openapi/gen/models/gistingTrainingConfigurationInputBatchSize.ts +2 -0
  30. package/openapi/gen/models/gistingTrainingConfigurationInputGistTokens.ts +2 -0
  31. package/openapi/gen/models/index.ts +28 -4
  32. package/openapi/gen/models/listDeliveryOrders200.ts +5 -0
  33. package/openapi/gen/models/sftTrainingConfiguration.ts +6 -3
  34. package/openapi/gen/models/sftTrainingConfigurationBatchSize.ts +2 -0
  35. package/openapi/gen/models/sftTrainingConfigurationInput.ts +6 -3
  36. package/openapi/gen/models/sftTrainingConfigurationInputBatchSize.ts +2 -0
  37. package/openapi/gen/models/sftTrainingConfigurationInputLoraRank.ts +2 -0
  38. package/openapi/gen/models/sftTrainingConfigurationLoraRank.ts +2 -0
  39. package/openapi/gen/models/trainingRunMetricHistory.ts +9 -0
  40. package/openapi/gen/models/trainingRunMetricHistoryMetric.ts +2 -0
  41. package/openapi/gen/models/trainingRunMetricHistoryPointsItem.ts +9 -0
  42. package/openapi/gen/models/trainingRunMetricHistoryPointsItemNonFinite.ts +2 -0
  43. package/openapi/gen/models/trainingRunMetricHistoryPointsItemValue.ts +2 -0
  44. package/openapi/gen/models/trainingRunMetrics.ts +9 -0
  45. package/openapi/gen/models/trainingRunMetricsMetricsItem.ts +8 -0
  46. package/openapi/gen/models/trainingRunMetricsMetricsItemLatest.ts +9 -0
  47. package/openapi/gen/models/trainingRunMetricsMetricsItemLatestNonFinite.ts +2 -0
  48. package/openapi/gen/models/trainingRunMetricsMetricsItemLatestValue.ts +2 -0
  49. package/openapi/gen/models/trainingRunMetricsMetricsItemMetric.ts +2 -0
  50. package/openapi/gen/models/trainingRunMetricsUnavailableReason.ts +2 -0
  51. package/package.json +2 -2
  52. package/skills/catalog.json +55 -7
  53. package/skills/toloka-build/SKILL.md +35 -21
  54. package/skills/toloka-build/references/node-semantics.md +13 -11
  55. package/skills/toloka-build/references/workflow-and-cli.md +31 -20
  56. package/skills/toloka-fine-tuning/SKILL.md +155 -0
  57. package/skills/toloka-get-started/SKILL.md +70 -0
  58. package/skills/toloka-gisting/SKILL.md +166 -0
  59. package/skills/toloka-report-issue/references/report-template.md +1 -1
  60. package/src/authoring/index.ts +14 -0
  61. package/src/checks/task-ui-typecheck.ts +12 -10
  62. package/src/cli/analytics.ts +88 -15
  63. package/src/cli/auth.ts +93 -21
  64. package/src/cli/auto-update.ts +36 -39
  65. package/src/cli/check.ts +88 -39
  66. package/src/cli/cli-entry.ts +3 -2
  67. package/src/cli/cli-error.ts +43 -0
  68. package/src/cli/commands/auth.ts +89 -0
  69. package/src/cli/commands/config.ts +28 -0
  70. package/src/cli/commands/dataset.ts +85 -0
  71. package/src/cli/commands/delivery-order.ts +18 -0
  72. package/src/cli/commands/models.ts +121 -0
  73. package/src/cli/commands/pipeline.ts +131 -0
  74. package/src/cli/commands/project.ts +168 -0
  75. package/src/cli/commands/report.ts +43 -0
  76. package/src/cli/commands/secret.ts +40 -0
  77. package/src/cli/commands/skill.ts +36 -0
  78. package/src/cli/commands/source.ts +204 -0
  79. package/src/cli/commands/status.ts +17 -0
  80. package/src/cli/commands/training.ts +261 -0
  81. package/src/cli/commands/version.ts +14 -0
  82. package/src/cli/config.ts +7 -8
  83. package/src/cli/context.ts +251 -0
  84. package/src/cli/credentials.ts +12 -4
  85. package/src/cli/data.ts +96 -34
  86. package/src/cli/delivery-orders.ts +47 -0
  87. package/src/cli/environment.ts +26 -0
  88. package/src/cli/file-system.ts +6 -2
  89. package/src/cli/format-error.ts +45 -2
  90. package/src/cli/init.ts +14 -11
  91. package/src/cli/models.ts +43 -12
  92. package/src/cli/program.ts +120 -1347
  93. package/src/cli/project-context.ts +126 -43
  94. package/src/cli/project.ts +80 -28
  95. package/src/cli/report.ts +24 -9
  96. package/src/cli/runs.ts +33 -8
  97. package/src/cli/sdk-version.ts +38 -0
  98. package/src/cli/settings.ts +13 -5
  99. package/src/cli/skill.ts +67 -24
  100. package/src/cli/source.ts +3 -2
  101. package/src/cli/terminal-io.ts +110 -0
  102. package/src/cli/trainings.ts +242 -57
  103. package/src/cli/version.ts +3 -3
  104. package/src/client.ts +21 -9
  105. package/src/importer.ts +32 -2
  106. package/src/transport.ts +13 -1
  107. package/openapi/gen/models/gistingTrainingConfigurationInputRegime.ts +0 -2
  108. package/openapi/gen/models/gistingTrainingConfigurationRegime.ts +0 -2
  109. package/openapi/gen/models/sftTrainingConfigurationInputRegime.ts +0 -2
  110. 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,
@@ -25,7 +24,10 @@ import {
25
24
  type TestEvidenceKind,
26
25
  } from "../testing/test-evidence";
27
26
  import { resolveCliEntry } from "./cli-entry";
27
+ import { API_KEY_ENVIRONMENT_NAMES } from "./environment";
28
28
  import { loadProjectSource, type ProjectSource } from "./source";
29
+ import { assertCli, REPORT_HINT, TolokaCliError } from "./cli-error";
30
+ import type { TolokaCliErrorOptions } from "./cli-error";
29
31
 
30
32
  export type CheckStatus = "passed" | "failed" | "missing" | "partial" | "infrastructure_error";
31
33
  export type CheckKind =
@@ -117,10 +119,10 @@ export type CheckProjectResult = {
117
119
 
118
120
  export type FreshCheckFailurePhase = "spawn" | "execute" | "read_result" | "parse_result";
119
121
 
120
- export class FreshCheckProcessError extends Error {
122
+ export class FreshCheckProcessError extends TolokaCliError {
121
123
  readonly phase: FreshCheckFailurePhase;
122
124
 
123
- constructor(phase: FreshCheckFailurePhase, message: string, options?: ErrorOptions) {
125
+ constructor(phase: FreshCheckFailurePhase, message: string, options: TolokaCliErrorOptions) {
124
126
  super(message, options);
125
127
  this.name = "FreshCheckProcessError";
126
128
  this.phase = phase;
@@ -269,18 +271,19 @@ const loadSourceWithoutPlatformCredentials = async (
269
271
  sourceRoot: string,
270
272
  environment: NodeJS.ProcessEnv,
271
273
  ): Promise<ProjectSource> => {
272
- const environments = new Map<NodeJS.ProcessEnv, string | undefined>([
273
- [process.env, process.env.POTATO_API_KEY],
274
- [environment, environment.POTATO_API_KEY],
275
- ]);
276
- for (const current of environments.keys()) delete current.POTATO_API_KEY;
274
+ const targets = [process.env, environment];
275
+ const saved = targets.map((current) => API_KEY_ENVIRONMENT_NAMES.map((name) => current[name]));
276
+ for (const current of targets) for (const name of API_KEY_ENVIRONMENT_NAMES) delete current[name];
277
277
  try {
278
278
  return await loadProjectSource(sourceRoot);
279
279
  } finally {
280
- for (const [current, apiKey] of environments) {
281
- if (apiKey === undefined) delete current.POTATO_API_KEY;
282
- else current.POTATO_API_KEY = apiKey;
283
- }
280
+ targets.forEach((current, target) => {
281
+ API_KEY_ENVIRONMENT_NAMES.forEach((name, index) => {
282
+ const value = saved[target]?.[index];
283
+ if (value === undefined) delete current[name];
284
+ else current[name] = value;
285
+ });
286
+ });
284
287
  }
285
288
  };
286
289
 
@@ -356,13 +359,15 @@ const filterInventory = (
356
359
  pipeline.sourceDirectory.split("/").at(-1) === pipelineSelector,
357
360
  );
358
361
  if (pipelineSelector !== undefined) {
359
- assert(
362
+ assertCli(
360
363
  pipelines.length > 0,
361
364
  `Unknown pipeline ${JSON.stringify(pipelineSelector)}. Available: ${inventory.map((item) => item.sourceDirectory).join(", ")}`,
365
+ { hint: { description: "Pass one of the listed source directories." } },
362
366
  );
363
- assert(
367
+ assertCli(
364
368
  pipelines.length === 1,
365
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." } },
366
371
  );
367
372
  }
368
373
  if (nodeSelector === undefined) return { pipelines };
@@ -371,11 +376,12 @@ const filterInventory = (
371
376
  .filter((node) => node.id === nodeSelector || node.name === nodeSelector)
372
377
  .map((node) => ({ pipeline, node })),
373
378
  );
374
- assert(
379
+ assertCli(
375
380
  matches.length === 1,
376
381
  matches.length === 0
377
382
  ? `Unknown checkable node ${JSON.stringify(nodeSelector)}.`
378
383
  : `Node selector ${JSON.stringify(nodeSelector)} is ambiguous across pipelines.`,
384
+ { hint: { description: "Pass the pipeline selector together with the node id." } },
379
385
  );
380
386
  return { pipelines: [matches[0]!.pipeline], nodeId: matches[0]!.node.id };
381
387
  };
@@ -683,7 +689,7 @@ export async function checkProject(
683
689
  try {
684
690
  const selectedPipeline = selected.pipelines.length === 1 ? selected.pipelines[0] : undefined;
685
691
  const checkEnvironment = { ...(options.environment ?? process.env) };
686
- delete checkEnvironment.POTATO_API_KEY;
692
+ for (const name of API_KEY_ENVIRONMENT_NAMES) delete checkEnvironment[name];
687
693
  const environment = {
688
694
  ...checkEnvironment,
689
695
  [TEST_EVIDENCE_DIR_ENV]: evidenceDirectory,
@@ -787,28 +793,47 @@ const isCheckStatus = (value: unknown): value is CheckStatus =>
787
793
  value === "infrastructure_error";
788
794
 
789
795
  const parseFreshCheckDiagnostic = (value: unknown): CheckDiagnostic => {
790
- 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
+ );
791
801
  const message = Reflect.get(value, "message");
792
802
  const severity = Reflect.get(value, "severity");
793
803
  const setting = Reflect.get(value, "setting");
794
804
  const line = Reflect.get(value, "line");
795
805
  const column = Reflect.get(value, "column");
796
806
  const code = Reflect.get(value, "code");
797
- assert(typeof message === "string", "Fresh check diagnostic has no message");
798
- assert(
807
+ assertCli(
808
+ typeof message === "string",
809
+ "Fresh check diagnostic has no message",
810
+ { hint: REPORT_HINT },
811
+ );
812
+ assertCli(
799
813
  severity === undefined || severity === "error" || severity === "warning",
800
814
  "Fresh check diagnostic has invalid severity",
815
+ { hint: REPORT_HINT },
801
816
  );
802
- assert(
817
+ assertCli(
803
818
  setting === undefined || typeof setting === "string",
804
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 },
805
826
  );
806
- assert(line === undefined || Number.isInteger(line), "Fresh check diagnostic has invalid line");
807
- assert(
827
+ assertCli(
808
828
  column === undefined || Number.isInteger(column),
809
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 },
810
836
  );
811
- assert(code === undefined || typeof code === "string", "Fresh check diagnostic has invalid code");
812
837
  return {
813
838
  message,
814
839
  ...(severity === undefined ? {} : { severity }),
@@ -820,7 +845,11 @@ const parseFreshCheckDiagnostic = (value: unknown): CheckDiagnostic => {
820
845
  };
821
846
 
822
847
  const parseFreshCheckResultItem = (value: unknown): CheckResult => {
823
- 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
+ );
824
853
  const scope = Reflect.get(value, "scope");
825
854
  const kind = Reflect.get(value, "kind");
826
855
  const status = Reflect.get(value, "status");
@@ -829,25 +858,33 @@ const parseFreshCheckResultItem = (value: unknown): CheckResult => {
829
858
  const pipeline = Reflect.get(value, "pipelineFingerprint");
830
859
  const nodeId = Reflect.get(value, "nodeId");
831
860
  const truncated = Reflect.get(value, "truncatedDiagnosticCount");
832
- assert(isCheckScope(scope), "Fresh check returned an invalid scope");
833
- assert(isCheckKind(kind), "Fresh check returned an invalid kind");
834
- assert(isCheckStatus(status), "Fresh check returned an invalid status");
835
- assert(Array.isArray(diagnostics), "Fresh check returned invalid diagnostics");
836
- 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(
837
870
  sourceDirectory === undefined || typeof sourceDirectory === "string",
838
871
  "Fresh check returned an invalid source directory",
872
+ { hint: REPORT_HINT },
839
873
  );
840
- assert(
874
+ assertCli(
841
875
  pipeline === undefined || typeof pipeline === "string",
842
876
  "Fresh check returned an invalid pipeline fingerprint",
877
+ { hint: REPORT_HINT },
843
878
  );
844
- assert(
879
+ assertCli(
845
880
  nodeId === undefined || typeof nodeId === "string",
846
881
  "Fresh check returned an invalid node id",
882
+ { hint: REPORT_HINT },
847
883
  );
848
- assert(
884
+ assertCli(
849
885
  truncated === undefined || (Number.isInteger(truncated) && Number(truncated) >= 0),
850
886
  "Fresh check returned an invalid truncated count",
887
+ { hint: REPORT_HINT },
851
888
  );
852
889
  return {
853
890
  scope,
@@ -862,14 +899,19 @@ const parseFreshCheckResultItem = (value: unknown): CheckResult => {
862
899
  };
863
900
 
864
901
  const parseFreshCheckResult = (value: unknown, json: boolean): CheckProjectResult => {
865
- 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
+ );
866
907
  const exitCode = Reflect.get(value, "exitCode");
867
908
  const rawResults = Reflect.get(value, "results");
868
- assert(
909
+ assertCli(
869
910
  exitCode === 0 || exitCode === 1 || exitCode === 2,
870
911
  "Fresh check returned an invalid exit code",
912
+ { hint: REPORT_HINT },
871
913
  );
872
- assert(Array.isArray(rawResults), "Fresh check returned no results");
914
+ assertCli(Array.isArray(rawResults), "Fresh check returned no results", { hint: REPORT_HINT });
873
915
  const results = rawResults.map(parseFreshCheckResultItem);
874
916
  return {
875
917
  exitCode,
@@ -897,13 +939,16 @@ export const readFreshCheckResult = async (
897
939
  output.length === 0
898
940
  ? `Local check process exited ${run.exitCode} before producing results`
899
941
  : `Local check process failed before producing results:\n${output}`,
900
- { cause: error },
942
+ {
943
+ cause: error,
944
+ hint: { description: "Fix the errors printed above, then re-run toloka check." },
945
+ },
901
946
  );
902
947
  }
903
948
  throw new FreshCheckProcessError(
904
949
  "read_result",
905
950
  "Local check process completed without producing a result file",
906
- { cause: error },
951
+ { cause: error, hint: REPORT_HINT },
907
952
  );
908
953
  }
909
954
 
@@ -913,6 +958,7 @@ export const readFreshCheckResult = async (
913
958
  } catch (error) {
914
959
  throw new FreshCheckProcessError("parse_result", "Local check process returned invalid JSON", {
915
960
  cause: error,
961
+ hint: REPORT_HINT,
916
962
  });
917
963
  }
918
964
 
@@ -922,7 +968,7 @@ export const readFreshCheckResult = async (
922
968
  throw new FreshCheckProcessError(
923
969
  "parse_result",
924
970
  `Local check process returned an invalid result: ${error instanceof Error ? error.message : String(error)}`,
925
- { cause: error },
971
+ { cause: error, hint: REPORT_HINT },
926
972
  );
927
973
  }
928
974
  };
@@ -956,7 +1002,10 @@ export const runFreshCheck = async (options: CheckProjectOptions): Promise<Check
956
1002
  throw new FreshCheckProcessError(
957
1003
  "spawn",
958
1004
  `Unable to start the local check process: ${error instanceof Error ? error.message : String(error)}`,
959
- { cause: error },
1005
+ {
1006
+ cause: error,
1007
+ hint: { description: "Check that bun is installed and on PATH, then re-run toloka check." },
1008
+ },
960
1009
  );
961
1010
  }
962
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
+ };
@@ -0,0 +1,89 @@
1
+ import type { Command } from "commander";
2
+ import { createClient } from "../../client";
3
+ import { authStatus, login, logout } from "../auth";
4
+ import {
5
+ identifyClientUser,
6
+ resolveSelectedProject,
7
+ resolveSourceRoot,
8
+ type CommandHost,
9
+ } from "../context";
10
+ import { findCommandCredentials, readStoredCredentials } from "../credentials";
11
+ import { resolveTolokaConfig } from "../config";
12
+ import { readTolokaState } from "../../state";
13
+
14
+ export const registerAuthCommands = (program: Command, host: CommandHost): void => {
15
+ const { context, exitCode, globals } = host;
16
+
17
+ program
18
+ .command("login")
19
+ .description("store an API key for this machine after verifying it")
20
+ .option("--api-key-stdin", "read the API key from standard input instead of prompting", false)
21
+ .action(async (options: { apiKeyStdin: boolean }) => {
22
+ const apiKey = options.apiKeyStdin ? (await context.io.readStdin()).trim() : globals().apiKey;
23
+ const injectedClient = context.injectedClient;
24
+ const result = await login({
25
+ ...(apiKey === undefined ? {} : { apiKey }),
26
+ ...(globals().baseUrl === undefined ? {} : { baseUrl: globals().baseUrl }),
27
+ ...(context.io.prompt === undefined ? {} : { prompt: context.io.prompt }),
28
+ ...(injectedClient === undefined ? {} : { probe: () => injectedClient }),
29
+ write: context.io.stdout,
30
+ environment: context.environment,
31
+ });
32
+ context.io.stdout(result.output);
33
+ try {
34
+ const stored = await readStoredCredentials(context.environment);
35
+ if (stored === undefined) return;
36
+ const client = context.injectedClient ?? createClient(stored);
37
+ await identifyClientUser(client, context.analytics);
38
+ } catch {}
39
+ });
40
+
41
+ program
42
+ .command("logout")
43
+ .description("remove stored credentials from this machine")
44
+ .action(async () => {
45
+ const result = await logout(context.environment);
46
+ context.io.stdout(result.output);
47
+ });
48
+
49
+ program
50
+ .command("auth-status")
51
+ .alias("whoami")
52
+ .description("show which platform and key the CLI would use")
53
+ .option("--show-token", "print the API key instead of masking it", false)
54
+ .action(async (options: { showToken: boolean }) => {
55
+ const flags = globals();
56
+ const credentialLookup = await findCommandCredentials(
57
+ context.environment,
58
+ flags.apiKey ?? context.environment.TOLOKA_API_KEY,
59
+ );
60
+ const stored = credentialLookup.found?.credentials;
61
+ const sourceRoot = await resolveSourceRoot(flags, context.io);
62
+ const state = await readTolokaState(sourceRoot);
63
+ const project = resolveSelectedProject(flags, context, stored, state);
64
+ const injectedClient = context.injectedClient;
65
+
66
+ const result = await authStatus({
67
+ environment: context.environment,
68
+ showToken: options.showToken,
69
+ credentialLookup,
70
+ ...(flags.apiKey === undefined ? {} : { flagApiKey: flags.apiKey }),
71
+ ...(flags.baseUrl === undefined ? {} : { flagBaseUrl: flags.baseUrl }),
72
+ ...(injectedClient === undefined ? {} : { probe: () => injectedClient }),
73
+ ...(project === undefined ? {} : { project }),
74
+ });
75
+
76
+ context.io.stdout(result.output);
77
+ if (!result.authenticated) {
78
+ exitCode.value = 1;
79
+ return;
80
+ }
81
+ try {
82
+ const config = resolveTolokaConfig(flags, state, context.environment, stored);
83
+ const client =
84
+ context.injectedClient ??
85
+ createClient({ apiKey: config.apiKey, baseUrl: config.baseUrl });
86
+ await identifyClientUser(client, context.analytics);
87
+ } catch {}
88
+ });
89
+ };
@@ -0,0 +1,28 @@
1
+ import type { Command } from "commander";
2
+ import type { CommandHost } from "../context";
3
+ import { getCliSetting, readCliSettings, setCliSetting } from "../settings";
4
+
5
+ export const registerConfigCommands = (program: Command, host: CommandHost): void => {
6
+ const config = program.command("config").description("read and write machine CLI settings");
7
+ config
8
+ .command("get")
9
+ .argument("[name]", "setting name; omit to print every setting")
10
+ .description("print a CLI setting, or every setting")
11
+ .action(async (name?: string) => {
12
+ const settings = await readCliSettings(host.context.environment);
13
+ host.context.io.stdout(getCliSetting(settings, name).output);
14
+ });
15
+ config
16
+ .command("set")
17
+ .argument("<name>", "setting name")
18
+ .argument("<value>", "setting value")
19
+ .description("write a CLI setting for this machine")
20
+ .action(async (name: string, value: string) => {
21
+ const result = await setCliSetting(host.context.environment, name, value);
22
+ host.context.io.stdout(result.output);
23
+ });
24
+ config.action(async () => {
25
+ const settings = await readCliSettings(host.context.environment);
26
+ host.context.io.stdout(getCliSetting(settings, undefined).output);
27
+ });
28
+ };
@@ -0,0 +1,85 @@
1
+ import { resolve } from "node:path";
2
+ import type { Command } from "commander";
3
+ import { positiveInteger, resolveContext, type CommandHost } from "../context";
4
+ import {
5
+ createDataset,
6
+ generateDatasetFieldDeclarations,
7
+ listProjectDatasets,
8
+ loadDataset,
9
+ uploadDataset,
10
+ } from "../data";
11
+ import { assertProjectSelected } from "../project-context";
12
+
13
+ export const registerDatasetCommands = (program: Command, host: CommandHost): void => {
14
+ const { context, globals } = host;
15
+ const dataset = program
16
+ .command("dataset")
17
+ .description("inspect and move dataset catalog and rows");
18
+ dataset
19
+ .command("list")
20
+ .description("list live datasets in the selected project")
21
+ .action(async () => {
22
+ const { client, projectId } = await resolveContext(globals(), context);
23
+ assertProjectSelected(projectId);
24
+ const result = await listProjectDatasets(client, { projectId });
25
+ context.io.stdout(result.output);
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
+ });
37
+ dataset
38
+ .command("fields")
39
+ .argument("<name>", "dataset name or id")
40
+ .description("print field declarations inferred from uploaded rows")
41
+ .action(async (name: string) => {
42
+ const { sourceRoot, client, projectId } = await resolveContext(globals(), context);
43
+ const result = await generateDatasetFieldDeclarations(client, {
44
+ sourceRoot,
45
+ datasetName: name,
46
+ ...(projectId === undefined ? {} : { projectId }),
47
+ });
48
+ context.io.stdout(result.output);
49
+ });
50
+ dataset
51
+ .command("load")
52
+ .argument("<name>", "dataset name or id")
53
+ .requiredOption("--out <file>", "JSONL file to write")
54
+ .option("--limit <n>", "how many rows to keep after download", positiveInteger)
55
+ .option("--timeout <ms>", "how long to wait for the export", positiveInteger)
56
+ .description("download live dataset rows into a JSONL file")
57
+ .action(async (name: string, options: { out: string; limit?: number; timeout?: number }) => {
58
+ const { sourceRoot, client, projectId } = await resolveContext(globals(), context);
59
+ const result = await loadDataset(client, {
60
+ sourceRoot,
61
+ datasetName: name,
62
+ outPath: resolve(options.out),
63
+ ...(projectId === undefined ? {} : { projectId }),
64
+ ...(options.limit === undefined ? {} : { limit: options.limit }),
65
+ ...(options.timeout === undefined ? {} : { timeoutMs: options.timeout }),
66
+ });
67
+ context.io.stdout(result.output);
68
+ });
69
+ dataset
70
+ .command("upload")
71
+ .argument("<name>", "dataset name or id")
72
+ .argument("<file>", "JSON array, JSONL, or CSV file")
73
+ .description("append rows to a dataset")
74
+ .action(async (name: string, file: string) => {
75
+ const { sourceRoot, client, projectId } = await resolveContext(globals(), context);
76
+ const result = await uploadDataset(client, {
77
+ sourceRoot,
78
+ datasetName: name,
79
+ filePath: resolve(file),
80
+ ...(projectId === undefined ? {} : { projectId }),
81
+ });
82
+ context.io.stdout(result.output);
83
+ if (result.warning !== undefined) context.io.stderr(result.warning);
84
+ });
85
+ };
@@ -0,0 +1,18 @@
1
+ import type { Command } from "commander";
2
+ import { resolveContext, type CommandHost } from "../context";
3
+ import { listDeliveryOrders } from "../delivery-orders";
4
+
5
+ export const registerDeliveryOrderCommands = (program: Command, host: CommandHost): void => {
6
+ const { context, globals } = host;
7
+ const deliveryOrder = program
8
+ .command("delivery-order")
9
+ .description("list Sunday delivery orders for project create");
10
+ deliveryOrder
11
+ .command("list")
12
+ .description("list active Sunday delivery orders this key can attach")
13
+ .action(async () => {
14
+ const { client } = await resolveContext(globals(), context);
15
+ const result = await listDeliveryOrders({ client });
16
+ context.io.stdout(result.output);
17
+ });
18
+ };