@toloka-platform/sdk 0.2.0-preview.3 → 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/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 +32 -4
- 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 +1 -1
- package/skills/catalog.json +10 -10
- package/skills/toloka-build/SKILL.md +28 -17
- package/skills/toloka-build/references/node-semantics.md +7 -7
- package/skills/toloka-build/references/workflow-and-cli.md +13 -10
- package/skills/toloka-fine-tuning/SKILL.md +145 -62
- package/skills/toloka-get-started/SKILL.md +35 -20
- package/skills/toloka-gisting/SKILL.md +156 -66
- 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 +4 -55
- 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
|
@@ -5,72 +5,162 @@ description: Create, estimate, start, monitor, and verify a project-owned prompt
|
|
|
5
5
|
|
|
6
6
|
# Compress a prompt with gisting
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
## Mission
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Help the user learn a compact representation of their static prompt from representative example conversations while preserving the exact prompt and their model, data, quality, latency, and budget constraints. Match the work to the requested outcome: inspect an existing Training or run, prepare a draft, calculate an estimate, start an approved run, monitor it, or retrieve its output. Do not create resources or start a run merely because they appear later in this reference.
|
|
11
|
+
|
|
12
|
+
For first-time setup details, read `https://platform.toloka.ai/agents/gisting.md`.
|
|
13
|
+
|
|
14
|
+
## Domain model
|
|
15
|
+
|
|
16
|
+
- **Prompt gisting** learns soft prompt tokens that stand in for a static instruction prompt. Training examples should contain the dynamic inputs and desired assistant responses; do not repeat the static prompt in every user message.
|
|
17
|
+
- A **Training** is an editable project-owned draft. A **Training Run** is one immutable, billable execution of a frozen draft, prompt, and Dataset selections.
|
|
18
|
+
- A run can start from a catalog **Base Model** or an exact existing **Model Version**. An explicit Model Version is the source; do not also select a Base Model.
|
|
19
|
+
- A completed run publishes a durable Model Version. It does not prove deployment or inference readiness.
|
|
20
|
+
- Training and validation are data roles, not file-layout rules. Separate Datasets and disjoint top-level filters are both valid. Without a validation selection, the provider reserves 10% of the training rows as a holdout.
|
|
21
|
+
|
|
22
|
+
## Working posture
|
|
23
|
+
|
|
24
|
+
- Inspect the user's request and current CLI, project, Training, Dataset, prompt, and run state before changing anything. The process below is a dependency map: skip satisfied or irrelevant work and stop when the requested outcome is complete.
|
|
25
|
+
- Honor explicit choices first. If a requested project, source, Dataset, prompt, or configuration is incompatible, explain the evidence and tradeoff before proposing a substitute.
|
|
26
|
+
- Make routine reversible choices and briefly explain them. Ask a focused question only for material ambiguity, missing authorization or inputs, conflicting constraints, or destructive or out-of-scope changes.
|
|
27
|
+
- If the goal is weight adaptation from labeled conversations or data labeling rather than prompt compression, explain the distinction and route to `toloka-fine-tuning` or `toloka-build`; never silently change the method.
|
|
28
|
+
|
|
29
|
+
## Safety
|
|
11
30
|
|
|
12
31
|
- Never ask for an API key in chat; the user enters it through `toloka login`.
|
|
13
|
-
- Never invent project, Dataset, Base Model, Training, Model, Model Version,
|
|
14
|
-
- Use authorized project Datasets containing representative examples. Keep production data out of source control.
|
|
15
|
-
- The prompt file must be non-empty UTF-8 text no larger than 256 KiB.
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
32
|
+
- Never invent project, Dataset, Base Model, Training, Model, Model Version, prompt, price, or run IDs. Read identity and source metadata from the CLI.
|
|
33
|
+
- Use only authorized project Datasets containing representative examples. Keep credentials and production data out of source control and agent messages.
|
|
34
|
+
- The prompt file must be non-empty UTF-8 text no larger than 256 KiB. Preserve user-provided prompt text exactly unless they ask to edit it; when the prompt is supplied in chat, materialize that exact text instead of inventing missing intent.
|
|
35
|
+
- Preserve train/validation separation and avoid duplicate or near-duplicate examples across roles.
|
|
36
|
+
- Never start a Training Run before the user approves the ready estimate's exact `maximumCharge`.
|
|
37
|
+
- Keep polling bounded. Report terminal state only when the CLI shows it, and report timeouts as still running.
|
|
38
|
+
|
|
39
|
+
## Process
|
|
40
|
+
|
|
41
|
+
### 1. Establish only the context the goal needs
|
|
42
|
+
|
|
43
|
+
When runtime, authentication, or binding is unknown, inspect it:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
bun --version
|
|
47
|
+
toloka --version
|
|
48
|
+
toloka whoami
|
|
49
|
+
toloka project current
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Reuse an authenticated, compatible workspace. For an inspection request, inspect existing resources rather than creating a project, Dataset, Training, prompt file, or run.
|
|
53
|
+
|
|
54
|
+
Use the project the user explicitly requested. Otherwise keep the current binding when it is consistent with the task. If no suitable project is bound and the requested work needs one, inspect `toloka project list --limit 20`, then create a task-named project or register the exact existing project the user identified. Do not silently select an unrelated project or rebind an existing checkout; use an explicit `--project-id` or a separate source root when working against another project without changing the binding.
|
|
55
|
+
|
|
56
|
+
### 2. Inspect and assign data roles
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
toloka dataset list
|
|
60
|
+
toloka dataset fields <dataset-name-or-id>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Use the user's authorized data and existing role assignments. If local JSONL files must be uploaded, organize Datasets by training and validation role, not automatically by file. `dataset upload` appends, so compatible files for one role can share a Dataset:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
toloka dataset create <role-dataset-name>
|
|
67
|
+
toloka dataset upload <role-dataset-name> <absolute-jsonl-path>
|
|
68
|
+
toloka dataset fields <role-dataset-name>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
CLI uploads to an otherwise undeclared Dataset store each row whole. A nested `split` value in that uploaded object is not a top-level Dataset field and cannot be used by `--train-filter` or `--validation-filter`. In that case, use separate role Datasets or omit validation flags and use the provider's 10% holdout. Existing Datasets with a real top-level split field can use disjoint filters. Never use overlapping selections or mix a supplied validation set back into training.
|
|
72
|
+
|
|
73
|
+
Check that the examples demonstrate the desired behavior under the prompt: dynamic input in user messages, target output in the final assistant message, representative edge cases, and no prompt or answer leakage across validation.
|
|
74
|
+
|
|
75
|
+
### 3. Preserve the prompt and choose the source
|
|
76
|
+
|
|
77
|
+
Confirm the prompt path contains non-empty UTF-8 text within 256 KiB. Use the supplied file as-is. If the user supplied prompt text in the request instead, write that exact text to a UTF-8 file. Ask only when the required prompt or its intended boundaries are genuinely missing; do not author a replacement prompt from assumptions.
|
|
78
|
+
|
|
79
|
+
Explicit source choices take precedence. An existing Model Version bypasses Base Model selection.
|
|
80
|
+
|
|
81
|
+
Gisting supports only `qwen3-4b` and `qwen3-8b` because it publishes full model weights. This restriction also applies to the underlying Base Model of an existing Model Version. Unsupported tiers are rejected when creating or updating a Training draft; explain the constraint rather than silently substituting a model.
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
toloka base-model list --json
|
|
85
|
+
toloka base-model get <base-model-id> --json
|
|
86
|
+
toloka model list --json
|
|
87
|
+
toloka model get <model-id> --json
|
|
88
|
+
toloka model versions <model-id> --json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
For a Base Model, compare available evidence about prompt and language fit, prior quality, data volume, desired compression fidelity, training budget, and serving latency or deployment constraints. Model size alone is not a quality decision; a smaller model is a useful cost and latency baseline when the task permits it, not a mandatory default. Verify the selected catalog ID, upstream model, and pinned revision with `base-model get`, and consult upstream documentation or user-provided evidence for capabilities the catalog does not expose.
|
|
92
|
+
|
|
93
|
+
For a Model Version, verify the exact ID and revision under its project-owned Model, and record the owning Model's Base Model and the Version's runtime base from JSON. If those identities conflict, report the conflict rather than guessing or substituting. Use `--model-version` only for that existing version. The ready cost estimate is the concrete final check that the source type, underlying Base Model, method, prompt, and data are supported together.
|
|
94
|
+
|
|
95
|
+
### 4. Reuse or create the draft
|
|
96
|
+
|
|
97
|
+
Inspect before creating another Training:
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
toloka training list --json
|
|
101
|
+
toloka training get <training-id> --json
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
When a new draft is needed, use a descriptive name and create normalized gisting configuration:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
toloka training create "<training-name>" \
|
|
108
|
+
--method gisting \
|
|
109
|
+
--prompt-file <absolute-prompt-path> \
|
|
110
|
+
--base-model <base-model-id> \
|
|
111
|
+
--dataset <training-dataset-id> \
|
|
112
|
+
--validation-dataset <validation-dataset-id> \
|
|
113
|
+
--json
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Omit `--validation-dataset` when no separate validation selection exists so the provider uses its 10% holdout. For disjoint selections in a Dataset with top-level fields, add `--train-filter <field>=<value>` and `--validation-filter <field>=<value>`. For an existing Model Version source, replace `--base-model` with `--model-version <model-version-id>`.
|
|
117
|
+
|
|
118
|
+
Preserve requested hyperparameters. The current CLI uses `--gist-tokens 8|32|64` (default 32), `--epochs 1..5` (default 3), `--learning-rate` within 0.001–0.3 (default 0.03), `--batch-size 8|16|32|64` (default 8), and `--max-sequence-length 64..16384` (default 3072), rather than `regime`. Use defaults when suitable; 8 gist tokens favor compression for simple prompts, while 64 may help when 32 loses holdout accuracy. Choose sequence length from actual examples within the supported limit. `--lora-rank` is rejected for gisting. `training update <name-or-id>` takes the same flags except `--method`; omitted flags retain current values.
|
|
119
|
+
|
|
120
|
+
Verify the saved method, exact source, prompt file, selections, filters, and configuration with `toloka training get <training-id> --json`.
|
|
121
|
+
|
|
122
|
+
### 5. Validate and estimate
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
toloka training cost <training-id> --json > <absolute-estimate-file>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
A blocked estimate exits 1, leaves the redirected file empty, and prints `Training cannot run with these inputs:` followed by one `- <message>` line per issue on stderr. Report every issue, correct the cause, and calculate a new estimate.
|
|
129
|
+
|
|
130
|
+
For a duration issue, use the reported rows, tokens, epochs, GPU count, and wall time to compare supported options. Gisting publishes full model weights, so only `qwen3-4b` and `qwen3-8b` are supported; switching to the larger 8-GPU tiers is not a valid workaround. Reducing rows can weaken coverage and reducing epochs can weaken compression quality. Do not silently change the requested model, discard data, or alter epochs merely to make the estimate pass. Explain the tradeoff, obtain direction when material, update the draft, re-estimate, and obtain approval for the new quote.
|
|
131
|
+
|
|
132
|
+
For a ready estimate, report the source, prompt, and selections, training and validation row counts, token totals, maximum GPU seconds, `maximumCharge`, currency, and fingerprint. If the user asked only for an estimate, stop here.
|
|
133
|
+
|
|
134
|
+
### 6. Run only after explicit cost approval
|
|
135
|
+
|
|
136
|
+
Ask the user to approve the exact ready `maximumCharge`. After approval, start only the requested run and pass the approved file so a new unseen quote cannot replace it:
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
toloka training run <training-id> \
|
|
140
|
+
--estimate-file <absolute-estimate-file> \
|
|
141
|
+
--version-name <unique-version-name> \
|
|
142
|
+
--json
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Both flags are optional at the CLI level. Without `--estimate-file`, `training run` calculates a fresh ready estimate; do not use that path when the user approved a saved quote. Without `--version-name`, the Model Version takes the Training name.
|
|
146
|
+
|
|
147
|
+
Monitor only as far as the user requested, with bounded commands:
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
toloka training status <training-run-id> --json
|
|
151
|
+
toloka training logs <training-run-id>
|
|
152
|
+
toloka training metrics <training-run-id> --json
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Resume only after the user confirms a billing block is resolved. Cancel only on explicit request.
|
|
156
|
+
|
|
157
|
+
For a completed run, verify the observed output and download only when requested:
|
|
158
|
+
|
|
159
|
+
```sh
|
|
160
|
+
toloka training get <training-id> --json
|
|
161
|
+
toloka model get <model-id> --json
|
|
162
|
+
toloka model versions <model-id> --json
|
|
163
|
+
toloka model version download <model-version-id> --out <absolute-output-directory>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The downloaded folder contains full model weights (`config.json` and `model-*.safetensors`) and tokenizer files, including supported text sidecars such as `merges.txt` and `chat_template.jinja`. Keep the files together. The learned prompt is represented by `<|gist_…|>` tokens that prefix requests in place of the original prompt. Verify serving separately with a compatible runtime; a successful download alone does not establish inference readiness.
|
package/src/cli/analytics.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { assert } from "es-toolkit";
|
|
2
1
|
import { randomUUID } from "node:crypto";
|
|
3
2
|
import { chmod, readFile, writeFile } from "node:fs/promises";
|
|
4
3
|
import { dirname, join } from "node:path";
|
|
5
4
|
import { PostHog } from "posthog-node";
|
|
6
|
-
import { PipelineGraphValidationError
|
|
5
|
+
import { PipelineGraphValidationError } from "../errors";
|
|
7
6
|
import type { TolokaEnvironment } from "./config";
|
|
8
7
|
import type { CliEnvironment } from "./credentials";
|
|
9
8
|
import { credentialsPath, preparePrivateFilePath } from "./credentials";
|
|
10
9
|
import { isFalseyFlag, isTruthyFlag } from "./boolean-flag";
|
|
11
10
|
import { SDK_VERSION } from "./sdk-version";
|
|
11
|
+
import { assertCli, TolokaCliError } from "./cli-error";
|
|
12
|
+
import { findPotatoApiError } from "./format-error";
|
|
12
13
|
|
|
13
14
|
export const POSTHOG_PROJECT_KEY = "phc_kqHt0f53B5MVqURyzWpW1aNlbOmYt1YbUYza0JPcg19";
|
|
14
15
|
export const POSTHOG_HOST = "https://eu.posthog.com";
|
|
@@ -77,7 +78,9 @@ export function telemetryEnabled(environment: CliEnvironment): boolean {
|
|
|
77
78
|
return true;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
|
|
81
|
+
type CommandGroup = Readonly<Record<string, true | Readonly<Record<string, true>>>>;
|
|
82
|
+
|
|
83
|
+
const COMMAND_GROUPS: Readonly<Record<string, CommandGroup>> = {
|
|
81
84
|
project: { list: true, add: true, switch: true, current: true, remove: true, create: true },
|
|
82
85
|
pipeline: {
|
|
83
86
|
list: true,
|
|
@@ -88,8 +91,31 @@ const COMMAND_GROUPS: Readonly<Record<string, Readonly<Record<string, true>>>> =
|
|
|
88
91
|
cancel: true,
|
|
89
92
|
results: true,
|
|
90
93
|
},
|
|
91
|
-
dataset: { list: true, fields: true, load: true, upload: true },
|
|
94
|
+
dataset: { list: true, create: true, fields: true, load: true, upload: true },
|
|
92
95
|
"delivery-order": { list: true },
|
|
96
|
+
training: {
|
|
97
|
+
list: true,
|
|
98
|
+
get: true,
|
|
99
|
+
create: true,
|
|
100
|
+
update: true,
|
|
101
|
+
cost: true,
|
|
102
|
+
run: true,
|
|
103
|
+
runs: true,
|
|
104
|
+
status: true,
|
|
105
|
+
resume: true,
|
|
106
|
+
cancel: true,
|
|
107
|
+
metrics: true,
|
|
108
|
+
logs: true,
|
|
109
|
+
},
|
|
110
|
+
"base-model": { list: true, get: true },
|
|
111
|
+
model: {
|
|
112
|
+
list: true,
|
|
113
|
+
get: true,
|
|
114
|
+
versions: true,
|
|
115
|
+
create: true,
|
|
116
|
+
archive: true,
|
|
117
|
+
version: { download: true },
|
|
118
|
+
},
|
|
93
119
|
secret: { set: true },
|
|
94
120
|
skill: { list: true, search: true, install: true },
|
|
95
121
|
config: { get: true, set: true },
|
|
@@ -121,9 +147,13 @@ const nameFromTokens = (tokens: readonly string[]): string => {
|
|
|
121
147
|
if (subcommands === undefined) return ROOT_COMMANDS[command] === true ? command : "unknown";
|
|
122
148
|
|
|
123
149
|
const subcommand = tokens[1];
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
150
|
+
const nested = subcommand === undefined ? undefined : subcommands[subcommand];
|
|
151
|
+
if (nested === undefined) return command;
|
|
152
|
+
if (nested === true) return `${command} ${subcommand}`;
|
|
153
|
+
const leaf = tokens[2];
|
|
154
|
+
return leaf !== undefined && nested[leaf] === true
|
|
155
|
+
? `${command} ${subcommand} ${leaf}`
|
|
156
|
+
: `${command} ${subcommand}`;
|
|
127
157
|
};
|
|
128
158
|
|
|
129
159
|
export function commandNameFromArgv(argv: readonly string[]): string {
|
|
@@ -142,19 +172,6 @@ export function commandNameFromArgv(argv: readonly string[]): string {
|
|
|
142
172
|
return nameFromTokens(tokens);
|
|
143
173
|
}
|
|
144
174
|
|
|
145
|
-
const findPotatoApiError = (error: unknown): PotatoApiError | undefined => {
|
|
146
|
-
if (error instanceof PotatoApiError) return error;
|
|
147
|
-
|
|
148
|
-
if (error instanceof AggregateError) {
|
|
149
|
-
for (const nested of error.errors) {
|
|
150
|
-
const apiError = findPotatoApiError(nested);
|
|
151
|
-
if (apiError !== undefined) return apiError;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return error instanceof Error ? findPotatoApiError(error.cause) : undefined;
|
|
156
|
-
};
|
|
157
|
-
|
|
158
175
|
export function classifyCliError(error: unknown): { readonly error_code: string } {
|
|
159
176
|
if (error === undefined) return { error_code: "command_failed" };
|
|
160
177
|
const apiError = findPotatoApiError(error);
|
|
@@ -197,7 +214,11 @@ const noopAnalytics = (): CliAnalytics => ({
|
|
|
197
214
|
identifyCurrentUser() {},
|
|
198
215
|
captureCommand() {},
|
|
199
216
|
async submitIssueReport() {
|
|
200
|
-
|
|
217
|
+
assertCli(
|
|
218
|
+
false,
|
|
219
|
+
"Issue reporting is disabled. Re-run with TOLOKA_TELEMETRY=1.",
|
|
220
|
+
{ hint: { disabled: true } },
|
|
221
|
+
);
|
|
201
222
|
},
|
|
202
223
|
async shutdown() {},
|
|
203
224
|
});
|
|
@@ -270,7 +291,9 @@ export function createCliAnalytics(options: {
|
|
|
270
291
|
const errorProperties = cliErrorProperties(event.error);
|
|
271
292
|
|
|
272
293
|
if (event.error !== undefined) {
|
|
273
|
-
const exception = new
|
|
294
|
+
const exception = new TolokaCliError(`CLI command failed: ${classified.error_code}`, {
|
|
295
|
+
hint: { disabled: true },
|
|
296
|
+
});
|
|
274
297
|
exception.name = "PotatoCliError";
|
|
275
298
|
client.captureException(exception, distinctId, {
|
|
276
299
|
...shared,
|
|
@@ -316,7 +339,11 @@ export function createCliAnalytics(options: {
|
|
|
316
339
|
},
|
|
317
340
|
submitIssueReport(report) {
|
|
318
341
|
const submission = queue.then(async () => {
|
|
319
|
-
|
|
342
|
+
assertCli(
|
|
343
|
+
client && distinctId,
|
|
344
|
+
"PostHog issue reporting could not be initialized.",
|
|
345
|
+
{ hint: { description: "Set TOLOKA_TELEMETRY=1 and check that this machine can reach PostHog, then retry." } },
|
|
346
|
+
);
|
|
320
347
|
deliveryErrors.length = 0;
|
|
321
348
|
client.capture({
|
|
322
349
|
distinctId,
|
package/src/cli/auth.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { assert } from "es-toolkit";
|
|
2
1
|
import { createClient } from "../client";
|
|
3
2
|
import { PotatoApiError } from "../errors";
|
|
4
3
|
import { PRODUCTION_BASE_URL, consoleUrlFor, resolveInternalBaseUrl } from "./config";
|
|
@@ -19,6 +18,7 @@ import type {
|
|
|
19
18
|
} from "./credentials";
|
|
20
19
|
import type { ProjectContext } from "./project-context";
|
|
21
20
|
import { formatProjectContextLine } from "./project-context";
|
|
21
|
+
import { assertCli, TolokaCliError } from "./cli-error";
|
|
22
22
|
|
|
23
23
|
type ProbeFailureKind = "rejected" | "unreachable" | "platform";
|
|
24
24
|
|
|
@@ -89,7 +89,14 @@ export type AuthProbe = {
|
|
|
89
89
|
readonly pagination: { readonly total: number };
|
|
90
90
|
}>;
|
|
91
91
|
};
|
|
92
|
-
readonly me?: () => Promise<{
|
|
92
|
+
readonly me?: () => Promise<{
|
|
93
|
+
readonly internal: boolean;
|
|
94
|
+
readonly teams?: ReadonlyArray<{
|
|
95
|
+
readonly id: string;
|
|
96
|
+
readonly writable: boolean;
|
|
97
|
+
readonly internal?: boolean;
|
|
98
|
+
}>;
|
|
99
|
+
}>;
|
|
93
100
|
};
|
|
94
101
|
|
|
95
102
|
export type LoginOptions = {
|
|
@@ -119,14 +126,19 @@ export async function login(options: LoginOptions = {}): Promise<LoginResult> {
|
|
|
119
126
|
|
|
120
127
|
let apiKey = options.apiKey ?? environment.TOLOKA_API_KEY;
|
|
121
128
|
if (apiKey === undefined) {
|
|
122
|
-
|
|
129
|
+
assertCli(
|
|
123
130
|
options.prompt,
|
|
124
131
|
"Missing API key. Without a terminal, pipe the key to toloka login --api-key-stdin, pass --api-key, or set TOLOKA_API_KEY.",
|
|
132
|
+
{ hint: { disabled: true } },
|
|
125
133
|
);
|
|
126
134
|
write(`\n${apiKeyInstructions(baseUrl)}\n`);
|
|
127
135
|
apiKey = (await options.prompt("API key: ", { mask: true })).trim();
|
|
128
136
|
}
|
|
129
|
-
|
|
137
|
+
assertCli(
|
|
138
|
+
apiKey.length > 0,
|
|
139
|
+
"No API key entered.",
|
|
140
|
+
{ hint: { description: "Re-run toloka login and paste the key at the prompt, or pass --api-key." } },
|
|
141
|
+
);
|
|
130
142
|
const credentials: StoredCredentials = { baseUrl, apiKey };
|
|
131
143
|
const probe: AuthProbe = options.probe?.(credentials) ?? createClient(credentials);
|
|
132
144
|
|
|
@@ -136,13 +148,13 @@ export async function login(options: LoginOptions = {}): Promise<LoginResult> {
|
|
|
136
148
|
reachable = `Authenticated against ${baseUrl}: ${page.pagination.total} project(s) visible.`;
|
|
137
149
|
} catch (error) {
|
|
138
150
|
const failure = classifyProbeFailure(error);
|
|
139
|
-
throw new
|
|
151
|
+
throw new TolokaCliError(
|
|
140
152
|
[
|
|
141
153
|
`Could not authenticate against ${baseUrl}: ${failure.reason}`,
|
|
142
154
|
"",
|
|
143
155
|
`Nothing was saved. ${LOGIN_FAILURE_ADVICE[failure.kind](baseUrl)}`,
|
|
144
156
|
].join("\n"),
|
|
145
|
-
{ cause: error },
|
|
157
|
+
{ cause: error, hint: { disabled: true } },
|
|
146
158
|
);
|
|
147
159
|
}
|
|
148
160
|
|
|
@@ -249,13 +261,21 @@ const resolvePlatform = (
|
|
|
249
261
|
);
|
|
250
262
|
};
|
|
251
263
|
|
|
252
|
-
const
|
|
253
|
-
if (probe.me === undefined) return
|
|
264
|
+
const accountLines = async (probe: AuthProbe): Promise<readonly string[]> => {
|
|
265
|
+
if (probe.me === undefined) return [];
|
|
254
266
|
try {
|
|
255
267
|
const me = await probe.me();
|
|
256
|
-
return `Internal: ${me.internal}
|
|
268
|
+
if (me.teams === undefined) return [`Internal: ${me.internal}`];
|
|
269
|
+
const teams = me.teams.map((team) => {
|
|
270
|
+
const qualifiers = [...(team.internal ? ["internal"] : []), ...(team.writable ? [] : ["read-only"])];
|
|
271
|
+
return qualifiers.length === 0 ? team.id : `${team.id} (${qualifiers.join(", ")})`;
|
|
272
|
+
});
|
|
273
|
+
return [
|
|
274
|
+
`Internal: ${me.internal}`,
|
|
275
|
+
`Teams: ${teams.length === 0 ? "(none)" : teams.join(", ")}`,
|
|
276
|
+
];
|
|
257
277
|
} catch {
|
|
258
|
-
return
|
|
278
|
+
return [];
|
|
259
279
|
}
|
|
260
280
|
};
|
|
261
281
|
|
|
@@ -306,8 +326,7 @@ export async function authStatus(options: AuthStatusOptions = {}): Promise<AuthS
|
|
|
306
326
|
try {
|
|
307
327
|
const page = await probe.projects.listPage({ limit: 1 });
|
|
308
328
|
lines.push("", `Reachable: ${page.pagination.total} project(s) visible.`);
|
|
309
|
-
|
|
310
|
-
if (internalLine !== undefined) lines.push(internalLine);
|
|
329
|
+
lines.push(...(await accountLines(probe)));
|
|
311
330
|
return { authenticated: true, output: lines.join("\n") };
|
|
312
331
|
} catch (error) {
|
|
313
332
|
const failure = classifyProbeFailure(error);
|
package/src/cli/auto-update.ts
CHANGED
|
@@ -17,6 +17,8 @@ import { commandNameFromArgv } from "./analytics";
|
|
|
17
17
|
import { readCliSettings } from "./settings";
|
|
18
18
|
import { CHECK_RESULT_FILE_ENV } from "./check";
|
|
19
19
|
import { isFalseyFlag, isTruthyFlag } from "./boolean-flag";
|
|
20
|
+
import { TolokaCliError } from "./cli-error";
|
|
21
|
+
import { formatCliError } from "./format-error";
|
|
20
22
|
|
|
21
23
|
export const AUTO_UPDATE_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
22
24
|
export const AUTO_UPDATE_QUARANTINE_MS = 24 * 60 * 60 * 1000;
|
|
@@ -199,9 +201,9 @@ const collectOutput = async (stream: NodeJS.ReadableStream | null): Promise<stri
|
|
|
199
201
|
return Buffer.concat(chunks).toString("utf8");
|
|
200
202
|
};
|
|
201
203
|
|
|
202
|
-
class RegistryError extends
|
|
204
|
+
class RegistryError extends TolokaCliError {
|
|
203
205
|
constructor(message: string) {
|
|
204
|
-
super(message);
|
|
206
|
+
super(message, { hint: { disabled: true } });
|
|
205
207
|
this.name = "RegistryError";
|
|
206
208
|
}
|
|
207
209
|
}
|
|
@@ -239,10 +241,16 @@ const defaultInstall: AutoUpdateInstall = async ({
|
|
|
239
241
|
const exitCode = close[0];
|
|
240
242
|
if (exitCode === 0) return;
|
|
241
243
|
const detail = [stderr.trim(), stdout.trim()].filter((part) => part.length > 0).join("\n");
|
|
242
|
-
throw new
|
|
244
|
+
throw new TolokaCliError(
|
|
243
245
|
detail.length > 0
|
|
244
246
|
? `bun add --global failed: ${detail}`
|
|
245
247
|
: `bun add --global exited ${String(exitCode)}`,
|
|
248
|
+
{
|
|
249
|
+
hint: {
|
|
250
|
+
description:
|
|
251
|
+
"Run the bun add --global command yourself, or turn the check off with toloka config set auto-update false.",
|
|
252
|
+
},
|
|
253
|
+
},
|
|
246
254
|
);
|
|
247
255
|
};
|
|
248
256
|
|
|
@@ -340,9 +348,7 @@ export async function maybeAutoUpdate(options: {
|
|
|
340
348
|
environment,
|
|
341
349
|
});
|
|
342
350
|
} catch (error) {
|
|
343
|
-
io.stderr(
|
|
344
|
-
`Auto-update to ${candidate.version} failed: ${error instanceof Error ? error.message : String(error)}.`,
|
|
345
|
-
);
|
|
351
|
+
io.stderr(`Auto-update to ${candidate.version} failed: ${formatCliError(error)}`);
|
|
346
352
|
return continueResult();
|
|
347
353
|
}
|
|
348
354
|
|