libretto 0.6.24 → 0.6.26
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 +9 -1
- package/README.template.md +9 -1
- package/dist/cli/commands/browser.js +17 -10
- package/dist/cli/commands/cloud-credentials.js +70 -0
- package/dist/cli/commands/deploy.js +24 -2
- package/dist/cli/commands/execution.js +9 -30
- package/dist/cli/commands/import-chrome-profiles.js +46 -0
- package/dist/cli/commands/profiles.js +71 -0
- package/dist/cli/commands/shared.js +1 -3
- package/dist/cli/core/browser.js +89 -75
- package/dist/cli/core/daemon/daemon.js +47 -35
- package/dist/cli/core/daemon/ipc.js +3 -0
- package/dist/cli/core/deploy-artifact.js +85 -22
- package/dist/cli/core/profiles.js +47 -0
- package/dist/cli/core/prompt.js +9 -0
- package/dist/cli/core/providers/libretto-cloud.js +6 -2
- package/dist/cli/core/session-logs.js +325 -0
- package/dist/cli/core/telemetry.js +110 -311
- package/dist/cli/core/workflow-runner/runner.js +65 -0
- package/dist/cli/router.js +9 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12 -0
- package/dist/shared/workflow/auth-profile-name.d.ts +3 -0
- package/dist/shared/workflow/auth-profile-name.js +29 -0
- package/dist/shared/workflow/auth-profile-state.d.ts +20 -0
- package/dist/shared/workflow/auth-profile-state.js +105 -0
- package/dist/shared/workflow/authenticate.d.ts +17 -0
- package/dist/shared/workflow/authenticate.js +37 -0
- package/dist/shared/workflow/credentials.d.ts +5 -0
- package/dist/shared/workflow/credentials.js +68 -0
- package/dist/shared/workflow/workflow.d.ts +16 -1
- package/dist/shared/workflow/workflow.js +56 -4
- package/package.json +1 -1
- package/skills/libretto/SKILL.md +3 -4
- package/skills/libretto/references/auth-profiles.md +61 -11
- package/skills/libretto/references/code-generation-rules.md +31 -1
- package/skills/libretto-readonly/SKILL.md +1 -1
- package/src/cli/commands/browser.ts +19 -11
- package/src/cli/commands/cloud-credentials.ts +82 -0
- package/src/cli/commands/deploy.ts +41 -2
- package/src/cli/commands/execution.ts +10 -31
- package/src/cli/commands/import-chrome-profiles.ts +46 -0
- package/src/cli/commands/profiles.ts +90 -0
- package/src/cli/commands/shared.ts +4 -8
- package/src/cli/core/browser.ts +102 -91
- package/src/cli/core/daemon/config.ts +4 -1
- package/src/cli/core/daemon/daemon.ts +52 -44
- package/src/cli/core/daemon/ipc.ts +15 -0
- package/src/cli/core/deploy-artifact.ts +131 -32
- package/src/cli/core/profiles.ts +53 -0
- package/src/cli/core/prompt.ts +15 -0
- package/src/cli/core/providers/libretto-cloud.ts +6 -2
- package/src/cli/core/providers/types.ts +4 -1
- package/src/cli/core/session-logs.ts +445 -0
- package/src/cli/core/telemetry.ts +142 -413
- package/src/cli/core/workflow-runner/runner.ts +86 -1
- package/src/cli/router.ts +8 -0
- package/src/index.ts +10 -0
- package/src/shared/workflow/auth-profile-name.ts +27 -0
- package/src/shared/workflow/auth-profile-state.ts +144 -0
- package/src/shared/workflow/authenticate.ts +63 -0
- package/src/shared/workflow/credentials.ts +91 -0
- package/src/shared/workflow/workflow.ts +89 -4
|
@@ -1,445 +1,174 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "node:
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
resourceType?: string;
|
|
20
|
-
status: number | null;
|
|
21
|
-
statusText?: string | null;
|
|
22
|
-
contentType: string | null;
|
|
23
|
-
requestHeaders?: Record<string, string> | null;
|
|
24
|
-
responseHeaders?: Record<string, string> | null;
|
|
25
|
-
requestBodyPreview?: string | null;
|
|
26
|
-
requestBodyPath?: string | null;
|
|
27
|
-
requestBodyBytes?: number | null;
|
|
28
|
-
requestBodyTruncated?: boolean;
|
|
29
|
-
requestBodyOmittedReason?: string | null;
|
|
30
|
-
responseBodyPreview?: string | null;
|
|
31
|
-
responseBodyPath?: string | null;
|
|
32
|
-
responseBodyBytes?: number | null;
|
|
33
|
-
responseBodyTruncated?: boolean;
|
|
34
|
-
responseBodyOmittedReason?: string | null;
|
|
35
|
-
errorText?: string | null;
|
|
36
|
-
postData?: string;
|
|
37
|
-
responseBody?: string | null;
|
|
38
|
-
size?: number | null;
|
|
39
|
-
durationMs?: number | null;
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { promises as fs } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { basename, dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import type { SimpleCLICommandMeta, SimpleCLIMiddleware } from "affordance";
|
|
8
|
+
import { resolveHostedApiUrl } from "./auth-fetch.js";
|
|
9
|
+
|
|
10
|
+
const TELEMETRY_FILE_NAME = "telemetry.json";
|
|
11
|
+
const TELEMETRY_ENDPOINT_PATH = "/v1/telemetry/recordCliEvent";
|
|
12
|
+
const TELEMETRY_TIMEOUT_MS = 250;
|
|
13
|
+
|
|
14
|
+
type BuildChannel = "node_modules" | "source" | "unknown";
|
|
15
|
+
|
|
16
|
+
type StoredTelemetryState = {
|
|
17
|
+
installId?: string;
|
|
18
|
+
enabled?: boolean;
|
|
40
19
|
};
|
|
41
20
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
): NetworkLogEntry[] {
|
|
51
|
-
assertSessionStateExistsOrThrow(session);
|
|
52
|
-
const logPath = getSessionNetworkLogPath(session);
|
|
53
|
-
if (!existsSync(logPath)) return [];
|
|
54
|
-
|
|
55
|
-
const lines = readFileSync(logPath, "utf-8")
|
|
56
|
-
.trim()
|
|
57
|
-
.split("\n")
|
|
58
|
-
.filter(Boolean);
|
|
59
|
-
let entries: NetworkLogEntry[] = lines.map(
|
|
60
|
-
(line) => JSON.parse(line) as NetworkLogEntry,
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
if (opts.method) {
|
|
64
|
-
const m = opts.method.toUpperCase();
|
|
65
|
-
entries = entries.filter((e) => e.method === m);
|
|
66
|
-
}
|
|
67
|
-
if (opts.filter) {
|
|
68
|
-
const re = new RegExp(opts.filter, "i");
|
|
69
|
-
entries = entries.filter((e) => re.test(e.url));
|
|
70
|
-
}
|
|
71
|
-
if (opts.pageId) {
|
|
72
|
-
entries = entries.filter((e) => e.pageId === opts.pageId);
|
|
73
|
-
}
|
|
21
|
+
type CliTelemetryPayload = {
|
|
22
|
+
installId: string;
|
|
23
|
+
timestamp: string;
|
|
24
|
+
event: string;
|
|
25
|
+
error: boolean;
|
|
26
|
+
packageVersion: string;
|
|
27
|
+
buildChannel: BuildChannel;
|
|
28
|
+
};
|
|
74
29
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
30
|
+
type PackageJson = {
|
|
31
|
+
version?: unknown;
|
|
32
|
+
};
|
|
79
33
|
|
|
80
|
-
|
|
34
|
+
function packageRoot(): string {
|
|
35
|
+
return join(dirname(fileURLToPath(import.meta.url)), "../../..");
|
|
81
36
|
}
|
|
82
37
|
|
|
83
|
-
|
|
84
|
-
ts: string;
|
|
85
|
-
pageId?: string;
|
|
86
|
-
action: string;
|
|
87
|
-
source: "user" | "agent";
|
|
88
|
-
selector?: string;
|
|
89
|
-
bestSemanticSelector?: string;
|
|
90
|
-
targetSelector?: string;
|
|
91
|
-
ancestorSelectors?: string[];
|
|
92
|
-
nearbyText?: string;
|
|
93
|
-
composedPath?: string[];
|
|
94
|
-
coordinates?: {
|
|
95
|
-
x: number;
|
|
96
|
-
y: number;
|
|
97
|
-
};
|
|
98
|
-
value?: string;
|
|
99
|
-
url?: string;
|
|
100
|
-
duration?: number;
|
|
101
|
-
success: boolean;
|
|
102
|
-
error?: string;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export function parentLogAction(
|
|
106
|
-
session: string,
|
|
107
|
-
entry: Record<string, unknown>,
|
|
108
|
-
): void {
|
|
38
|
+
function readPackageVersion(): string {
|
|
109
39
|
try {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
40
|
+
const parsed = JSON.parse(
|
|
41
|
+
readFileSync(join(packageRoot(), "package.json"), "utf8"),
|
|
42
|
+
) as PackageJson;
|
|
43
|
+
return typeof parsed.version === "string" ? parsed.version : "unknown";
|
|
44
|
+
} catch {
|
|
45
|
+
return "unknown";
|
|
46
|
+
}
|
|
116
47
|
}
|
|
117
48
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
} = {},
|
|
127
|
-
): ActionLogEntry[] {
|
|
128
|
-
assertSessionStateExistsOrThrow(session);
|
|
129
|
-
const logPath = getSessionActionsLogPath(session);
|
|
130
|
-
if (!existsSync(logPath)) return [];
|
|
131
|
-
|
|
132
|
-
const lines = readFileSync(logPath, "utf-8")
|
|
133
|
-
.trim()
|
|
134
|
-
.split("\n")
|
|
135
|
-
.filter(Boolean);
|
|
136
|
-
let entries: ActionLogEntry[] = lines.map(
|
|
137
|
-
(line) => JSON.parse(line) as ActionLogEntry,
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
if (opts.action) {
|
|
141
|
-
const a = opts.action.toLowerCase();
|
|
142
|
-
entries = entries.filter((e) => e.action === a);
|
|
143
|
-
}
|
|
144
|
-
if (opts.source) {
|
|
145
|
-
const s = opts.source.toLowerCase();
|
|
146
|
-
entries = entries.filter((e) => e.source === s);
|
|
147
|
-
}
|
|
148
|
-
if (opts.filter) {
|
|
149
|
-
const re = new RegExp(opts.filter, "i");
|
|
150
|
-
entries = entries.filter(
|
|
151
|
-
(e) =>
|
|
152
|
-
re.test(e.action) ||
|
|
153
|
-
re.test(e.selector || "") ||
|
|
154
|
-
re.test(e.bestSemanticSelector || "") ||
|
|
155
|
-
re.test(e.targetSelector || "") ||
|
|
156
|
-
re.test((e.ancestorSelectors || []).join(" ")) ||
|
|
157
|
-
re.test(e.nearbyText || "") ||
|
|
158
|
-
re.test((e.composedPath || []).join(" ")) ||
|
|
159
|
-
re.test(e.value || "") ||
|
|
160
|
-
re.test(e.url || ""),
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
if (opts.pageId) {
|
|
164
|
-
entries = entries.filter((e) => e.pageId === opts.pageId);
|
|
49
|
+
function resolveBuildChannel(): BuildChannel {
|
|
50
|
+
const root = packageRoot();
|
|
51
|
+
const workspaceRoot = join(root, "../..");
|
|
52
|
+
if (
|
|
53
|
+
basename(dirname(root)) === "packages" &&
|
|
54
|
+
existsSync(join(workspaceRoot, "pnpm-workspace.yaml"))
|
|
55
|
+
) {
|
|
56
|
+
return "source";
|
|
165
57
|
}
|
|
166
58
|
|
|
167
|
-
const
|
|
168
|
-
if (
|
|
169
|
-
entries = entries.slice(-last);
|
|
170
|
-
}
|
|
59
|
+
const pathSegments = root.split(/[\\/]+/);
|
|
60
|
+
if (pathSegments.includes("node_modules")) return "node_modules";
|
|
171
61
|
|
|
172
|
-
return
|
|
62
|
+
return "unknown";
|
|
173
63
|
}
|
|
174
64
|
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
"dblclick",
|
|
178
|
-
"fill",
|
|
179
|
-
"type",
|
|
180
|
-
"press",
|
|
181
|
-
"check",
|
|
182
|
-
"uncheck",
|
|
183
|
-
"selectOption",
|
|
184
|
-
"hover",
|
|
185
|
-
"focus",
|
|
186
|
-
"scrollIntoViewIfNeeded",
|
|
187
|
-
"waitFor",
|
|
188
|
-
"innerHTML",
|
|
189
|
-
"innerText",
|
|
190
|
-
"textContent",
|
|
191
|
-
"inputValue",
|
|
192
|
-
"isChecked",
|
|
193
|
-
"isDisabled",
|
|
194
|
-
"isEditable",
|
|
195
|
-
"isEnabled",
|
|
196
|
-
"isHidden",
|
|
197
|
-
"isVisible",
|
|
198
|
-
"count",
|
|
199
|
-
"boundingBox",
|
|
200
|
-
"screenshot",
|
|
201
|
-
"evaluate",
|
|
202
|
-
"evaluateAll",
|
|
203
|
-
"evaluateHandle",
|
|
204
|
-
"getAttribute",
|
|
205
|
-
"dispatchEvent",
|
|
206
|
-
"setInputFiles",
|
|
207
|
-
"selectText",
|
|
208
|
-
"dragTo",
|
|
209
|
-
"highlight",
|
|
210
|
-
"tap",
|
|
211
|
-
] as const;
|
|
65
|
+
const packageVersion = readPackageVersion();
|
|
66
|
+
const buildChannel = resolveBuildChannel();
|
|
212
67
|
|
|
213
|
-
|
|
214
|
-
"
|
|
215
|
-
|
|
216
|
-
"locator",
|
|
217
|
-
"getByRole",
|
|
218
|
-
"getByText",
|
|
219
|
-
"getByLabel",
|
|
220
|
-
"getByPlaceholder",
|
|
221
|
-
"getByAltText",
|
|
222
|
-
"getByTitle",
|
|
223
|
-
"getByTestId",
|
|
224
|
-
"filter",
|
|
225
|
-
"and",
|
|
226
|
-
"or",
|
|
227
|
-
] as const;
|
|
68
|
+
function telemetryDir(): string {
|
|
69
|
+
return join(homedir(), ".libretto");
|
|
70
|
+
}
|
|
228
71
|
|
|
229
|
-
function
|
|
230
|
-
|
|
231
|
-
return `${method}(${formatted})`;
|
|
72
|
+
function telemetryPath(): string {
|
|
73
|
+
return join(telemetryDir(), TELEMETRY_FILE_NAME);
|
|
232
74
|
}
|
|
233
75
|
|
|
234
|
-
function
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
): any {
|
|
242
|
-
if (locator.__librettoActionLogged) return locator;
|
|
243
|
-
locator.__librettoActionLogged = true;
|
|
76
|
+
function isTelemetryDisabled(): boolean {
|
|
77
|
+
return (
|
|
78
|
+
process.env.LIBRETTO_TELEMETRY_DISABLED === "1" ||
|
|
79
|
+
process.env.DO_NOT_TRACK === "1" ||
|
|
80
|
+
process.env.CI === "1"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
244
83
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
(window as any).__btApiActionInProgress = true;
|
|
253
|
-
});
|
|
254
|
-
} catch {}
|
|
255
|
-
try {
|
|
256
|
-
const result = await origAct(...actArgs);
|
|
257
|
-
parentLogAction(session, {
|
|
258
|
-
pageId,
|
|
259
|
-
action: actMethod,
|
|
260
|
-
source: "agent",
|
|
261
|
-
selector: hint,
|
|
262
|
-
value:
|
|
263
|
-
actArgs[0] !== undefined
|
|
264
|
-
? String(actArgs[0]).slice(0, 100)
|
|
265
|
-
: undefined,
|
|
266
|
-
duration: Date.now() - start,
|
|
267
|
-
success: true,
|
|
268
|
-
});
|
|
269
|
-
onActivity?.();
|
|
270
|
-
return result;
|
|
271
|
-
} catch (err: any) {
|
|
272
|
-
parentLogAction(session, {
|
|
273
|
-
pageId,
|
|
274
|
-
action: actMethod,
|
|
275
|
-
source: "agent",
|
|
276
|
-
selector: hint,
|
|
277
|
-
duration: Date.now() - start,
|
|
278
|
-
success: false,
|
|
279
|
-
error: err.message,
|
|
280
|
-
});
|
|
281
|
-
onActivity?.();
|
|
282
|
-
throw err;
|
|
283
|
-
} finally {
|
|
284
|
-
try {
|
|
285
|
-
await page.evaluate(() => {
|
|
286
|
-
(window as any).__btApiActionInProgress = false;
|
|
287
|
-
});
|
|
288
|
-
} catch {}
|
|
289
|
-
}
|
|
290
|
-
};
|
|
84
|
+
async function readTelemetryState(): Promise<StoredTelemetryState | null> {
|
|
85
|
+
try {
|
|
86
|
+
const raw = await fs.readFile(telemetryPath(), "utf8");
|
|
87
|
+
return JSON.parse(raw) as Partial<StoredTelemetryState>;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
90
|
+
return null;
|
|
291
91
|
}
|
|
92
|
+
}
|
|
292
93
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
locator[method] = (...args: any[]) => {
|
|
297
|
-
const child = origMethod(...args);
|
|
298
|
-
const childHint =
|
|
299
|
-
args.length > 0
|
|
300
|
-
? `${hint}.${formatHint(method, args)}`
|
|
301
|
-
: `${hint}.${method}()`;
|
|
302
|
-
return wrapLocator(child, childHint, session, page, pageId, onActivity);
|
|
303
|
-
};
|
|
304
|
-
}
|
|
94
|
+
async function readOrCreateInstallId(): Promise<string | null> {
|
|
95
|
+
const state = await readTelemetryState();
|
|
96
|
+
if (state?.enabled === false) return null;
|
|
305
97
|
|
|
306
|
-
if (typeof
|
|
307
|
-
|
|
308
|
-
locator.nth = (index: number) => {
|
|
309
|
-
const child = origNth(index);
|
|
310
|
-
const childHint = `${hint}.nth(${index})`;
|
|
311
|
-
return wrapLocator(child, childHint, session, page, pageId, onActivity);
|
|
312
|
-
};
|
|
98
|
+
if (typeof state?.installId === "string" && state.installId.length > 0) {
|
|
99
|
+
return state.installId;
|
|
313
100
|
}
|
|
314
101
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const childHint = `${hint}.all()[${i}]`;
|
|
321
|
-
return wrapLocator(item, childHint, session, page, pageId, onActivity);
|
|
322
|
-
});
|
|
323
|
-
};
|
|
324
|
-
}
|
|
102
|
+
const installId = randomUUID();
|
|
103
|
+
writeTelemetryNotice();
|
|
104
|
+
await writeTelemetryState({ installId, enabled: true });
|
|
105
|
+
return installId;
|
|
106
|
+
}
|
|
325
107
|
|
|
326
|
-
|
|
108
|
+
function writeTelemetryNotice(): void {
|
|
109
|
+
if (!process.stderr.isTTY) return;
|
|
110
|
+
process.stderr.write(
|
|
111
|
+
[
|
|
112
|
+
"Libretto collects anonymous CLI telemetry: install id, timestamp, command event, error status, package version, and build channel only.",
|
|
113
|
+
"Set LIBRETTO_TELEMETRY_DISABLED=1 or DO_NOT_TRACK=1 to disable it, or set enabled:false in ~/.libretto/telemetry.json.",
|
|
114
|
+
].join(" ") + "\n",
|
|
115
|
+
);
|
|
327
116
|
}
|
|
328
117
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
"click",
|
|
337
|
-
"dblclick",
|
|
338
|
-
"fill",
|
|
339
|
-
"type",
|
|
340
|
-
"press",
|
|
341
|
-
"check",
|
|
342
|
-
"uncheck",
|
|
343
|
-
"selectOption",
|
|
344
|
-
"hover",
|
|
345
|
-
"focus",
|
|
346
|
-
] as const;
|
|
347
|
-
const NAV_ACTIONS = ["goto", "reload", "goBack", "goForward"] as const;
|
|
118
|
+
async function writeTelemetryState(state: StoredTelemetryState): Promise<void> {
|
|
119
|
+
await fs.mkdir(telemetryDir(), { recursive: true, mode: 0o700 });
|
|
120
|
+
const target = telemetryPath();
|
|
121
|
+
const tmp = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
122
|
+
await fs.writeFile(tmp, JSON.stringify(state, null, 2), { mode: 0o600 });
|
|
123
|
+
await fs.rename(tmp, target);
|
|
124
|
+
}
|
|
348
125
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
args[1] !== undefined ? String(args[1]).slice(0, 100) : undefined,
|
|
367
|
-
duration: Date.now() - start,
|
|
368
|
-
success: true,
|
|
369
|
-
});
|
|
370
|
-
onActivity?.();
|
|
371
|
-
return result;
|
|
372
|
-
} catch (err: any) {
|
|
373
|
-
parentLogAction(session, {
|
|
374
|
-
pageId,
|
|
375
|
-
action: method,
|
|
376
|
-
source: "agent",
|
|
377
|
-
selector: typeof args[0] === "string" ? args[0] : undefined,
|
|
378
|
-
duration: Date.now() - start,
|
|
379
|
-
success: false,
|
|
380
|
-
error: err.message,
|
|
381
|
-
});
|
|
382
|
-
onActivity?.();
|
|
383
|
-
throw err;
|
|
384
|
-
} finally {
|
|
385
|
-
try {
|
|
386
|
-
await page.evaluate(() => {
|
|
387
|
-
(window as any).__btApiActionInProgress = false;
|
|
388
|
-
});
|
|
389
|
-
} catch {}
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
}
|
|
126
|
+
async function recordCliTelemetryEvent(
|
|
127
|
+
command: SimpleCLICommandMeta,
|
|
128
|
+
error: boolean,
|
|
129
|
+
): Promise<void> {
|
|
130
|
+
if (isTelemetryDisabled()) return;
|
|
131
|
+
const installId = await readOrCreateInstallId();
|
|
132
|
+
if (!installId) return;
|
|
133
|
+
|
|
134
|
+
await sendWithTimeout({
|
|
135
|
+
installId,
|
|
136
|
+
timestamp: new Date().toISOString(),
|
|
137
|
+
event: `libretto ${command.path.join(" ")}`,
|
|
138
|
+
error,
|
|
139
|
+
packageVersion,
|
|
140
|
+
buildChannel,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
393
143
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
onActivity?.();
|
|
409
|
-
return result;
|
|
410
|
-
} catch (err: any) {
|
|
411
|
-
parentLogAction(session, {
|
|
412
|
-
pageId,
|
|
413
|
-
action: method,
|
|
414
|
-
source: "agent",
|
|
415
|
-
url: typeof args[0] === "string" ? args[0] : undefined,
|
|
416
|
-
duration: Date.now() - start,
|
|
417
|
-
success: false,
|
|
418
|
-
error: err.message,
|
|
419
|
-
});
|
|
420
|
-
onActivity?.();
|
|
421
|
-
throw err;
|
|
422
|
-
}
|
|
423
|
-
};
|
|
144
|
+
async function sendWithTimeout(payload: CliTelemetryPayload): Promise<void> {
|
|
145
|
+
const controller = new AbortController();
|
|
146
|
+
const timeout = setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT_MS);
|
|
147
|
+
try {
|
|
148
|
+
await fetch(`${resolveHostedApiUrl()}${TELEMETRY_ENDPOINT_PATH}`, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
},
|
|
153
|
+
body: JSON.stringify({ json: payload }),
|
|
154
|
+
signal: controller.signal,
|
|
155
|
+
});
|
|
156
|
+
} finally {
|
|
157
|
+
clearTimeout(timeout);
|
|
424
158
|
}
|
|
159
|
+
}
|
|
425
160
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const orig = (page as any)[factory].bind(page);
|
|
439
|
-
(page as any)[factory] = (...factoryArgs: any[]) => {
|
|
440
|
-
const locator = orig(...factoryArgs);
|
|
441
|
-
const hint = formatHint(factory, factoryArgs);
|
|
442
|
-
return wrapLocator(locator, hint, session, page, pageId, onActivity);
|
|
443
|
-
};
|
|
161
|
+
export const telemetryMiddleware: SimpleCLIMiddleware<
|
|
162
|
+
unknown,
|
|
163
|
+
{},
|
|
164
|
+
{}
|
|
165
|
+
> = async ({ command, next }) => {
|
|
166
|
+
try {
|
|
167
|
+
const result = await next();
|
|
168
|
+
await recordCliTelemetryEvent(command, false).catch(() => {});
|
|
169
|
+
return result;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
await recordCliTelemetryEvent(command, true).catch(() => {});
|
|
172
|
+
throw error;
|
|
444
173
|
}
|
|
445
|
-
}
|
|
174
|
+
};
|