runline 0.22.0 → 0.24.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/dist/plugins/shiftOcr/src/index.js +13 -0
- package/dist/plugins/shiftPages/src/pages.js +72 -15
- package/dist/plugins/shiftPages/src/shared.js +5 -0
- package/dist/plugins/shiftTranscription/src/index.js +1 -1
- package/dist/plugins/shiftTranscription/src/transcription.js +21 -5
- package/dist/plugins/shiftWork/src/issues.js +119 -33
- package/dist/plugins/steel/src/browser.js +11 -79
- package/dist/plugins/steel/src/index.js +2 -0
- package/dist/plugins/steel/src/page/bundle.generated.js +11 -0
- package/dist/plugins/steel/src/page/cdp.js +232 -0
- package/dist/plugins/steel/src/page/driver.js +286 -0
- package/dist/plugins/steel/src/page/entry.js +91 -0
- package/dist/plugins/steel/src/page/install.js +3 -0
- package/dist/plugins/steel/src/page/page-snapshot.js +330 -0
- package/dist/plugins/steel/src/page/tools.js +649 -0
- package/dist/plugins/steel/src/page/vendor/aria-snapshot/ariaSnapshot.js +411 -0
- package/dist/plugins/steel/src/page/vendor/aria-snapshot/ariaSnapshotDistiller.js +207 -0
- package/dist/plugins/steel/src/page/vendor/aria-snapshot/ariaTypes.js +62 -0
- package/dist/plugins/steel/src/page/vendor/aria-snapshot/cssTokenizer.js +949 -0
- package/dist/plugins/steel/src/page/vendor/aria-snapshot/domUtils.js +157 -0
- package/dist/plugins/steel/src/page/vendor/aria-snapshot/roleUtils.js +1193 -0
- package/dist/plugins/steel/src/page/vendor/aria-snapshot/stringUtils.js +194 -0
- package/dist/plugins/steel/src/page/vendor/aria-snapshot/yaml.js +80 -0
- package/dist/plugins/steel/src/page/wait-for.js +30 -0
- package/dist/plugins/steel/src/sessions.js +2 -7
- package/dist/plugins/steel/src/shared.js +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
// NOTE: this function should not be used to escape any selectors.
|
|
17
|
+
export function escapeWithQuotes(text, char = '\'') {
|
|
18
|
+
const stringified = JSON.stringify(text);
|
|
19
|
+
const escapedText = stringified.substring(1, stringified.length - 1).replace(/\\"/g, '"');
|
|
20
|
+
if (char === '\'')
|
|
21
|
+
return char + escapedText.replace(/[']/g, '\\\'') + char;
|
|
22
|
+
if (char === '"')
|
|
23
|
+
return char + escapedText.replace(/["]/g, '\\"') + char;
|
|
24
|
+
if (char === '`')
|
|
25
|
+
return char + escapedText.replace(/[`]/g, '\\`') + char;
|
|
26
|
+
throw new Error('Invalid escape char');
|
|
27
|
+
}
|
|
28
|
+
export function escapeTemplateString(text) {
|
|
29
|
+
return text
|
|
30
|
+
.replace(/\\/g, '\\\\')
|
|
31
|
+
.replace(/`/g, '\\`')
|
|
32
|
+
.replace(/\$\{/g, '\\${');
|
|
33
|
+
}
|
|
34
|
+
export function isString(obj) {
|
|
35
|
+
return typeof obj === 'string' || obj instanceof String;
|
|
36
|
+
}
|
|
37
|
+
export function toTitleCase(name) {
|
|
38
|
+
return name.charAt(0).toUpperCase() + name.substring(1);
|
|
39
|
+
}
|
|
40
|
+
export function toSnakeCase(name) {
|
|
41
|
+
// E.g. ignoreHTTPSErrors => ignore_https_errors.
|
|
42
|
+
return name.replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/([A-Z])([A-Z][a-z])/g, '$1_$2').toLowerCase();
|
|
43
|
+
}
|
|
44
|
+
export function formatObject(value, indent = ' ', mode = 'multiline') {
|
|
45
|
+
if (typeof value === 'string')
|
|
46
|
+
return escapeWithQuotes(value, '\'');
|
|
47
|
+
if (Array.isArray(value))
|
|
48
|
+
return `[${value.map(o => formatObject(o)).join(', ')}]`;
|
|
49
|
+
if (typeof value === 'object') {
|
|
50
|
+
const keys = Object.keys(value).filter(key => key !== 'timeout' && value[key] !== undefined).sort();
|
|
51
|
+
if (!keys.length)
|
|
52
|
+
return '{}';
|
|
53
|
+
const tokens = [];
|
|
54
|
+
for (const key of keys)
|
|
55
|
+
tokens.push(`${key}: ${formatObject(value[key])}`);
|
|
56
|
+
if (mode === 'multiline')
|
|
57
|
+
return `{\n${tokens.map(t => indent + t).join(`,\n`)}\n}`;
|
|
58
|
+
return `{ ${tokens.join(', ')} }`;
|
|
59
|
+
}
|
|
60
|
+
return String(value);
|
|
61
|
+
}
|
|
62
|
+
export function formatObjectOrVoid(value, indent = ' ') {
|
|
63
|
+
const result = formatObject(value, indent);
|
|
64
|
+
return result === '{}' ? '' : result;
|
|
65
|
+
}
|
|
66
|
+
export function quoteCSSAttributeValue(text) {
|
|
67
|
+
return `"${text.replace(/["\\]/g, char => '\\' + char)}"`;
|
|
68
|
+
}
|
|
69
|
+
let normalizedWhitespaceCache;
|
|
70
|
+
export function cacheNormalizedWhitespaces() {
|
|
71
|
+
normalizedWhitespaceCache = new Map();
|
|
72
|
+
}
|
|
73
|
+
export function normalizeWhiteSpace(text) {
|
|
74
|
+
let result = normalizedWhitespaceCache?.get(text);
|
|
75
|
+
if (result === undefined) {
|
|
76
|
+
result = text.replace(/[\u200b\u00ad]/g, '').trim().replace(/\s+/g, ' ');
|
|
77
|
+
normalizedWhitespaceCache?.set(text, result);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
export function normalizeEscapedRegexQuotes(source) {
|
|
82
|
+
// This function reverses the effect of escapeRegexForSelector below.
|
|
83
|
+
// Odd number of backslashes followed by the quote -> remove unneeded backslash.
|
|
84
|
+
return source.replace(/(^|[^\\])(\\\\)*\\(['"`])/g, '$1$2$3');
|
|
85
|
+
}
|
|
86
|
+
function escapeRegexForSelector(re) {
|
|
87
|
+
// Unicode mode does not allow "identity character escapes", so we do not escape and
|
|
88
|
+
// hope that it does not contain quotes and/or >> signs.
|
|
89
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Character_escape
|
|
90
|
+
// TODO: rework RE usages in internal selectors away from literal representation to json, e.g. {source,flags}.
|
|
91
|
+
if (re.unicode || re.unicodeSets)
|
|
92
|
+
return String(re);
|
|
93
|
+
// Even number of backslashes followed by the quote -> insert a backslash.
|
|
94
|
+
return String(re).replace(/(^|[^\\])(\\\\)*(["'`])/g, '$1$2\\$3').replace(/>>/g, '\\>\\>');
|
|
95
|
+
}
|
|
96
|
+
export function escapeForTextSelector(text, exact) {
|
|
97
|
+
if (typeof text !== 'string')
|
|
98
|
+
return escapeRegexForSelector(text);
|
|
99
|
+
return `${JSON.stringify(text)}${exact ? 's' : 'i'}`;
|
|
100
|
+
}
|
|
101
|
+
export function escapeForAttributeSelector(value, exact) {
|
|
102
|
+
if (typeof value !== 'string')
|
|
103
|
+
return escapeRegexForSelector(value);
|
|
104
|
+
// TODO: this should actually be
|
|
105
|
+
// cssEscape(value).replace(/\\ /g, ' ')
|
|
106
|
+
// However, our attribute selectors do not conform to CSS parsing spec,
|
|
107
|
+
// so we escape them differently.
|
|
108
|
+
return `"${value.replace(/\\/g, '\\\\').replace(/["]/g, '\\"')}"${exact ? 's' : 'i'}`;
|
|
109
|
+
}
|
|
110
|
+
export function trimString(input, cap, suffix = '') {
|
|
111
|
+
if (input.length <= cap)
|
|
112
|
+
return input;
|
|
113
|
+
const chars = [...input];
|
|
114
|
+
if (chars.length > cap)
|
|
115
|
+
return chars.slice(0, cap - suffix.length).join('') + suffix;
|
|
116
|
+
return chars.join('');
|
|
117
|
+
}
|
|
118
|
+
export function trimStringWithEllipsis(input, cap) {
|
|
119
|
+
return trimString(input, cap, '\u2026');
|
|
120
|
+
}
|
|
121
|
+
export function truncateDataUrl(url) {
|
|
122
|
+
// Data URLs can carry megabytes of base64 payload, which is never useful in
|
|
123
|
+
// human/AI-facing output. Keep the media type prefix for context, drop the data.
|
|
124
|
+
if (!url.startsWith('data:'))
|
|
125
|
+
return url;
|
|
126
|
+
const comma = url.indexOf(',');
|
|
127
|
+
if (comma === -1)
|
|
128
|
+
return url;
|
|
129
|
+
return url.slice(0, comma + 1) + '\u2026';
|
|
130
|
+
}
|
|
131
|
+
export function escapeRegExp(s) {
|
|
132
|
+
// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
|
|
133
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
134
|
+
}
|
|
135
|
+
const escaped = { '&': '&', '<': '<', '>': '>', '"': '"', '\'': ''' };
|
|
136
|
+
export function escapeHTMLAttribute(s) {
|
|
137
|
+
return s.replace(/[&<>"']/ug, char => escaped[char]);
|
|
138
|
+
}
|
|
139
|
+
export function escapeHTML(s) {
|
|
140
|
+
return s.replace(/[&<]/ug, char => escaped[char]);
|
|
141
|
+
}
|
|
142
|
+
export function longestCommonSubstring(s1, s2) {
|
|
143
|
+
const n = s1.length;
|
|
144
|
+
const m = s2.length;
|
|
145
|
+
let maxLen = 0;
|
|
146
|
+
let endingIndex = 0;
|
|
147
|
+
// Initialize a 2D array with zeros
|
|
148
|
+
const dp = Array(n + 1)
|
|
149
|
+
.fill(null)
|
|
150
|
+
.map(() => Array(m + 1).fill(0));
|
|
151
|
+
// Build the dp table
|
|
152
|
+
for (let i = 1; i <= n; i++) {
|
|
153
|
+
for (let j = 1; j <= m; j++) {
|
|
154
|
+
if (s1[i - 1] === s2[j - 1]) {
|
|
155
|
+
dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
156
|
+
if (dp[i][j] > maxLen) {
|
|
157
|
+
maxLen = dp[i][j];
|
|
158
|
+
endingIndex = i;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Extract the longest common substring
|
|
164
|
+
return s1.slice(endingIndex - maxLen, endingIndex);
|
|
165
|
+
}
|
|
166
|
+
export function parseRegex(regex) {
|
|
167
|
+
if (regex[0] !== '/')
|
|
168
|
+
throw new Error(`Invalid regex, must start with '/': ${regex}`);
|
|
169
|
+
const lastSlash = regex.lastIndexOf('/');
|
|
170
|
+
if (lastSlash <= 0)
|
|
171
|
+
throw new Error(`Invalid regex, must end with '/' followed by optional flags: ${regex}`);
|
|
172
|
+
const source = regex.slice(1, lastSlash);
|
|
173
|
+
const flags = regex.slice(lastSlash + 1);
|
|
174
|
+
return new RegExp(source, flags);
|
|
175
|
+
}
|
|
176
|
+
export function tomlBasicString(value) {
|
|
177
|
+
// JSON.stringify produces a valid TOML basic string: escapes \", \\, \n, \r, \t and uses \uXXXX for control chars.
|
|
178
|
+
return JSON.stringify(value);
|
|
179
|
+
}
|
|
180
|
+
export function tomlArray(values) {
|
|
181
|
+
return `[${values.map(value => tomlBasicString(value)).join(', ')}]`;
|
|
182
|
+
}
|
|
183
|
+
export function tomlMultilineBasicString(value) {
|
|
184
|
+
// Triple-quoted basic string: escape backslashes first, then any literal """ sequences.
|
|
185
|
+
const escaped = value.replace(/\\/g, '\\\\').replace(/"""/g, '\\"\\"\\"');
|
|
186
|
+
return `"""\n${escaped}\n"""`;
|
|
187
|
+
}
|
|
188
|
+
// Semicolons removed from [[\]()#;?] to avoid polynomial backtracking
|
|
189
|
+
// when both that group and (?:;...)* can match runs of semicolons.
|
|
190
|
+
// \d{1,4} relaxed to \d{0,4} so empty params (e.g. ESC[;H) still match.
|
|
191
|
+
export const ansiRegex = new RegExp('([\\u001B\\u009B][[\\]()#?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{0,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~])))', 'g');
|
|
192
|
+
export function stripAnsiEscapes(str) {
|
|
193
|
+
return str.replace(ansiRegex, '');
|
|
194
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
export function yamlEscapeKeyIfNeeded(str) {
|
|
17
|
+
if (!yamlStringNeedsQuotes(str))
|
|
18
|
+
return str;
|
|
19
|
+
return `'` + str.replace(/'/g, `''`) + `'`;
|
|
20
|
+
}
|
|
21
|
+
export function yamlEscapeValueIfNeeded(str) {
|
|
22
|
+
if (!yamlStringNeedsQuotes(str))
|
|
23
|
+
return str;
|
|
24
|
+
return '"' + str.replace(/[\\"\x00-\x1f\x7f-\x9f]/g, c => {
|
|
25
|
+
switch (c) {
|
|
26
|
+
case '\\':
|
|
27
|
+
return '\\\\';
|
|
28
|
+
case '"':
|
|
29
|
+
return '\\"';
|
|
30
|
+
case '\b':
|
|
31
|
+
return '\\b';
|
|
32
|
+
case '\f':
|
|
33
|
+
return '\\f';
|
|
34
|
+
case '\n':
|
|
35
|
+
return '\\n';
|
|
36
|
+
case '\r':
|
|
37
|
+
return '\\r';
|
|
38
|
+
case '\t':
|
|
39
|
+
return '\\t';
|
|
40
|
+
default:
|
|
41
|
+
const code = c.charCodeAt(0);
|
|
42
|
+
return '\\x' + code.toString(16).padStart(2, '0');
|
|
43
|
+
}
|
|
44
|
+
}) + '"';
|
|
45
|
+
}
|
|
46
|
+
function yamlStringNeedsQuotes(str) {
|
|
47
|
+
if (str.length === 0)
|
|
48
|
+
return true;
|
|
49
|
+
// Strings with leading or trailing whitespace need quotes
|
|
50
|
+
if (/^\s|\s$/.test(str))
|
|
51
|
+
return true;
|
|
52
|
+
// Strings containing control characters need quotes
|
|
53
|
+
if (/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/.test(str))
|
|
54
|
+
return true;
|
|
55
|
+
// Strings starting with '-' need quotes
|
|
56
|
+
if (/^-/.test(str))
|
|
57
|
+
return true;
|
|
58
|
+
// Strings containing ':' or '\n' followed by a space or at the end need quotes
|
|
59
|
+
if (/[\n:](\s|$)/.test(str))
|
|
60
|
+
return true;
|
|
61
|
+
// Strings containing '#' preceded by a space need quotes (comment indicator)
|
|
62
|
+
if (/\s#/.test(str))
|
|
63
|
+
return true;
|
|
64
|
+
// Strings that contain line breaks need quotes
|
|
65
|
+
if (/[\n\r]/.test(str))
|
|
66
|
+
return true;
|
|
67
|
+
// Strings starting with indicator characters or quotes need quotes
|
|
68
|
+
if (/^[&*\],?!>|@"'#%]/.test(str))
|
|
69
|
+
return true;
|
|
70
|
+
// Strings containing special characters that could cause ambiguity
|
|
71
|
+
if (/[{}`]/.test(str))
|
|
72
|
+
return true;
|
|
73
|
+
// YAML array starts with [
|
|
74
|
+
if (/^\[/.test(str))
|
|
75
|
+
return true;
|
|
76
|
+
// Non-string types recognized by YAML
|
|
77
|
+
if (!isNaN(Number(str)) || ['y', 'n', 'yes', 'no', 'true', 'false', 'on', 'off', 'null'].includes(str.toLowerCase()))
|
|
78
|
+
return true;
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export async function waitForPage(document, request, options = {}) {
|
|
2
|
+
const now = options.now ?? Date.now;
|
|
3
|
+
const sleep = options.sleep ??
|
|
4
|
+
(async (ms) => {
|
|
5
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
6
|
+
});
|
|
7
|
+
const timeoutMs = Math.min(30, Math.max(0, request.time ?? 5)) * 1_000;
|
|
8
|
+
const started = now();
|
|
9
|
+
if (!request.text && !request.textGone) {
|
|
10
|
+
await sleep(timeoutMs);
|
|
11
|
+
return {
|
|
12
|
+
matched: true,
|
|
13
|
+
timedOut: false,
|
|
14
|
+
elapsedMs: Math.max(0, now() - started),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
while (true) {
|
|
18
|
+
const text = document.body?.innerText ?? document.body?.textContent ?? "";
|
|
19
|
+
const found = !request.text || text.includes(request.text);
|
|
20
|
+
const gone = !request.textGone || !text.includes(request.textGone);
|
|
21
|
+
const elapsedMs = Math.max(0, now() - started);
|
|
22
|
+
if (found && gone) {
|
|
23
|
+
return { matched: true, timedOut: false, elapsedMs };
|
|
24
|
+
}
|
|
25
|
+
if (elapsedMs >= timeoutMs) {
|
|
26
|
+
return { matched: false, timedOut: true, elapsedMs };
|
|
27
|
+
}
|
|
28
|
+
await sleep(Math.min(100, timeoutMs - elapsedMs));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as t from "typebox";
|
|
2
|
-
import { LIST_INPUT_SCHEMA, SESSION_OPTIONS_SCHEMA, api,
|
|
2
|
+
import { LIST_INPUT_SCHEMA, SESSION_OPTIONS_SCHEMA, api, cdpUrl, compactRecord } from "./shared.js";
|
|
3
3
|
export function registerSessionActions(rl) {
|
|
4
4
|
rl.registerAction("session.create", {
|
|
5
5
|
access: "write",
|
|
@@ -119,12 +119,7 @@ export function registerSessionActions(rl) {
|
|
|
119
119
|
inputSchema: t.Object({ id: t.String({ description: "Session ID" }), websocketUrl: t.Optional(t.String({ description: "Optional websocketUrl returned by session.create. If omitted, uses wss://connect.steel.dev with sessionId." })) }),
|
|
120
120
|
async execute(input, ctx) {
|
|
121
121
|
const { id, websocketUrl } = input;
|
|
122
|
-
|
|
123
|
-
if (websocketUrl) {
|
|
124
|
-
const sep = websocketUrl.includes("?") ? "&" : "?";
|
|
125
|
-
return { cdpUrl: `${websocketUrl}${sep}apiKey=${key}` };
|
|
126
|
-
}
|
|
127
|
-
return { cdpUrl: `wss://connect.steel.dev?apiKey=${key}&sessionId=${encodeURIComponent(id)}` };
|
|
122
|
+
return { cdpUrl: cdpUrl(ctx, id, websocketUrl) };
|
|
128
123
|
},
|
|
129
124
|
});
|
|
130
125
|
}
|
|
@@ -45,6 +45,18 @@ export async function api(ctx, path, options = {}) {
|
|
|
45
45
|
return JSON.parse(text);
|
|
46
46
|
return text;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* The CDP endpoint for a session. One definition: this URL carries the
|
|
50
|
+
* API key, so a second hand-written copy is a second place to leak it or
|
|
51
|
+
* to forget encoding.
|
|
52
|
+
*/
|
|
53
|
+
export function cdpUrl(ctx, sessionId, websocketUrl) {
|
|
54
|
+
const key = encodeURIComponent(apiKey(ctx));
|
|
55
|
+
if (websocketUrl) {
|
|
56
|
+
return `${websocketUrl}${websocketUrl.includes("?") ? "&" : "?"}apiKey=${key}`;
|
|
57
|
+
}
|
|
58
|
+
return `wss://connect.steel.dev?apiKey=${key}&sessionId=${encodeURIComponent(sessionId)}`;
|
|
59
|
+
}
|
|
48
60
|
export function compactRecord(input) {
|
|
49
61
|
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined && value !== null));
|
|
50
62
|
}
|