@anweat/dsh-browser 0.1.4 → 0.1.6

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/lib/index.js CHANGED
@@ -11,13 +11,24 @@ import path from 'node:path';
11
11
  import { Config, resolveConfig } from "./config.js";
12
12
  import { BrowserService } from "./browser-service.js";
13
13
  import { registerTools } from "./tools.js";
14
+ import { browserPolicyDecision } from "./approval-policy.js";
14
15
  export const name = 'dsh-browser';
15
16
  export const inject = ['tools'];
16
17
  export { Config };
18
+ export { BUILTIN_SCRIPTS, validateUserscript } from "./scripts.js";
17
19
  export function apply(ctx, config) {
18
20
  const resolved = resolveConfig(config);
19
21
  fs.mkdirSync(resolved.snapshotDir, { recursive: true });
20
22
  const service = new BrowserService(resolved);
23
+ // Arbitrary userscripts, general OpenCLI commands, and mutating multi-step
24
+ // recipes must pass through the harness' native one-shot approval flow. The
25
+ // policy fails closed when no approval service or calling agent is present.
26
+ ctx.on('tools/pre-execute', async (exec, next) => {
27
+ const downstream = await next();
28
+ if (downstream.kind !== 'allow')
29
+ return downstream;
30
+ return browserPolicyDecision(exec.name, exec.arguments);
31
+ });
21
32
  // Provide the `browser` service so consumers (web-search-pro) can inject it.
22
33
  // ctx.provide is scoped to this plugin's fiber; the browser instance itself
23
34
  // is closed via the effect disposer below.
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,MAAM,EAAE,aAAa,EAAuB,MAAM,aAAa,CAAA;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,MAAM,CAAC,MAAM,IAAI,GAAG,aAAa,CAAA;AACjC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,CAAA;AAE/B,OAAO,EAAE,MAAM,EAAE,CAAA;AAIjB,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,MAAc;IAChD,MAAM,QAAQ,GAAmB,aAAa,CAAC,MAAM,CAAC,CAAA;IACtD,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAEvD,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC,QAAQ,CAAC,CAAA;IAE5C,6EAA6E;IAC7E,4EAA4E;IAC5E,2CAA2C;IAC3C,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;IAC/B,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,KAAK,EAAE,CAAC,CAAA;IAE5C,aAAa,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;IAErC,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;QACrB,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;YAC/D,EAAE,CAAC,cAAc,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC;gBAC3C,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBAC5B,MAAM,EAAE,IAAI;gBACZ,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,cAAc,EAAE,QAAQ,CAAC,cAAc;gBACvC,WAAW,EAAE,QAAQ,CAAC,WAAW;aAClC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAA;QACpB,CAAC;QAAC,MAAM,CAAC,CAAC,2BAA2B,CAAC,CAAC;IACzC,CAAC;IAED,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,8BAA8B,GAAG,QAAQ,CAAC,OAAO,GAAG,YAAY,GAAG,QAAQ,CAAC,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAA;AACvJ,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,MAAM,EAAE,aAAa,EAAuB,MAAM,aAAa,CAAA;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAE5D,MAAM,CAAC,MAAM,IAAI,GAAG,aAAa,CAAA;AACjC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,CAAA;AAE/B,OAAO,EAAE,MAAM,EAAE,CAAA;AAOjB,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAElE,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,MAAc;IAChD,MAAM,QAAQ,GAAmB,aAAa,CAAC,MAAM,CAAC,CAAA;IACtD,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAEvD,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC,QAAQ,CAAC,CAAA;IAE5C,2EAA2E;IAC3E,4EAA4E;IAC5E,4EAA4E;IAC5E,GAAG,CAAC,EAAE,CAAC,mBAAmB,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;QAC/C,MAAM,UAAU,GAAG,MAAM,IAAI,EAAE,CAAA;QAC/B,IAAI,UAAU,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,UAAU,CAAA;QAClD,OAAO,qBAAqB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;IACzD,CAAC,CAAC,CAAA;IAEF,6EAA6E;IAC7E,4EAA4E;IAC5E,2CAA2C;IAC3C,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;IAC/B,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,KAAK,EAAE,CAAC,CAAA;IAE5C,aAAa,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;IAErC,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;QACrB,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;YAC/D,EAAE,CAAC,cAAc,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC;gBAC3C,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBAC5B,MAAM,EAAE,IAAI;gBACZ,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,cAAc,EAAE,QAAQ,CAAC,cAAc;gBACvC,WAAW,EAAE,QAAQ,CAAC,WAAW;aAClC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAA;QACpB,CAAC;QAAC,MAAM,CAAC,CAAC,2BAA2B,CAAC,CAAC;IACzC,CAAC;IAED,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,8BAA8B,GAAG,QAAQ,CAAC,OAAO,GAAG,YAAY,GAAG,QAAQ,CAAC,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAA;AACvJ,CAAC"}
@@ -0,0 +1,32 @@
1
+ export type RuleStep = {
2
+ type: 'waitFor';
3
+ selector: string;
4
+ timeoutMs?: number;
5
+ optional?: boolean;
6
+ } | {
7
+ type: 'click';
8
+ selector: string;
9
+ timeoutMs?: number;
10
+ optional?: boolean;
11
+ } | {
12
+ type: 'scroll';
13
+ deltaY?: number;
14
+ repeat?: number;
15
+ waitMs?: number;
16
+ } | {
17
+ type: 'wait';
18
+ waitMs: number;
19
+ };
20
+ export interface RulePackConfig {
21
+ matches: string[];
22
+ initScriptPath?: string;
23
+ /** Required when initScriptPath is set. */
24
+ initScriptSha256?: string;
25
+ steps?: RuleStep[];
26
+ }
27
+ export interface ResolvedRulePack extends RulePackConfig {
28
+ id: string;
29
+ steps: RuleStep[];
30
+ }
31
+ export declare function resolveRulePack(packs: Record<string, RulePackConfig>, id: string | undefined, targetUrl: string): ResolvedRulePack | undefined;
32
+ export declare function applyRuleSteps(page: any, pack: ResolvedRulePack | undefined): Promise<void>;
@@ -0,0 +1,77 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ function matchHost(hostname, values) {
4
+ const host = hostname.toLowerCase().replace(/\.$/, '');
5
+ return values.some(value => {
6
+ const domain = value.toLowerCase().trim().replace(/^\*\./, '').replace(/\.$/, '');
7
+ return domain.length > 0 && (host === domain || host.endsWith('.' + domain));
8
+ });
9
+ }
10
+ function validateStep(step) {
11
+ if ((step.type === 'waitFor' || step.type === 'click') && (!step.selector || step.selector.length > 500))
12
+ throw new Error(step.type + ' selector is invalid');
13
+ if ((step.type === 'waitFor' || step.type === 'click') && (step.timeoutMs ?? 15_000) > 30_000)
14
+ throw new Error(step.type + ' timeoutMs exceeds 30000');
15
+ if (step.type === 'wait' && (step.waitMs < 0 || step.waitMs > 10_000))
16
+ throw new Error('waitMs must be between 0 and 10000');
17
+ if (step.type === 'scroll') {
18
+ if ((step.repeat ?? 1) < 1 || (step.repeat ?? 1) > 10)
19
+ throw new Error('scroll repeat must be between 1 and 10');
20
+ if ((step.waitMs ?? 300) < 0 || (step.waitMs ?? 300) > 5_000)
21
+ throw new Error('scroll waitMs must be between 0 and 5000');
22
+ if (Math.abs(step.deltaY ?? 2000) > 20_000)
23
+ throw new Error('scroll deltaY exceeds 20000');
24
+ }
25
+ }
26
+ export function resolveRulePack(packs, id, targetUrl) {
27
+ if (!id)
28
+ return undefined;
29
+ const pack = packs[id];
30
+ if (!pack)
31
+ throw new Error('unknown rule pack: ' + id);
32
+ const url = new URL(targetUrl);
33
+ if (!matchHost(url.hostname, pack.matches ?? []))
34
+ throw new Error('rule pack ' + id + ' is not allowed for ' + url.hostname);
35
+ const steps = pack.steps ?? [];
36
+ if (steps.length > 25)
37
+ throw new Error('rule pack has more than 25 steps');
38
+ steps.forEach(validateStep);
39
+ if (pack.initScriptPath) {
40
+ if (!pack.initScriptSha256 || !/^[a-f\d]{64}$/i.test(pack.initScriptSha256))
41
+ throw new Error('rule pack init script requires a SHA-256 hash');
42
+ const stat = fs.statSync(pack.initScriptPath);
43
+ if (stat.size > 64 * 1024)
44
+ throw new Error('rule pack init script exceeds 65536 bytes');
45
+ const actual = crypto.createHash('sha256').update(fs.readFileSync(pack.initScriptPath)).digest('hex');
46
+ if (actual.toLowerCase() !== pack.initScriptSha256.toLowerCase())
47
+ throw new Error('rule pack init script hash mismatch');
48
+ }
49
+ return { id, ...pack, steps };
50
+ }
51
+ export async function applyRuleSteps(page, pack) {
52
+ if (!pack)
53
+ return;
54
+ for (const step of pack.steps) {
55
+ try {
56
+ if (step.type === 'waitFor')
57
+ await page.waitForSelector(step.selector, { timeout: step.timeoutMs ?? 15_000 });
58
+ else if (step.type === 'click') {
59
+ await page.waitForSelector(step.selector, { timeout: step.timeoutMs ?? 15_000 });
60
+ await page.click(step.selector);
61
+ }
62
+ else if (step.type === 'wait')
63
+ await page.waitForTimeout(step.waitMs);
64
+ else
65
+ for (let i = 0; i < (step.repeat ?? 1); i++) {
66
+ await page.mouse?.wheel(0, step.deltaY ?? 2000);
67
+ await page.waitForTimeout(step.waitMs ?? 300);
68
+ }
69
+ }
70
+ catch (error) {
71
+ if ((step.type === 'waitFor' || step.type === 'click') && step.optional)
72
+ continue;
73
+ throw error;
74
+ }
75
+ }
76
+ }
77
+ //# sourceMappingURL=rule-packs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-packs.js","sourceRoot":"","sources":["../src/rule-packs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Tampermonkey-compatible metadata validation and bounded userscript execution.
3
+ * Only `@grant none` is supported; GM_* APIs are intentionally not emulated.
4
+ * @module dsh-browser/scripts
5
+ */
6
+ export interface UserscriptMetadata {
7
+ name: string;
8
+ description?: string;
9
+ matches: string[];
10
+ excludes: string[];
11
+ grants: string[];
12
+ }
13
+ export interface UserscriptValidation {
14
+ valid: boolean;
15
+ sha256: string;
16
+ bytes: number;
17
+ metadata: UserscriptMetadata;
18
+ capabilities: string[];
19
+ errors: string[];
20
+ warnings: string[];
21
+ }
22
+ export interface BuiltinScript {
23
+ id: string;
24
+ name: string;
25
+ description: string;
26
+ source: string;
27
+ }
28
+ export declare const BUILTIN_SCRIPTS: readonly BuiltinScript[];
29
+ export declare function matchUserscriptPattern(pattern: string, targetUrl: string): boolean;
30
+ export declare function validateUserscript(source: string, targetUrl?: string): UserscriptValidation;
31
+ export declare function builtinScript(id: string): BuiltinScript;
32
+ export declare function executeUserscript(page: any, source: string, maxResultChars?: number): Promise<{
33
+ resultJson: string;
34
+ truncated: boolean;
35
+ }>;
package/lib/scripts.js ADDED
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Tampermonkey-compatible metadata validation and bounded userscript execution.
3
+ * Only `@grant none` is supported; GM_* APIs are intentionally not emulated.
4
+ * @module dsh-browser/scripts
5
+ */
6
+ import crypto from 'node:crypto';
7
+ const header = (name, description) => `// ==UserScript==
8
+ // @name ${name}
9
+ // @description ${description}
10
+ // @match *://*/*
11
+ // @grant none
12
+ // ==/UserScript==`;
13
+ export const BUILTIN_SCRIPTS = [
14
+ {
15
+ id: 'article-clean',
16
+ name: 'Article Clean Reader',
17
+ description: 'Extract title, canonical URL, headings, and readable article text.',
18
+ source: `${header('Article Clean Reader', 'Extract readable article content without mutating the page.')}
19
+ const root = document.querySelector('article, main, [role="main"]') || document.body
20
+ return {
21
+ title: document.title,
22
+ canonicalUrl: document.querySelector('link[rel="canonical"]')?.href || location.href,
23
+ headings: [...root.querySelectorAll('h1,h2,h3')].slice(0, 100).map(el => (el.textContent || '').trim()).filter(Boolean),
24
+ text: (root.innerText || root.textContent || '').trim().slice(0, 50000),
25
+ }`,
26
+ },
27
+ {
28
+ id: 'links',
29
+ name: 'Link Inventory',
30
+ description: 'Extract up to 200 visible links with absolute URLs.',
31
+ source: `${header('Link Inventory', 'Extract visible links without clicking them.')}
32
+ return [...document.querySelectorAll('a[href]')].slice(0, 200).map(a => ({
33
+ text: (a.textContent || '').trim().slice(0, 500),
34
+ url: a.href,
35
+ })).filter(item => item.url)`,
36
+ },
37
+ {
38
+ id: 'jsonld',
39
+ name: 'JSON-LD Extractor',
40
+ description: 'Read structured JSON-LD blocks from the page.',
41
+ source: `${header('JSON-LD Extractor', 'Extract JSON-LD structured data.')}
42
+ return [...document.querySelectorAll('script[type="application/ld+json"]')].slice(0, 20).map(node => {
43
+ try { return JSON.parse(node.textContent || 'null') } catch { return { invalid: true, text: (node.textContent || '').slice(0, 2000) } }
44
+ })`,
45
+ },
46
+ {
47
+ id: 'forms',
48
+ name: 'Form Structure',
49
+ description: 'Describe forms and controls without returning current values.',
50
+ source: `${header('Form Structure', 'Describe form controls without exposing entered values.')}
51
+ return [...document.forms].slice(0, 30).map((form, formIndex) => ({
52
+ form: formIndex,
53
+ action: form.action,
54
+ method: form.method,
55
+ controls: [...form.elements].slice(0, 100).map(el => ({
56
+ tag: el.tagName.toLowerCase(), type: el.type || '', name: el.name || '', id: el.id || '', required: !!el.required,
57
+ })),
58
+ }))`,
59
+ },
60
+ ];
61
+ function metadataOf(source) {
62
+ const start = source.indexOf('// ==UserScript==');
63
+ const end = source.indexOf('// ==/UserScript==');
64
+ const rows = start >= 0 && end > start ? source.slice(start, end).split(/\r?\n/) : [];
65
+ const values = new Map();
66
+ for (const row of rows) {
67
+ const match = row.match(/^\s*\/\/\s*@([\w-]+)\s*(.*?)\s*$/);
68
+ if (!match)
69
+ continue;
70
+ const key = match[1].toLowerCase();
71
+ const list = values.get(key) ?? [];
72
+ list.push(match[2]);
73
+ values.set(key, list);
74
+ }
75
+ return {
76
+ name: values.get('name')?.[0] || 'External userscript',
77
+ ...values.get('description')?.[0] ? { description: values.get('description')[0] } : {},
78
+ matches: values.get('match') ?? [],
79
+ excludes: values.get('exclude-match') ?? [],
80
+ grants: values.get('grant') ?? [],
81
+ requires: values.get('require') ?? [],
82
+ };
83
+ }
84
+ function globPath(pattern) {
85
+ return new RegExp('^' + pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*') + '$');
86
+ }
87
+ export function matchUserscriptPattern(pattern, targetUrl) {
88
+ const parsed = pattern.match(/^(\*|https?|file|ftp):\/\/([^/]+)(\/.*)$/i);
89
+ if (!parsed)
90
+ return false;
91
+ const url = new URL(targetUrl);
92
+ if (parsed[1] === '*' ? !/^https?:$/.test(url.protocol) : url.protocol !== parsed[1].toLowerCase() + ':')
93
+ return false;
94
+ const wantedHost = parsed[2].toLowerCase();
95
+ const host = url.hostname.toLowerCase();
96
+ const hostMatches = wantedHost === '*'
97
+ || (wantedHost.startsWith('*.') ? host === wantedHost.slice(2) || host.endsWith('.' + wantedHost.slice(2)) : host === wantedHost);
98
+ return hostMatches && globPath(parsed[3]).test(url.pathname + url.search + url.hash);
99
+ }
100
+ function capabilitiesOf(source) {
101
+ const capabilities = new Set(['dom-read']);
102
+ if (/\b(fetch|XMLHttpRequest|WebSocket|sendBeacon)\b/.test(source))
103
+ capabilities.add('network');
104
+ if (/\b(localStorage|sessionStorage|indexedDB|document\.cookie)\b/.test(source))
105
+ capabilities.add('storage');
106
+ if (/\.click\s*\(|dispatchEvent\s*\(|\.submit\s*\(|\blocation\s*=|history\.(pushState|replaceState)/.test(source))
107
+ capabilities.add('interaction');
108
+ if (/\b(remove|append|prepend|replaceWith|insertAdjacentHTML)\s*\(|\.innerHTML\s*=|\.textContent\s*=/.test(source))
109
+ capabilities.add('dom-write');
110
+ return [...capabilities];
111
+ }
112
+ export function validateUserscript(source, targetUrl) {
113
+ const bytes = Buffer.byteLength(source, 'utf8');
114
+ const sha256 = crypto.createHash('sha256').update(source, 'utf8').digest('hex');
115
+ const parsed = metadataOf(source);
116
+ const errors = [];
117
+ const warnings = [];
118
+ if (bytes < 1 || bytes > 64 * 1024)
119
+ errors.push('source must contain between 1 and 65536 UTF-8 bytes');
120
+ if (!source.includes('// ==UserScript==') || !source.includes('// ==/UserScript=='))
121
+ errors.push('userscript metadata block is required');
122
+ if (parsed.matches.length < 1)
123
+ errors.push('at least one @match is required');
124
+ if (parsed.matches.some(pattern => !/^(\*|https?):\/\//i.test(pattern)))
125
+ errors.push('only HTTP(S) @match patterns are supported');
126
+ if (parsed.requires.length > 0)
127
+ errors.push('@require is not supported; external code must be supplied inline for approval');
128
+ const invalidGrants = parsed.grants.filter(grant => grant !== 'none');
129
+ if (invalidGrants.length > 0)
130
+ errors.push('unsupported @grant values: ' + invalidGrants.join(', '));
131
+ if (parsed.grants.length === 0)
132
+ warnings.push('missing @grant; execution still provides no GM_* APIs');
133
+ if (targetUrl !== undefined) {
134
+ let matched = false;
135
+ try {
136
+ matched = parsed.matches.some(pattern => matchUserscriptPattern(pattern, targetUrl))
137
+ && !parsed.excludes.some(pattern => matchUserscriptPattern(pattern, targetUrl));
138
+ }
139
+ catch {
140
+ errors.push('target URL is invalid');
141
+ }
142
+ if (!matched)
143
+ errors.push('target URL is outside the userscript @match scope');
144
+ }
145
+ return {
146
+ valid: errors.length === 0,
147
+ sha256,
148
+ bytes,
149
+ metadata: {
150
+ name: parsed.name,
151
+ ...parsed.description ? { description: parsed.description } : {},
152
+ matches: parsed.matches,
153
+ excludes: parsed.excludes,
154
+ grants: parsed.grants,
155
+ },
156
+ capabilities: capabilitiesOf(source),
157
+ errors,
158
+ warnings,
159
+ };
160
+ }
161
+ export function builtinScript(id) {
162
+ const script = BUILTIN_SCRIPTS.find(candidate => candidate.id === id);
163
+ if (!script)
164
+ throw new Error('unknown built-in script: ' + id);
165
+ return script;
166
+ }
167
+ export async function executeUserscript(page, source, maxResultChars = 100_000) {
168
+ const result = await page.evaluate(`(async () => {\n${source}\n})()`);
169
+ let resultJson;
170
+ try {
171
+ resultJson = JSON.stringify(result ?? null);
172
+ }
173
+ catch {
174
+ resultJson = JSON.stringify({ unserializable: true, text: String(result) });
175
+ }
176
+ const truncated = resultJson.length > maxResultChars;
177
+ if (truncated)
178
+ resultJson = resultJson.slice(0, maxResultChars) + '…';
179
+ return { resultJson, truncated };
180
+ }
181
+ //# sourceMappingURL=scripts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scripts.js","sourceRoot":"","sources":["../src/scripts.ts"],"names":[],"mappings":"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"}