@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.
- package/README.md +10 -4
- package/dist/cli/toloka.js +162 -154
- package/dist/cli/toloka.js.jsc +0 -0
- package/dist/cli/toloka.js.jsc.sha256 +1 -1
- package/dist/cli/toloka.js.sha256 +1 -1
- package/docs/authoring.md +18 -1
- package/docs/cli-v2.md +6 -4
- package/docs/cli.md +58 -11
- package/docs/getting-started.md +3 -3
- package/docs/limitations.md +1 -1
- package/docs/testing.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/examples/face-image-collection/README.md +2 -2
- package/openapi/gen/api.ts +78 -0
- package/openapi/gen/models/baseModel.ts +1 -0
- package/openapi/gen/models/checkReport.ts +2 -1
- package/openapi/gen/models/checkReportAnyOfTwoseven.ts +5 -0
- package/openapi/gen/models/checkReportAnyOfTwosevenResultsItem.ts +6 -0
- package/openapi/gen/models/checkReportAnyOfTwosevenResultsItemVerdict.ts +2 -0
- package/openapi/gen/models/currentUser.ts +3 -0
- package/openapi/gen/models/currentUserTeamsItem.ts +6 -0
- package/openapi/gen/models/deliveryOrder.ts +9 -0
- package/openapi/gen/models/deliveryOrderClientName.ts +2 -0
- package/openapi/gen/models/getTrainingRunMetricHistoryParams.ts +4 -0
- package/openapi/gen/models/gistingTrainingConfiguration.ts +6 -2
- package/openapi/gen/models/gistingTrainingConfigurationBatchSize.ts +2 -0
- package/openapi/gen/models/gistingTrainingConfigurationGistTokens.ts +2 -0
- package/openapi/gen/models/gistingTrainingConfigurationInput.ts +6 -2
- package/openapi/gen/models/gistingTrainingConfigurationInputBatchSize.ts +2 -0
- package/openapi/gen/models/gistingTrainingConfigurationInputGistTokens.ts +2 -0
- package/openapi/gen/models/index.ts +28 -4
- package/openapi/gen/models/listDeliveryOrders200.ts +5 -0
- package/openapi/gen/models/sftTrainingConfiguration.ts +6 -3
- package/openapi/gen/models/sftTrainingConfigurationBatchSize.ts +2 -0
- package/openapi/gen/models/sftTrainingConfigurationInput.ts +6 -3
- package/openapi/gen/models/sftTrainingConfigurationInputBatchSize.ts +2 -0
- package/openapi/gen/models/sftTrainingConfigurationInputLoraRank.ts +2 -0
- package/openapi/gen/models/sftTrainingConfigurationLoraRank.ts +2 -0
- package/openapi/gen/models/trainingRunMetricHistory.ts +9 -0
- package/openapi/gen/models/trainingRunMetricHistoryMetric.ts +2 -0
- package/openapi/gen/models/trainingRunMetricHistoryPointsItem.ts +9 -0
- package/openapi/gen/models/trainingRunMetricHistoryPointsItemNonFinite.ts +2 -0
- package/openapi/gen/models/trainingRunMetricHistoryPointsItemValue.ts +2 -0
- package/openapi/gen/models/trainingRunMetrics.ts +9 -0
- package/openapi/gen/models/trainingRunMetricsMetricsItem.ts +8 -0
- package/openapi/gen/models/trainingRunMetricsMetricsItemLatest.ts +9 -0
- package/openapi/gen/models/trainingRunMetricsMetricsItemLatestNonFinite.ts +2 -0
- package/openapi/gen/models/trainingRunMetricsMetricsItemLatestValue.ts +2 -0
- package/openapi/gen/models/trainingRunMetricsMetricsItemMetric.ts +2 -0
- package/openapi/gen/models/trainingRunMetricsUnavailableReason.ts +2 -0
- package/package.json +2 -2
- package/skills/catalog.json +55 -7
- package/skills/toloka-build/SKILL.md +35 -21
- package/skills/toloka-build/references/node-semantics.md +13 -11
- package/skills/toloka-build/references/workflow-and-cli.md +31 -20
- package/skills/toloka-fine-tuning/SKILL.md +155 -0
- package/skills/toloka-get-started/SKILL.md +70 -0
- package/skills/toloka-gisting/SKILL.md +166 -0
- package/skills/toloka-report-issue/references/report-template.md +1 -1
- package/src/authoring/index.ts +14 -0
- package/src/checks/task-ui-typecheck.ts +12 -10
- package/src/cli/analytics.ts +88 -15
- package/src/cli/auth.ts +93 -21
- package/src/cli/auto-update.ts +36 -39
- package/src/cli/check.ts +88 -39
- package/src/cli/cli-entry.ts +3 -2
- package/src/cli/cli-error.ts +43 -0
- package/src/cli/commands/auth.ts +89 -0
- package/src/cli/commands/config.ts +28 -0
- package/src/cli/commands/dataset.ts +85 -0
- package/src/cli/commands/delivery-order.ts +18 -0
- package/src/cli/commands/models.ts +121 -0
- package/src/cli/commands/pipeline.ts +131 -0
- package/src/cli/commands/project.ts +168 -0
- package/src/cli/commands/report.ts +43 -0
- package/src/cli/commands/secret.ts +40 -0
- package/src/cli/commands/skill.ts +36 -0
- package/src/cli/commands/source.ts +204 -0
- package/src/cli/commands/status.ts +17 -0
- package/src/cli/commands/training.ts +261 -0
- package/src/cli/commands/version.ts +14 -0
- package/src/cli/config.ts +7 -8
- package/src/cli/context.ts +251 -0
- package/src/cli/credentials.ts +12 -4
- package/src/cli/data.ts +96 -34
- package/src/cli/delivery-orders.ts +47 -0
- package/src/cli/environment.ts +26 -0
- package/src/cli/file-system.ts +6 -2
- package/src/cli/format-error.ts +45 -2
- package/src/cli/init.ts +14 -11
- package/src/cli/models.ts +43 -12
- package/src/cli/program.ts +120 -1347
- package/src/cli/project-context.ts +126 -43
- package/src/cli/project.ts +80 -28
- package/src/cli/report.ts +24 -9
- package/src/cli/runs.ts +33 -8
- package/src/cli/sdk-version.ts +38 -0
- package/src/cli/settings.ts +13 -5
- package/src/cli/skill.ts +67 -24
- package/src/cli/source.ts +3 -2
- package/src/cli/terminal-io.ts +110 -0
- package/src/cli/trainings.ts +242 -57
- package/src/cli/version.ts +3 -3
- package/src/client.ts +21 -9
- package/src/importer.ts +32 -2
- package/src/transport.ts +13 -1
- package/openapi/gen/models/gistingTrainingConfigurationInputRegime.ts +0 -2
- package/openapi/gen/models/gistingTrainingConfigurationRegime.ts +0 -2
- package/openapi/gen/models/sftTrainingConfigurationInputRegime.ts +0 -2
- 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
|
|
122
|
+
export class FreshCheckProcessError extends TolokaCliError {
|
|
121
123
|
readonly phase: FreshCheckFailurePhase;
|
|
122
124
|
|
|
123
|
-
constructor(phase: FreshCheckFailurePhase, message: string, options
|
|
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
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
798
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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(
|
package/src/cli/cli-entry.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
+
};
|