@toloka-platform/sdk 0.1.0-preview.3 → 0.1.0-preview.5
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 +2 -2
- package/bin/toloka.js +7 -0
- package/dist/cli/toloka.js +119 -114
- 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 +4 -0
- package/docs/cli.md +56 -5
- package/docs/getting-started.md +3 -3
- package/docs/limitations.md +1 -1
- package/openapi/gen/api.ts +22 -0
- package/openapi/gen/models/createDatasetBody.ts +2 -0
- package/openapi/gen/models/createDatasetBodyProfile.ts +2 -0
- package/openapi/gen/models/createDatasetUploadBodyFormat.ts +1 -1
- package/openapi/gen/models/dataset.ts +2 -0
- package/openapi/gen/models/datasetProfile.ts +3 -0
- package/openapi/gen/models/datasetProfileStamp.ts +6 -0
- package/openapi/gen/models/datasetProfileStampName.ts +2 -0
- package/openapi/gen/models/index.ts +5 -0
- package/openapi/gen/models/runCost.ts +7 -0
- package/package.json +4 -2
- package/skills/catalog.json +22 -0
- package/skills/{SKILL.md → toloka-build/SKILL.md} +1 -1
- package/skills/{references → toloka-build/references}/workflow-and-cli.md +14 -4
- package/skills/toloka-report-issue/SKILL.md +25 -0
- package/skills/toloka-report-issue/references/report-template.md +34 -0
- package/src/authoring/index.ts +216 -171
- package/src/cli/analytics.ts +53 -1
- package/src/cli/auth.ts +5 -5
- package/src/cli/credentials.ts +8 -2
- package/src/cli/file-system.ts +34 -0
- package/src/cli/format-error.ts +19 -0
- package/src/cli/init.ts +149 -194
- package/src/cli/program.ts +103 -21
- package/src/cli/report.ts +144 -0
- package/src/cli/skill.ts +363 -0
- package/src/pipeline-graph-v1.contract.json +5 -1
- /package/skills/{references → toloka-build/references}/node-semantics.md +0 -0
package/src/cli/analytics.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assert } from "es-toolkit";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { chmod, readFile, writeFile } from "node:fs/promises";
|
|
3
4
|
import { dirname, join } from "node:path";
|
|
@@ -28,13 +29,22 @@ export type CliAnalyticsCapture = {
|
|
|
28
29
|
readonly error?: unknown;
|
|
29
30
|
};
|
|
30
31
|
|
|
32
|
+
export type IssueReportSubmission = {
|
|
33
|
+
readonly reportId: string;
|
|
34
|
+
readonly reportText: string;
|
|
35
|
+
readonly authoredBy: "human" | "agent";
|
|
36
|
+
readonly platform: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
31
39
|
export type CliAnalytics = {
|
|
32
40
|
readonly shouldIdentify: boolean;
|
|
41
|
+
readonly issueReportingEnabled: boolean;
|
|
33
42
|
readonly identifyCurrentUser: (user: {
|
|
34
43
|
readonly id: string;
|
|
35
44
|
readonly email: string | null;
|
|
36
45
|
}) => void;
|
|
37
46
|
readonly captureCommand: (event: CliAnalyticsCapture) => void;
|
|
47
|
+
readonly submitIssueReport: (report: IssueReportSubmission) => Promise<void>;
|
|
38
48
|
readonly shutdown: () => Promise<void>;
|
|
39
49
|
};
|
|
40
50
|
|
|
@@ -46,6 +56,8 @@ export type PosthogLike = {
|
|
|
46
56
|
disableGeoip: boolean;
|
|
47
57
|
}) => void;
|
|
48
58
|
alias: (event: { distinctId: string; alias: string }) => void;
|
|
59
|
+
flush: () => Promise<void>;
|
|
60
|
+
on?: (event: "error", listener: (error: Error) => void) => void;
|
|
49
61
|
shutdown: (shutdownTimeoutMs?: number) => Promise<void>;
|
|
50
62
|
};
|
|
51
63
|
|
|
@@ -86,6 +98,7 @@ const COMMAND_GROUPS: Readonly<Record<string, Readonly<Record<string, true>>>> =
|
|
|
86
98
|
},
|
|
87
99
|
dataset: { list: true, fields: true, load: true, upload: true },
|
|
88
100
|
secret: { set: true },
|
|
101
|
+
skill: { list: true, search: true, install: true },
|
|
89
102
|
};
|
|
90
103
|
|
|
91
104
|
const ROOT_COMMANDS: Readonly<Record<string, true>> = {
|
|
@@ -99,6 +112,8 @@ const ROOT_COMMANDS: Readonly<Record<string, true>> = {
|
|
|
99
112
|
plan: true,
|
|
100
113
|
status: true,
|
|
101
114
|
validate: true,
|
|
115
|
+
report: true,
|
|
116
|
+
version: true,
|
|
102
117
|
whoami: true,
|
|
103
118
|
};
|
|
104
119
|
|
|
@@ -116,6 +131,7 @@ const nameFromTokens = (tokens: readonly string[]): string => {
|
|
|
116
131
|
};
|
|
117
132
|
|
|
118
133
|
export function commandNameFromArgv(argv: readonly string[]): string {
|
|
134
|
+
if (argv.includes("-v") || argv.includes("--version")) return "version";
|
|
119
135
|
const tokens: string[] = [];
|
|
120
136
|
for (let index = 0; index < argv.length; index += 1) {
|
|
121
137
|
const token = argv[index];
|
|
@@ -161,8 +177,12 @@ const readAnonymousId = async (environment: CliEnvironment): Promise<string> =>
|
|
|
161
177
|
|
|
162
178
|
const noopAnalytics = (): CliAnalytics => ({
|
|
163
179
|
shouldIdentify: false,
|
|
180
|
+
issueReportingEnabled: false,
|
|
164
181
|
identifyCurrentUser() {},
|
|
165
182
|
captureCommand() {},
|
|
183
|
+
async submitIssueReport() {
|
|
184
|
+
assert(false, "Issue reporting is disabled. Re-run with TOLOKA_TELEMETRY=1.");
|
|
185
|
+
},
|
|
166
186
|
async shutdown() {},
|
|
167
187
|
});
|
|
168
188
|
|
|
@@ -177,6 +197,11 @@ export function createCliAnalytics(options: {
|
|
|
177
197
|
let client = options.client;
|
|
178
198
|
let distinctId = options.distinctId;
|
|
179
199
|
let identifiedUserId: string | undefined;
|
|
200
|
+
const deliveryErrors: Error[] = [];
|
|
201
|
+
const captureDeliveryError = (error: Error) => {
|
|
202
|
+
deliveryErrors.push(error);
|
|
203
|
+
};
|
|
204
|
+
client?.on?.("error", captureDeliveryError);
|
|
180
205
|
|
|
181
206
|
const ensure = async () => {
|
|
182
207
|
if (client !== undefined && distinctId !== undefined) return;
|
|
@@ -186,7 +211,7 @@ export function createCliAnalytics(options: {
|
|
|
186
211
|
flushAt: 1,
|
|
187
212
|
flushInterval: 0,
|
|
188
213
|
});
|
|
189
|
-
created.on("error",
|
|
214
|
+
created.on("error", captureDeliveryError);
|
|
190
215
|
client = created;
|
|
191
216
|
}
|
|
192
217
|
distinctId ??= await readAnonymousId(environment);
|
|
@@ -202,6 +227,7 @@ export function createCliAnalytics(options: {
|
|
|
202
227
|
let queue: Promise<void> = ensure().catch(() => {});
|
|
203
228
|
|
|
204
229
|
return {
|
|
230
|
+
issueReportingEnabled: true,
|
|
205
231
|
shouldIdentify: true,
|
|
206
232
|
identifyCurrentUser(user) {
|
|
207
233
|
queue = queue
|
|
@@ -252,6 +278,32 @@ export function createCliAnalytics(options: {
|
|
|
252
278
|
})
|
|
253
279
|
.catch(() => {});
|
|
254
280
|
},
|
|
281
|
+
submitIssueReport(report) {
|
|
282
|
+
const submission = queue.then(async () => {
|
|
283
|
+
assert(client && distinctId, "PostHog issue reporting could not be initialized.");
|
|
284
|
+
deliveryErrors.length = 0;
|
|
285
|
+
client.capture({
|
|
286
|
+
distinctId,
|
|
287
|
+
event: "sdk_cli_issue_report_submitted",
|
|
288
|
+
disableGeoip: true,
|
|
289
|
+
properties: {
|
|
290
|
+
...sharedProperties(),
|
|
291
|
+
report_id: report.reportId,
|
|
292
|
+
report_text: report.reportText,
|
|
293
|
+
report_length_bytes: new TextEncoder().encode(report.reportText).length,
|
|
294
|
+
authored_by: report.authoredBy,
|
|
295
|
+
platform: report.platform,
|
|
296
|
+
$process_person_profile: false,
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
await client.flush();
|
|
300
|
+
const reportError = deliveryErrors[0];
|
|
301
|
+
deliveryErrors.length = 0;
|
|
302
|
+
if (reportError !== undefined) throw reportError;
|
|
303
|
+
});
|
|
304
|
+
queue = submission.catch(() => {});
|
|
305
|
+
return submission;
|
|
306
|
+
},
|
|
255
307
|
async shutdown() {
|
|
256
308
|
try {
|
|
257
309
|
await queue;
|
package/src/cli/auth.ts
CHANGED
|
@@ -74,12 +74,12 @@ export type LoginResult = {
|
|
|
74
74
|
export async function login(options: LoginOptions = {}): Promise<LoginResult> {
|
|
75
75
|
const environment = options.environment ?? process.env;
|
|
76
76
|
const write = options.write ?? (() => undefined);
|
|
77
|
-
const baseUrl = (
|
|
77
|
+
const baseUrl = normalizeBaseUrl(
|
|
78
78
|
options.baseUrl ??
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
)
|
|
79
|
+
environment.POTATO_BASE_URL ??
|
|
80
|
+
resolveInternalBaseUrl(environment) ??
|
|
81
|
+
PRODUCTION_BASE_URL,
|
|
82
|
+
);
|
|
83
83
|
|
|
84
84
|
let apiKey = options.apiKey;
|
|
85
85
|
if (apiKey === undefined) {
|
package/src/cli/credentials.ts
CHANGED
|
@@ -52,13 +52,19 @@ export const credentialsPath = (environment: CliEnvironment = process.env): stri
|
|
|
52
52
|
export const legacyCredentialsPath = (environment: CliEnvironment = process.env): string =>
|
|
53
53
|
join(home(environment), ".toloka", "credentials.json");
|
|
54
54
|
|
|
55
|
+
const removeTrailingSlashes = (value: string): string => {
|
|
56
|
+
let end = value.length;
|
|
57
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1;
|
|
58
|
+
return end === value.length ? value : value.slice(0, end);
|
|
59
|
+
};
|
|
60
|
+
|
|
55
61
|
export const normalizeBaseUrl = (baseUrl: string): string => {
|
|
56
|
-
const trimmed = baseUrl.trim()
|
|
62
|
+
const trimmed = removeTrailingSlashes(baseUrl.trim());
|
|
57
63
|
try {
|
|
58
64
|
const url = new URL(trimmed);
|
|
59
65
|
url.hash = "";
|
|
60
66
|
url.search = "";
|
|
61
|
-
return url.toString()
|
|
67
|
+
return removeTrailingSlashes(url.toString());
|
|
62
68
|
} catch {
|
|
63
69
|
return trimmed;
|
|
64
70
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { assert } from "es-toolkit";
|
|
2
|
+
import { lstat } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
export const hasErrorCode = (error: unknown, ...codes: readonly string[]) =>
|
|
6
|
+
error instanceof Error &&
|
|
7
|
+
"code" in error &&
|
|
8
|
+
typeof error.code === "string" &&
|
|
9
|
+
codes.includes(error.code);
|
|
10
|
+
|
|
11
|
+
export const isMissingFileError = (error: unknown) => hasErrorCode(error, "ENOENT");
|
|
12
|
+
|
|
13
|
+
export const entryStats = async (path: string) => {
|
|
14
|
+
try {
|
|
15
|
+
return await lstat(path);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
if (isMissingFileError(error)) return undefined;
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const assertSafeDestination = async (
|
|
23
|
+
targetDirectory: string,
|
|
24
|
+
relativePath: string,
|
|
25
|
+
action = "write",
|
|
26
|
+
) => {
|
|
27
|
+
let currentPath = targetDirectory;
|
|
28
|
+
for (const segment of relativePath.split("/")) {
|
|
29
|
+
currentPath = join(currentPath, segment);
|
|
30
|
+
const entry = await entryStats(currentPath);
|
|
31
|
+
if (entry === undefined) return;
|
|
32
|
+
assert(!entry.isSymbolicLink(), `Refusing to ${action} through symbolic link ${currentPath}.`);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { PotatoApiError } from "../errors";
|
|
2
|
+
|
|
3
|
+
export const apiErrorDetails = (error: PotatoApiError): string | undefined => {
|
|
4
|
+
const details =
|
|
5
|
+
typeof error.body === "string"
|
|
6
|
+
? error.body
|
|
7
|
+
: error.body === undefined
|
|
8
|
+
? undefined
|
|
9
|
+
: JSON.stringify(error.body);
|
|
10
|
+
return details === undefined || details.length === 0 ? undefined : details;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const formatCliError = (error: unknown): string => {
|
|
14
|
+
if (error instanceof PotatoApiError) {
|
|
15
|
+
const details = apiErrorDetails(error);
|
|
16
|
+
return details === undefined ? error.message : `${error.message}\n${details}`;
|
|
17
|
+
}
|
|
18
|
+
return error instanceof Error ? error.message : String(error);
|
|
19
|
+
};
|