/** * @license * Copyright 2026 Steven Roussey * SPDX-License-Identifier: Apache-2.0 */ import type { IExecuteContext, TaskConfig, TaskEntitlements } from "@workglow/task-graph"; import { CreateWorkflow, Task } from "@workglow/task-graph"; import type { DataPortSchema, FromSchema } from "@workglow/util/schema"; import { linesFromText } from "../util/textLines"; export { linesFromText }; declare const inputSchema: { readonly type: "object"; readonly properties: { readonly url: { readonly type: "string"; readonly title: "URL"; readonly description: "URL to search (http://, https://). The Node/Electron build additionally accepts a `file://` URL or an absolute filesystem path, subject to the `filesystem:read` entitlement and any configured `roots`."; readonly format: "uri"; }; readonly pattern: { readonly type: "string"; readonly title: "Pattern"; readonly description: "Regular expression, or a literal string when fixedString is set"; }; readonly ignoreCase: { readonly type: "boolean"; readonly title: "Ignore Case"; readonly description: "Case-insensitive matching (-i)"; }; readonly fixedString: { readonly type: "boolean"; readonly title: "Fixed String"; readonly description: "Treat pattern as a literal string (-F)"; }; readonly invertMatch: { readonly type: "boolean"; readonly title: "Invert Match"; readonly description: "Select lines that do not match (-v)"; }; readonly onlyMatching: { readonly type: "boolean"; readonly title: "Only Matching"; readonly description: "Emit the matched substrings instead of whole lines, one per match (-o)"; }; readonly afterContext: { readonly type: "integer"; readonly title: "After Context"; readonly description: "Lines after each match (-A)"; readonly minimum: 0; }; readonly beforeContext: { readonly type: "integer"; readonly title: "Before Context"; readonly description: "Lines before each match (-B)"; readonly minimum: 0; }; readonly context: { readonly type: "integer"; readonly title: "Context"; readonly description: "Lines before and after each match (-C)"; readonly minimum: 0; }; readonly maxMatches: { readonly type: "integer"; readonly title: "Max Matches"; readonly description: "Stop after this many matching lines (-m)"; readonly minimum: 1; }; readonly existsOnly: { readonly type: "boolean"; readonly title: "Exists Only"; readonly description: "Return only whether a match exists (-q-like behavior)"; }; readonly countOnly: { readonly type: "boolean"; readonly title: "Count Only"; readonly description: "Return only the number of matching lines (-c-like behavior)"; }; readonly maxOutputLines: { readonly type: "integer"; readonly title: "Max Output Lines"; readonly description: "Maximum number of output lines, including context. Defaults to 10000; set this to override."; readonly minimum: 0; }; readonly maxOutputChars: { readonly type: "integer"; readonly title: "Max Output Characters"; readonly description: "Maximum number of output characters, including newlines. Defaults to 1000000; set this to override."; readonly minimum: 0; }; }; readonly required: readonly ["url", "pattern"]; readonly additionalProperties: false; }; declare const outputSchema: { readonly type: "object"; readonly properties: { readonly groups: { readonly type: "array"; readonly title: "Groups"; readonly description: "Contiguous match groups, including context lines"; readonly items: { readonly type: "object"; readonly properties: { readonly startLine: { readonly type: "integer"; readonly title: "Start Line"; }; readonly endLine: { readonly type: "integer"; readonly title: "End Line"; }; readonly lines: { readonly type: "array"; readonly items: { readonly type: "object"; readonly properties: { readonly line: { readonly type: "integer"; readonly title: "Line"; readonly description: "1-based line number"; }; readonly text: { readonly type: "string"; readonly title: "Text"; readonly description: "Line contents without the terminator"; }; readonly match: { readonly type: "boolean"; readonly title: "Match"; readonly description: "Whether this line matched the pattern"; }; }; readonly required: readonly ["line", "text", "match"]; readonly additionalProperties: false; }; }; }; readonly required: readonly ["startLine", "endLine", "lines"]; readonly additionalProperties: false; }; }; readonly matchCount: { readonly type: "integer"; readonly title: "Match Count"; readonly description: "Number of matching lines. With `existsOnly` the scan stops at the first match, so this is 1 rather than a full count."; }; readonly exists: { readonly type: "boolean"; readonly title: "Exists"; readonly description: "Whether any line matched"; }; readonly truncated: { readonly type: "boolean"; readonly title: "Truncated"; readonly description: "Search stopped before EOF because of an output or search limit"; }; }; readonly required: readonly ["groups", "matchCount", "exists", "truncated"]; readonly additionalProperties: false; }; export type FileGrepTaskInput = FromSchema; export type FileGrepTaskOutput = FromSchema; export type GrepOptions = Omit; /** * Matches a batch of lines at once. Batching is what makes an interruptible * matcher affordable — see `createBoundedRegexMatcher` on the server build, * whose per-call cost only amortizes over a batch. */ export interface GrepLineMatcher { readonly matchBatch: (texts: readonly string[]) => boolean[]; /** * Slices matched substrings out of each line, positionally aligned with * `texts`. The server matcher bounds this under the same interruptible * budget as {@link matchBatch}; without it, `onlyMatching` falls back to an * unbounded `exec` on the calling thread. */ readonly extractBatch?: (texts: readonly string[]) => string[][]; } export declare function createMatcher(pattern: string, options: GrepOptions): GrepLineMatcher; /** * Scans `lines`, matching in batches of {@link SECURITY_LIMITS.regexMatchBatchLines}. * * Abort and the overall search deadline are checked once per BATCH, not per * line: a single `regex.test` is uninterruptible, so per-line checks bought * nothing a hostile pattern could not ignore. The granularity that matters is * therefore the batch, and the matcher itself bounds how long one batch may run. */ export declare function grepLines(lines: AsyncIterable, pattern: string, options?: GrepOptions, signal?: AbortSignal, matcher?: GrepLineMatcher | undefined): Promise; /** * Task for grepping documents fetched over http(s). * * This build handles http and https only: the fetch routes through * `FetchUrlTask` -> `safeFetch` -> `classifyUrl`, which rejects every other * scheme. `file://` URLs and bare filesystem paths are handled only by the * server build (`FileGrepTask.server`), which requires the `filesystem:read` * entitlement. */ export declare class FileGrepTask extends Task { static type: string; static category: string; static title: string; static description: string; static cacheable: boolean; static hasDynamicEntitlements: boolean; /** * The owned `FetchUrlTask` does the network access, but an owned child is * created inside `execute()` and so is absent from the graph-start snapshot * `computeGraphEntitlements` takes over `graph.getTasks()`. Declaring the * fetch's entitlements here is what puts them in front of the enforcer. */ static entitlements(): TaskEntitlements; entitlements(): TaskEntitlements; static inputSchema(): DataPortSchema; static outputSchema(): DataPortSchema; /** * Seam for the matcher the scan runs. The server build overrides it to bound * regex matching with an interruptible time budget; there is no `vm` in a * browser, so a hostile pattern there still blocks that tab (a tab — not the * process hosting a local API). */ protected createLineMatcher(pattern: string, options: GrepOptions): GrepLineMatcher; execute(input: FileGrepTaskInput, context: IExecuteContext): Promise; } /** * Config for both builds. `roots` is declared here rather than beside the * server subclass because a `declare module` augmentation must state one type * across every file that contributes it, and both files augment `Workflow` * with `fileGrep`. Declaring it only on the server made the two disagree * (TS2717); declaring `TaskConfig` in both would keep them agreeing but drop * `roots` from `workflow.fileGrep(...)`, which is the API most callers use. */ export interface FileGrepTaskConfig extends TaskConfig { /** * Directories a local path must resolve inside, checked after symlinks are * resolved. Omitted means `[process.cwd()]`, NOT unrestricted — the * `filesystem:read` entitlement is only consulted when the embedder runs * with `enforceEntitlements` and a registered enforcer, so it cannot stand * in as the default fence. State {@link allowAnyRoot} to opt out. * * Honored only by the server build — the cross-platform class reaches * http(s) through `FetchUrlTask` and touches no filesystem. */ readonly roots?: readonly string[] | undefined; /** * Read any path the process can open, skipping containment entirely. Only * the literal `true` does so, and it is never implied by leaving `roots` * unset. */ readonly allowAnyRoot?: boolean | undefined; } export declare const fileGrep: (input: FileGrepTaskInput, config?: FileGrepTaskConfig) => Promise<{ exists: boolean; groups: { endLine: number; lines: { line: number; match: boolean; text: string; }[]; startLine: number; }[]; matchCount: number; truncated: boolean; }>; declare module "@workglow/task-graph" { interface Workflow { fileGrep: CreateWorkflow; } }