@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.
- package/README.md +5 -6
- package/dist/cli/toloka.js +136 -136
- 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/cli-v2.md +3 -2
- package/docs/cli.md +35 -7
- package/docs/getting-started.md +4 -2
- package/docs/limitations.md +2 -2
- package/openapi/gen/models/baseModel.ts +1 -0
- package/openapi/gen/models/currentUser.ts +2 -0
- package/openapi/gen/models/currentUserTeamsItem.ts +6 -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 +9 -4
- 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/package.json +2 -2
- package/skills/catalog.json +11 -11
- package/skills/toloka-build/SKILL.md +29 -18
- package/skills/toloka-build/references/node-semantics.md +7 -7
- package/skills/toloka-build/references/workflow-and-cli.md +15 -12
- package/skills/toloka-fine-tuning/SKILL.md +145 -62
- package/skills/toloka-get-started/SKILL.md +37 -21
- package/skills/toloka-gisting/SKILL.md +156 -66
- package/skills/toloka-report-issue/references/report-template.md +1 -1
- package/src/cli/analytics.ts +50 -23
- package/src/cli/auth.ts +31 -12
- package/src/cli/auto-update.ts +12 -6
- package/src/cli/check.ts +76 -29
- package/src/cli/cli-entry.ts +3 -2
- package/src/cli/cli-error.ts +43 -0
- package/src/cli/commands/dataset.ts +13 -3
- package/src/cli/commands/models.ts +6 -7
- package/src/cli/commands/pipeline.ts +6 -7
- package/src/cli/commands/project.ts +9 -16
- package/src/cli/commands/report.ts +6 -2
- package/src/cli/commands/secret.ts +5 -4
- package/src/cli/commands/source.ts +4 -4
- package/src/cli/commands/status.ts +2 -3
- package/src/cli/commands/training.ts +50 -17
- package/src/cli/config.ts +2 -2
- package/src/cli/context.ts +55 -17
- package/src/cli/credentials.ts +12 -4
- package/src/cli/data.ts +96 -34
- package/src/cli/delivery-orders.ts +18 -1
- package/src/cli/file-system.ts +6 -2
- package/src/cli/format-error.ts +45 -2
- package/src/cli/init.ts +13 -7
- package/src/cli/models.ts +43 -12
- package/src/cli/program.ts +5 -56
- package/src/cli/project-context.ts +117 -32
- package/src/cli/project.ts +80 -28
- package/src/cli/report.ts +24 -9
- package/src/cli/runs.ts +33 -8
- package/src/cli/settings.ts +13 -5
- package/src/cli/skill.ts +61 -22
- package/src/cli/source.ts +3 -2
- package/src/cli/terminal-io.ts +13 -6
- package/src/cli/trainings.ts +129 -35
- package/src/cli/version.ts +2 -2
- package/src/client.ts +3 -9
- 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/runs.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import select from "@inquirer/select";
|
|
2
|
-
import { assert } from "es-toolkit";
|
|
3
2
|
import type { Pipeline, PotatoClient } from "../client";
|
|
4
3
|
import { PotatoApiError } from "../errors";
|
|
5
4
|
import type { TolokaState } from "../state";
|
|
6
5
|
import type { RunItem, RunJob } from "../index";
|
|
6
|
+
import { assertCli } from "./cli-error";
|
|
7
7
|
|
|
8
8
|
export type PipelineRef = {
|
|
9
9
|
readonly pipelineId: string;
|
|
@@ -131,10 +131,18 @@ const uniqueMatch = (
|
|
|
131
131
|
ambiguous: string,
|
|
132
132
|
unknown: string,
|
|
133
133
|
): PipelineRef | undefined => {
|
|
134
|
-
|
|
134
|
+
assertCli(
|
|
135
|
+
matches.length <= 1,
|
|
136
|
+
ambiguous,
|
|
137
|
+
{ hint: { description: "Pass the pipeline id shown by toloka pipeline list instead of the name." } },
|
|
138
|
+
);
|
|
135
139
|
if (matches.length !== 1) return undefined;
|
|
136
140
|
const [ref] = matches;
|
|
137
|
-
|
|
141
|
+
assertCli(
|
|
142
|
+
ref,
|
|
143
|
+
unknown,
|
|
144
|
+
{ hint: { description: "Run toloka pipeline list to see the pipelines of this project." } },
|
|
145
|
+
);
|
|
138
146
|
return ref;
|
|
139
147
|
};
|
|
140
148
|
|
|
@@ -174,7 +182,11 @@ const resolveToken = (token: string, live: readonly PipelineRef[]): PipelineRef
|
|
|
174
182
|
);
|
|
175
183
|
if (basename !== undefined) return basename;
|
|
176
184
|
|
|
177
|
-
|
|
185
|
+
assertCli(
|
|
186
|
+
false,
|
|
187
|
+
unknown,
|
|
188
|
+
{ hint: { description: "Run toloka pipeline list to see the pipelines of this project." } },
|
|
189
|
+
);
|
|
178
190
|
};
|
|
179
191
|
|
|
180
192
|
export async function resolvePipelineRef(options: {
|
|
@@ -188,7 +200,11 @@ export async function resolvePipelineRef(options: {
|
|
|
188
200
|
|
|
189
201
|
if (options.pipeline !== undefined) return resolveToken(options.pipeline, live);
|
|
190
202
|
|
|
191
|
-
|
|
203
|
+
assertCli(
|
|
204
|
+
live.length > 0,
|
|
205
|
+
"This project has no pipelines.",
|
|
206
|
+
{ hint: { description: "Run toloka apply to create the pipelines declared in source." } },
|
|
207
|
+
);
|
|
192
208
|
|
|
193
209
|
if (live.length > 1 && options.select !== undefined) {
|
|
194
210
|
const chosen = await options.select(
|
|
@@ -199,13 +215,22 @@ export async function resolvePipelineRef(options: {
|
|
|
199
215
|
|
|
200
216
|
if (live.length === 1) {
|
|
201
217
|
const [only] = live;
|
|
202
|
-
|
|
203
|
-
|
|
218
|
+
assertCli(
|
|
219
|
+
only,
|
|
220
|
+
"This project has no pipelines.",
|
|
221
|
+
{ hint: { description: "Run toloka apply to create the pipelines declared in source." } },
|
|
222
|
+
);
|
|
223
|
+
assertCli(
|
|
224
|
+
false,
|
|
225
|
+
`Pass a pipeline name. This project has: ${formatPipelineChoice(only)}.`,
|
|
226
|
+
{ hint: { disabled: true } },
|
|
227
|
+
);
|
|
204
228
|
}
|
|
205
229
|
|
|
206
|
-
|
|
230
|
+
assertCli(
|
|
207
231
|
false,
|
|
208
232
|
`This project tracks several pipelines, so name one: ${live.map(formatPipelineChoice).join(", ")}.`,
|
|
233
|
+
{ hint: { disabled: true } },
|
|
209
234
|
);
|
|
210
235
|
}
|
|
211
236
|
|
package/src/cli/settings.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { join } from "node:path";
|
|
|
3
3
|
import { isFalseyFlag, isTruthyFlag } from "./boolean-flag";
|
|
4
4
|
import type { CliEnvironment } from "./credentials";
|
|
5
5
|
import { configDirectory, preparePrivateFilePath } from "./credentials";
|
|
6
|
+
import { TolokaCliError } from "./cli-error";
|
|
7
|
+
import type { TolokaCliErrorOptions } from "./cli-error";
|
|
6
8
|
|
|
7
9
|
export const AUTO_UPDATE_SETTING = "auto-update";
|
|
8
10
|
|
|
@@ -14,8 +16,8 @@ export const DEFAULT_CLI_SETTINGS: CliSettings = {
|
|
|
14
16
|
autoUpdate: true,
|
|
15
17
|
};
|
|
16
18
|
|
|
17
|
-
export class SettingsError extends
|
|
18
|
-
constructor(message: string, options
|
|
19
|
+
export class SettingsError extends TolokaCliError {
|
|
20
|
+
constructor(message: string, options: TolokaCliErrorOptions) {
|
|
19
21
|
super(message, options);
|
|
20
22
|
this.name = "SettingsError";
|
|
21
23
|
}
|
|
@@ -59,7 +61,9 @@ export async function writeCliSettings(
|
|
|
59
61
|
export const parseBooleanSetting = (value: string): boolean => {
|
|
60
62
|
if (isTruthyFlag(value)) return true;
|
|
61
63
|
if (isFalseyFlag(value)) return false;
|
|
62
|
-
throw new SettingsError(`Expected a boolean (true or false), got ${JSON.stringify(value)}
|
|
64
|
+
throw new SettingsError(`Expected a boolean (true or false), got ${JSON.stringify(value)}.`, {
|
|
65
|
+
hint: { description: "Run toloka config set auto-update true or false." },
|
|
66
|
+
});
|
|
63
67
|
};
|
|
64
68
|
|
|
65
69
|
export const formatCliSettings = (settings: CliSettings): string =>
|
|
@@ -71,7 +75,9 @@ export const getCliSetting = (
|
|
|
71
75
|
): { readonly output: string } => {
|
|
72
76
|
if (name === undefined || name.length === 0) return { output: formatCliSettings(settings) };
|
|
73
77
|
if (name !== AUTO_UPDATE_SETTING) {
|
|
74
|
-
throw new SettingsError(`Unknown setting "${name}". Known settings: ${AUTO_UPDATE_SETTING}
|
|
78
|
+
throw new SettingsError(`Unknown setting "${name}". Known settings: ${AUTO_UPDATE_SETTING}.`, {
|
|
79
|
+
hint: { disabled: true },
|
|
80
|
+
});
|
|
75
81
|
}
|
|
76
82
|
return { output: String(settings.autoUpdate) };
|
|
77
83
|
};
|
|
@@ -82,7 +88,9 @@ export async function setCliSetting(
|
|
|
82
88
|
value: string,
|
|
83
89
|
): Promise<{ readonly output: string; readonly settings: CliSettings }> {
|
|
84
90
|
if (name !== AUTO_UPDATE_SETTING) {
|
|
85
|
-
throw new SettingsError(`Unknown setting "${name}". Known settings: ${AUTO_UPDATE_SETTING}
|
|
91
|
+
throw new SettingsError(`Unknown setting "${name}". Known settings: ${AUTO_UPDATE_SETTING}.`, {
|
|
92
|
+
hint: { disabled: true },
|
|
93
|
+
});
|
|
86
94
|
}
|
|
87
95
|
const settings = { autoUpdate: parseBooleanSetting(value) };
|
|
88
96
|
await writeCliSettings(settings, environment);
|
package/src/cli/skill.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { assert } from "es-toolkit";
|
|
2
1
|
import { createHash } from "node:crypto";
|
|
3
2
|
import { copyFile, mkdir, mkdtemp, readFile, realpath, rename, rm, stat } from "node:fs/promises";
|
|
4
3
|
import { dirname, join } from "node:path";
|
|
5
4
|
import { assertSafeDestination, entryStats, hasErrorCode } from "./file-system";
|
|
6
5
|
import { formatCliError } from "./format-error";
|
|
7
6
|
import { releaseChannelFor, SDK_PACKAGE_NAME, SDK_VERSION } from "./sdk-version";
|
|
7
|
+
import { assertCli, REINSTALL_HINT, TolokaCliError } from "./cli-error";
|
|
8
8
|
|
|
9
9
|
export const SKILL_CATALOG_MANIFEST = "catalog.json";
|
|
10
10
|
export const SKILL_INSTALL_ROOT = ".agents/skills";
|
|
@@ -76,31 +76,51 @@ const isSafeRelativePath = (path: string) =>
|
|
|
76
76
|
path.split("/").every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
|
|
77
77
|
|
|
78
78
|
const parseDescriptor = (value: unknown, invalid: (detail: string) => string): SkillDescriptor => {
|
|
79
|
-
|
|
79
|
+
assertCli(isRecord(value), invalid("every skill must be an object."), { hint: REINSTALL_HINT });
|
|
80
80
|
const { id, name, description, tags, version, digest, files } = value;
|
|
81
|
-
|
|
81
|
+
assertCli(
|
|
82
82
|
isSafeSkillId(id),
|
|
83
83
|
invalid(
|
|
84
84
|
`skill id ${JSON.stringify(id)} must be at most ${MAX_SKILL_ID_LENGTH} lowercase letters, digits, and single hyphens.`,
|
|
85
85
|
),
|
|
86
|
+
{ hint: REINSTALL_HINT },
|
|
86
87
|
);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
assertCli(isNonEmptyString(name), invalid(`skill ${id} needs a name.`), { hint: REINSTALL_HINT });
|
|
89
|
+
assertCli(
|
|
90
|
+
isNonEmptyString(description),
|
|
91
|
+
invalid(`skill ${id} needs a description.`),
|
|
92
|
+
{ hint: REINSTALL_HINT },
|
|
93
|
+
);
|
|
94
|
+
assertCli(
|
|
95
|
+
isStringArray(tags),
|
|
96
|
+
invalid(`skill ${id} tags must be strings.`),
|
|
97
|
+
{ hint: REINSTALL_HINT },
|
|
98
|
+
);
|
|
99
|
+
assertCli(
|
|
100
|
+
isNonEmptyString(version),
|
|
101
|
+
invalid(`skill ${id} needs a version.`),
|
|
102
|
+
{ hint: REINSTALL_HINT },
|
|
103
|
+
);
|
|
104
|
+
assertCli(
|
|
92
105
|
typeof digest === "string" && DIGEST_PATTERN.test(digest),
|
|
93
106
|
invalid(`skill ${id} digest must be the sha256 hex digest of its files.`),
|
|
107
|
+
{ hint: REINSTALL_HINT },
|
|
94
108
|
);
|
|
95
|
-
|
|
109
|
+
assertCli(
|
|
96
110
|
isStringArray(files) && files.includes(SKILL_ENTRY_POINT),
|
|
97
111
|
invalid(`skill ${id} files must include ${SKILL_ENTRY_POINT}.`),
|
|
112
|
+
{ hint: REINSTALL_HINT },
|
|
98
113
|
);
|
|
99
|
-
|
|
114
|
+
assertCli(
|
|
100
115
|
files.every(isSafeRelativePath),
|
|
101
116
|
invalid(`skill ${id} files must be relative paths inside its directory.`),
|
|
117
|
+
{ hint: REINSTALL_HINT },
|
|
118
|
+
);
|
|
119
|
+
assertCli(
|
|
120
|
+
new Set(files).size === files.length,
|
|
121
|
+
invalid(`skill ${id} lists a file more than once.`),
|
|
122
|
+
{ hint: REINSTALL_HINT },
|
|
102
123
|
);
|
|
103
|
-
assert(new Set(files).size === files.length, invalid(`skill ${id} lists a file more than once.`));
|
|
104
124
|
return { id, name, description, tags, version, digest, files };
|
|
105
125
|
};
|
|
106
126
|
|
|
@@ -108,7 +128,10 @@ const parseManifest = (source: string, manifestPath: string): unknown => {
|
|
|
108
128
|
try {
|
|
109
129
|
return JSON.parse(source);
|
|
110
130
|
} catch (error) {
|
|
111
|
-
throw new
|
|
131
|
+
throw new TolokaCliError(`Invalid skill catalog ${manifestPath}: ${formatCliError(error)}`, {
|
|
132
|
+
cause: error,
|
|
133
|
+
hint: REINSTALL_HINT,
|
|
134
|
+
});
|
|
112
135
|
}
|
|
113
136
|
};
|
|
114
137
|
|
|
@@ -140,9 +163,10 @@ export async function resolveBundledSkillCatalogRoot(
|
|
|
140
163
|
entry === undefined
|
|
141
164
|
? "process.argv[1] names no runtime entry"
|
|
142
165
|
: `none exists between ${entry} and the filesystem root`;
|
|
143
|
-
|
|
166
|
+
assertCli(
|
|
144
167
|
workingDirectoryRoot !== undefined,
|
|
145
168
|
`Cannot locate the bundled skill catalog. An installed @toloka-platform/sdk must ship ${SKILL_CATALOG_DIRECTORY}/${SKILL_CATALOG_MANIFEST} in a directory above its runtime entry, but ${searched}, and no ${SKILL_CATALOG_DIRECTORY}/${SKILL_CATALOG_MANIFEST} exists above ${process.cwd()} either. Reinstall @toloka-platform/sdk.`,
|
|
169
|
+
{ hint: { disabled: true } },
|
|
146
170
|
);
|
|
147
171
|
return workingDirectoryRoot;
|
|
148
172
|
}
|
|
@@ -154,23 +178,30 @@ export async function loadSkillCatalog(catalogRoot: string): Promise<SkillCatalo
|
|
|
154
178
|
const manifestPath = join(catalogRoot, SKILL_CATALOG_MANIFEST);
|
|
155
179
|
const invalid = (detail: string) => `Invalid skill catalog ${manifestPath}: ${detail}`;
|
|
156
180
|
const { size } = await stat(manifestPath);
|
|
157
|
-
|
|
181
|
+
assertCli(
|
|
158
182
|
size <= MAX_MANIFEST_BYTES,
|
|
159
183
|
invalid(`the manifest is ${size} bytes; the limit is ${MAX_MANIFEST_BYTES} bytes.`),
|
|
184
|
+
{ hint: REINSTALL_HINT },
|
|
160
185
|
);
|
|
161
186
|
const manifest = parseManifest(await readFile(manifestPath, "utf8"), manifestPath);
|
|
162
|
-
|
|
187
|
+
assertCli(
|
|
163
188
|
isRecord(manifest) && Array.isArray(manifest.skills),
|
|
164
189
|
invalid("expected an object with a skills array."),
|
|
190
|
+
{ hint: REINSTALL_HINT },
|
|
165
191
|
);
|
|
166
|
-
|
|
192
|
+
assertCli(
|
|
167
193
|
manifest.skills.length <= MAX_SKILL_COUNT,
|
|
168
194
|
invalid(`${manifest.skills.length} skills exceed the limit of ${MAX_SKILL_COUNT}.`),
|
|
195
|
+
{ hint: REINSTALL_HINT },
|
|
169
196
|
);
|
|
170
197
|
const skills = manifest.skills.map((skill) => parseDescriptor(skill, invalid));
|
|
171
198
|
const ids = new Set<string>();
|
|
172
199
|
for (const skill of skills) {
|
|
173
|
-
|
|
200
|
+
assertCli(
|
|
201
|
+
!ids.has(skill.id),
|
|
202
|
+
invalid(`skill id ${skill.id} appears more than once.`),
|
|
203
|
+
{ hint: REINSTALL_HINT },
|
|
204
|
+
);
|
|
174
205
|
ids.add(skill.id);
|
|
175
206
|
}
|
|
176
207
|
return { root: catalogRoot, manifestPath, skills };
|
|
@@ -253,9 +284,14 @@ const swapDirectories = async (
|
|
|
253
284
|
try {
|
|
254
285
|
await renameEntry(backup, destination);
|
|
255
286
|
} catch (restoreError) {
|
|
256
|
-
throw new
|
|
287
|
+
throw new TolokaCliError(
|
|
257
288
|
`Failed to replace ${destination}; the previous version is preserved at ${backup}. ${formatCliError(restoreError)}`,
|
|
258
|
-
{
|
|
289
|
+
{
|
|
290
|
+
cause: error,
|
|
291
|
+
hint: {
|
|
292
|
+
description: `Move ${backup} back to ${destination} by hand, then re-run toloka skill install <skill-id> --force.`,
|
|
293
|
+
},
|
|
294
|
+
},
|
|
259
295
|
);
|
|
260
296
|
}
|
|
261
297
|
throw error;
|
|
@@ -271,17 +307,19 @@ export async function installSkill({
|
|
|
271
307
|
rename: renameEntry = rename,
|
|
272
308
|
}: InstallSkillOptions): Promise<InstalledSkill> {
|
|
273
309
|
const skill = catalog.skills.find((candidate) => candidate.id === skillId);
|
|
274
|
-
|
|
310
|
+
assertCli(
|
|
275
311
|
skill,
|
|
276
312
|
`Skill ${JSON.stringify(skillId)} is not in the catalog bundled with ${SDK_PACKAGE_NAME} ${SDK_VERSION}. Run \`toloka skill list\` to see the available skills; a newer release may ship it (${UPGRADE_HINT}).`,
|
|
313
|
+
{ hint: { disabled: true } },
|
|
277
314
|
);
|
|
278
315
|
const relativeDestination = `${SKILL_INSTALL_ROOT}/${skill.id}`;
|
|
279
316
|
await assertSafeDestination(sourceRoot, relativeDestination);
|
|
280
317
|
const destination = join(sourceRoot, relativeDestination);
|
|
281
318
|
const existing = await entryStats(destination);
|
|
282
|
-
|
|
319
|
+
assertCli(
|
|
283
320
|
existing === undefined || force,
|
|
284
321
|
`${relativeDestination} already exists. Re-run with --force to replace it with the catalog version of ${skill.id}.`,
|
|
322
|
+
{ hint: { disabled: true } },
|
|
285
323
|
);
|
|
286
324
|
|
|
287
325
|
const skillsRoot = dirname(destination);
|
|
@@ -293,9 +331,10 @@ export async function installSkill({
|
|
|
293
331
|
await mkdir(dirname(target), { recursive: true });
|
|
294
332
|
await copyFile(join(catalog.root, skill.id, path), target);
|
|
295
333
|
}
|
|
296
|
-
|
|
334
|
+
assertCli(
|
|
297
335
|
(await computeSkillDigest(temporary, skill.files)) === skill.digest,
|
|
298
336
|
`Skill ${skill.id} failed its digest check: the packaged files do not match the catalog digest ${skill.digest}. Nothing was installed.`,
|
|
337
|
+
{ hint: REINSTALL_HINT },
|
|
299
338
|
);
|
|
300
339
|
if (existing === undefined) {
|
|
301
340
|
await renameEntry(temporary, destination);
|
package/src/cli/source.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { assert } from "es-toolkit";
|
|
2
1
|
import { readdir, stat } from "node:fs/promises";
|
|
3
2
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
4
3
|
import { pathToFileURL } from "node:url";
|
|
5
4
|
import { validatePipelineDefinition } from "../authoring";
|
|
6
5
|
import type { PipelineDefinition } from "../authoring";
|
|
6
|
+
import { assertCli } from "./cli-error";
|
|
7
7
|
|
|
8
8
|
export type ProjectPipelineSource = {
|
|
9
9
|
readonly sourceDirectory: string;
|
|
@@ -64,9 +64,10 @@ export async function loadProjectSource(sourceRoot: string): Promise<ProjectSour
|
|
|
64
64
|
const moduleUrl = pathToFileURL(modulePath);
|
|
65
65
|
moduleUrl.searchParams.set("version", `${moduleStat.mtimeMs}-${moduleStat.size}`);
|
|
66
66
|
const module: unknown = await import(moduleUrl.href);
|
|
67
|
-
|
|
67
|
+
assertCli(
|
|
68
68
|
typeof module === "object" && module !== null,
|
|
69
69
|
`Invalid ${modulePath}: expected a module`,
|
|
70
|
+
{ hint: { description: "Make the file a TypeScript module that exports the pipeline definition." } },
|
|
70
71
|
);
|
|
71
72
|
const definition: unknown = Reflect.get(module, "default");
|
|
72
73
|
validatePipelineDefinition(definition);
|
package/src/cli/terminal-io.ts
CHANGED
|
@@ -2,6 +2,15 @@ import { createInterface } from "node:readline/promises";
|
|
|
2
2
|
import type { AuthPrompt } from "./auth";
|
|
3
3
|
import type { CliIo } from "./context";
|
|
4
4
|
import { pickPipeline } from "./runs";
|
|
5
|
+
import { TolokaCliError } from "./cli-error";
|
|
6
|
+
|
|
7
|
+
const stdinClosedError = () =>
|
|
8
|
+
new TolokaCliError("Standard input closed before an API key was entered.", {
|
|
9
|
+
hint: {
|
|
10
|
+
description:
|
|
11
|
+
"Run toloka login in an interactive terminal, or pipe the key with toloka login --api-key-stdin.",
|
|
12
|
+
},
|
|
13
|
+
});
|
|
5
14
|
|
|
6
15
|
const readStandardInput = async (): Promise<string> => {
|
|
7
16
|
process.stdin.setEncoding("utf8");
|
|
@@ -35,7 +44,7 @@ const readMaskedLine = async (question: string): Promise<string> => {
|
|
|
35
44
|
|
|
36
45
|
const onEnd = () => {
|
|
37
46
|
finish();
|
|
38
|
-
reject(
|
|
47
|
+
reject(stdinClosedError());
|
|
39
48
|
};
|
|
40
49
|
|
|
41
50
|
const onError = (error: Error) => {
|
|
@@ -53,11 +62,9 @@ const readMaskedLine = async (question: string): Promise<string> => {
|
|
|
53
62
|
if (character === "\u0003" || character === "\u0004") {
|
|
54
63
|
finish();
|
|
55
64
|
reject(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
: "Standard input closed before an API key was entered.",
|
|
60
|
-
),
|
|
65
|
+
character === "\u0003"
|
|
66
|
+
? new TolokaCliError("Canceled.", { hint: { disabled: true } })
|
|
67
|
+
: stdinClosedError(),
|
|
61
68
|
);
|
|
62
69
|
return;
|
|
63
70
|
}
|