@toloka-platform/sdk 0.1.0-preview.7 → 0.1.0-preview.9

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 (41) hide show
  1. package/README.md +3 -4
  2. package/dist/cli/toloka.js +133 -130
  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 +3 -5
  7. package/docs/cli.md +15 -0
  8. package/docs/limitations.md +1 -1
  9. package/examples/face-image-collection/nodes/order-faces/node.ts +2 -4
  10. package/examples/video-pair-rating/nodes/adjudication_match/node.ts +15 -17
  11. package/examples/video-pair-rating/nodes/assemble/node.ts +25 -27
  12. package/examples/video-pair-rating/nodes/cascade/node.ts +24 -26
  13. package/examples/video-pair-rating/nodes/finalize_overall/node.ts +17 -19
  14. package/examples/video-pair-rating/nodes/merge_adjudication/node.ts +13 -15
  15. package/examples/video-pair-rating/nodes/merge_op_adjudication/node.ts +12 -14
  16. package/examples/video-pair-rating/nodes/op_quorum_resolve/node.ts +13 -15
  17. package/examples/video-pair-rating/nodes/route_adjudication/node.ts +1 -3
  18. package/examples/video-pair-rating/nodes/route_op_adjudication/node.ts +5 -7
  19. package/examples/video-pair-rating/nodes/route_overall/node.ts +1 -3
  20. package/examples/video-pair-rating/nodes/shuffle_ab/node.ts +11 -13
  21. package/openapi/gen/models/datasetItem.ts +0 -2
  22. package/openapi/gen/models/index.ts +0 -1
  23. package/package.json +1 -1
  24. package/skills/catalog.json +4 -4
  25. package/skills/toloka-build/SKILL.md +1 -0
  26. package/skills/toloka-build/references/node-semantics.md +5 -5
  27. package/skills/toloka-build/references/workflow-and-cli.md +6 -1
  28. package/skills/toloka-report-issue/references/report-template.md +1 -1
  29. package/src/authoring/index.ts +27 -26
  30. package/src/checks/task-ui-typecheck.ts +1 -2
  31. package/src/cli/analytics.ts +8 -19
  32. package/src/cli/auto-update.ts +367 -0
  33. package/src/cli/boolean-flag.ts +13 -0
  34. package/src/cli/check.ts +3 -4
  35. package/src/cli/cli-entry.ts +41 -0
  36. package/src/cli/credentials.ts +3 -0
  37. package/src/cli/program.ts +37 -1
  38. package/src/cli/settings.ts +93 -0
  39. package/src/cli/skill.ts +4 -5
  40. package/src/importer.ts +12 -16
  41. package/openapi/gen/models/datasetItemDuplicateOfSubmissionId.ts +0 -2
@@ -19,6 +19,8 @@ import { authStatus, login, logout } from "./auth";
19
19
  import type { AuthPrompt } from "./auth";
20
20
  import { commandNameFromArgv, createCliAnalytics } from "./analytics";
21
21
  import type { CliAnalytics, CliAnalyticsCommandOutcome } from "./analytics";
22
+ import type { AutoUpdateHooks } from "./auto-update";
23
+ import { maybeAutoUpdate } from "./auto-update";
22
24
  import { PRODUCTION_BASE_URL, resolveInternalBaseUrl, resolveTolokaConfig } from "./config";
23
25
  import type { TolokaEnvironment } from "./config";
24
26
  import {
@@ -27,6 +29,7 @@ import {
27
29
  requireStoredCredentials,
28
30
  } from "./credentials";
29
31
  import type { StoredCredentials } from "./credentials";
32
+ import { getCliSetting, readCliSettings, setCliSetting } from "./settings";
30
33
  import {
31
34
  generateDatasetFieldDeclarations,
32
35
  listProjectDatasets,
@@ -269,7 +272,8 @@ export function createProgram(context: ProgramContext): CreatedProgram {
269
272
  commandNames.includes("help") ||
270
273
  commandNames.includes("report") ||
271
274
  commandNames.includes("url") ||
272
- commandNames.includes("version")
275
+ commandNames.includes("version") ||
276
+ commandNames.includes("config")
273
277
  )
274
278
  return;
275
279
  const sourceRoot = await resolveSourceRoot(globals(), context.io);
@@ -283,6 +287,29 @@ export function createProgram(context: ProgramContext): CreatedProgram {
283
287
  .description("print the current CLI version")
284
288
  .action(() => context.io.stdout(SDK_VERSION));
285
289
 
290
+ const config = program.command("config").description("read and write machine CLI settings");
291
+ config
292
+ .command("get")
293
+ .argument("[name]", "setting name; omit to print every setting")
294
+ .description("print a CLI setting, or every setting")
295
+ .action(async (name?: string) => {
296
+ const settings = await readCliSettings(context.environment);
297
+ context.io.stdout(getCliSetting(settings, name).output);
298
+ });
299
+ config
300
+ .command("set")
301
+ .argument("<name>", "setting name")
302
+ .argument("<value>", "setting value")
303
+ .description("write a CLI setting for this machine")
304
+ .action(async (name: string, value: string) => {
305
+ const result = await setCliSetting(context.environment, name, value);
306
+ context.io.stdout(result.output);
307
+ });
308
+ config.action(async () => {
309
+ const settings = await readCliSettings(context.environment);
310
+ context.io.stdout(getCliSetting(settings, undefined).output);
311
+ });
312
+
286
313
  program
287
314
  .command("login")
288
315
  .description("store an API key for this machine after verifying it")
@@ -1127,7 +1154,16 @@ export async function runTolokaCli(
1127
1154
  injectedClient?: PotatoClient,
1128
1155
  analytics?: CliAnalytics,
1129
1156
  runChecks?: typeof checkProject,
1157
+ autoUpdate?: AutoUpdateHooks,
1130
1158
  ): Promise<number> {
1159
+ const autoUpdateResult = await maybeAutoUpdate({
1160
+ argv,
1161
+ environment,
1162
+ io,
1163
+ ...(autoUpdate === undefined ? {} : { hooks: autoUpdate }),
1164
+ });
1165
+ if (autoUpdateResult.status === "reexec") return autoUpdateResult.exitCode;
1166
+
1131
1167
  const activeAnalytics =
1132
1168
  environment[CHECK_RESULT_FILE_ENV] === undefined
1133
1169
  ? (analytics ?? createCliAnalytics({ environment }))
@@ -0,0 +1,93 @@
1
+ import { chmod, readFile, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { isFalseyFlag, isTruthyFlag } from "./boolean-flag";
4
+ import type { CliEnvironment } from "./credentials";
5
+ import { configDirectory, preparePrivateFilePath } from "./credentials";
6
+
7
+ export const AUTO_UPDATE_SETTING = "auto-update";
8
+
9
+ export type CliSettings = {
10
+ readonly autoUpdate: boolean;
11
+ };
12
+
13
+ export const DEFAULT_CLI_SETTINGS: CliSettings = {
14
+ autoUpdate: true,
15
+ };
16
+
17
+ export class SettingsError extends Error {
18
+ constructor(message: string, options?: { readonly cause?: unknown }) {
19
+ super(message, options);
20
+ this.name = "SettingsError";
21
+ }
22
+ }
23
+
24
+ export const settingsPath = (environment: CliEnvironment = process.env): string =>
25
+ join(configDirectory(environment), "settings.json");
26
+
27
+ const asSettings = (payload: unknown): CliSettings => {
28
+ const autoUpdate =
29
+ typeof payload === "object" && payload !== null
30
+ ? Reflect.get(payload, "autoUpdate")
31
+ : undefined;
32
+ if (typeof autoUpdate !== "boolean") return DEFAULT_CLI_SETTINGS;
33
+ return { autoUpdate };
34
+ };
35
+
36
+ export async function readCliSettings(
37
+ environment: CliEnvironment = process.env,
38
+ ): Promise<CliSettings> {
39
+ try {
40
+ return asSettings(JSON.parse(await readFile(settingsPath(environment), "utf8")));
41
+ } catch {
42
+ return DEFAULT_CLI_SETTINGS;
43
+ }
44
+ }
45
+
46
+ export async function writeCliSettings(
47
+ settings: CliSettings,
48
+ environment: CliEnvironment = process.env,
49
+ ): Promise<string> {
50
+ const path = settingsPath(environment);
51
+ await preparePrivateFilePath(path);
52
+ await writeFile(path, `${JSON.stringify({ autoUpdate: settings.autoUpdate }, null, 2)}\n`, {
53
+ mode: 0o600,
54
+ });
55
+ await chmod(path, 0o600);
56
+ return path;
57
+ }
58
+
59
+ export const parseBooleanSetting = (value: string): boolean => {
60
+ if (isTruthyFlag(value)) return true;
61
+ if (isFalseyFlag(value)) return false;
62
+ throw new SettingsError(`Expected a boolean (true or false), got ${JSON.stringify(value)}.`);
63
+ };
64
+
65
+ export const formatCliSettings = (settings: CliSettings): string =>
66
+ `${AUTO_UPDATE_SETTING}=${settings.autoUpdate}`;
67
+
68
+ export const getCliSetting = (
69
+ settings: CliSettings,
70
+ name: string | undefined,
71
+ ): { readonly output: string } => {
72
+ if (name === undefined || name.length === 0) return { output: formatCliSettings(settings) };
73
+ if (name !== AUTO_UPDATE_SETTING) {
74
+ throw new SettingsError(`Unknown setting "${name}". Known settings: ${AUTO_UPDATE_SETTING}.`);
75
+ }
76
+ return { output: String(settings.autoUpdate) };
77
+ };
78
+
79
+ export async function setCliSetting(
80
+ environment: CliEnvironment,
81
+ name: string,
82
+ value: string,
83
+ ): Promise<{ readonly output: string; readonly settings: CliSettings }> {
84
+ if (name !== AUTO_UPDATE_SETTING) {
85
+ throw new SettingsError(`Unknown setting "${name}". Known settings: ${AUTO_UPDATE_SETTING}.`);
86
+ }
87
+ const settings = { autoUpdate: parseBooleanSetting(value) };
88
+ await writeCliSettings(settings, environment);
89
+ return {
90
+ settings,
91
+ output: `Set ${AUTO_UPDATE_SETTING} to ${settings.autoUpdate}.`,
92
+ };
93
+ }
package/src/cli/skill.ts CHANGED
@@ -2,11 +2,9 @@ import { assert } from "es-toolkit";
2
2
  import { createHash } from "node:crypto";
3
3
  import { copyFile, mkdir, mkdtemp, readFile, realpath, rename, rm, stat } from "node:fs/promises";
4
4
  import { dirname, join } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
5
  import { assertSafeDestination, entryStats, hasErrorCode } from "./file-system";
7
6
  import { formatCliError } from "./format-error";
8
7
 
9
- export const BUNDLED_SKILL_CATALOG_ROOT = fileURLToPath(new URL("../../skills", import.meta.url));
10
8
  export const SKILL_CATALOG_MANIFEST = "catalog.json";
11
9
  export const SKILL_INSTALL_ROOT = ".agents/skills";
12
10
  export const SKILL_ENTRY_POINT = "SKILL.md";
@@ -133,15 +131,16 @@ export async function resolveBundledSkillCatalogRoot(
133
131
  const runtimeRoot =
134
132
  entry === undefined ? undefined : await findSkillCatalogRootAbove(dirname(entry));
135
133
  if (runtimeRoot !== undefined) return runtimeRoot;
134
+ const workingDirectoryRoot = await findSkillCatalogRootAbove(process.cwd());
136
135
  const searched =
137
136
  entry === undefined
138
137
  ? "process.argv[1] names no runtime entry"
139
138
  : `none exists between ${entry} and the filesystem root`;
140
139
  assert(
141
- await isFileAt(join(BUNDLED_SKILL_CATALOG_ROOT, SKILL_CATALOG_MANIFEST)),
142
- `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 the source checkout catalog ${BUNDLED_SKILL_CATALOG_ROOT} is absent. Reinstall @toloka-platform/sdk.`,
140
+ workingDirectoryRoot !== undefined,
141
+ `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.`,
143
142
  );
144
- return BUNDLED_SKILL_CATALOG_ROOT;
143
+ return workingDirectoryRoot;
145
144
  }
146
145
 
147
146
  const loadBundledSkillCatalog = async (): Promise<SkillCatalog> =>
package/src/importer.ts CHANGED
@@ -220,7 +220,7 @@ function formatSource(value: unknown, depth = 0): string {
220
220
  return `{\n${properties}\n${indentation(depth)}}`;
221
221
  }
222
222
 
223
- function transformFieldProxy(
223
+ function transformFieldReference(
224
224
  reference: { id: string; optional: boolean },
225
225
  fieldReference: FieldReference,
226
226
  ): unknown {
@@ -276,10 +276,10 @@ function authoringSettings(node: GraphNode, fieldReference: FieldReference): unk
276
276
  return {
277
277
  ...settings,
278
278
  started: startedFieldProxies.output.map((reference) =>
279
- transformFieldProxy(reference, fieldReference),
279
+ transformFieldReference(reference, fieldReference),
280
280
  ),
281
281
  completed: completedFieldProxies.input.map((reference) =>
282
- transformFieldProxy(reference, fieldReference),
282
+ transformFieldReference(reference, fieldReference),
283
283
  ),
284
284
  };
285
285
  }
@@ -287,14 +287,12 @@ function authoringSettings(node: GraphNode, fieldReference: FieldReference): unk
287
287
  const { fieldProxies, ...settings } = node.settings;
288
288
  return {
289
289
  ...settings,
290
- fieldProxies: {
291
- input: fieldProxies.input.map((reference) =>
292
- transformFieldProxy(reference, fieldReference),
293
- ),
294
- output: fieldProxies.output.map((reference) =>
295
- transformFieldProxy(reference, fieldReference),
296
- ),
297
- },
290
+ input: fieldProxies.input.map((reference) =>
291
+ transformFieldReference(reference, fieldReference),
292
+ ),
293
+ output: fieldProxies.output.map((reference) =>
294
+ transformFieldReference(reference, fieldReference),
295
+ ),
298
296
  };
299
297
  }
300
298
  case "generation": {
@@ -322,11 +320,9 @@ function authoringSettings(node: GraphNode, fieldReference: FieldReference): unk
322
320
  field: fieldReference(fieldProxyId),
323
321
  })),
324
322
  })),
325
- fieldProxies: {
326
- input: fieldProxies.input.map((reference) =>
327
- transformFieldProxy(reference, fieldReference),
328
- ),
329
- },
323
+ input: fieldProxies.input.map((reference) =>
324
+ transformFieldReference(reference, fieldReference),
325
+ ),
330
326
  };
331
327
  }
332
328
  case "quorum": {
@@ -1,2 +0,0 @@
1
-
2
- export type DatasetItemDuplicateOfSubmissionId = string | null;