@xandreed/plugin-workspace 0.0.0-stage → 0.15.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/LICENSE +21 -0
- package/README.md +4 -2
- package/dist/bash-tool.adapter.d.ts +15 -0
- package/dist/bash-tool.adapter.js +73 -0
- package/dist/bash.adapter.d.ts +81 -0
- package/dist/bash.adapter.js +112 -0
- package/dist/file-system.adapter.d.ts +24 -0
- package/dist/file-system.adapter.js +52 -0
- package/dist/image-resizer.adapter.d.ts +5 -0
- package/dist/image-resizer.adapter.js +68 -0
- package/dist/image-resizer.entity.d.ts +23 -0
- package/dist/image-resizer.entity.functions.d.ts +15 -0
- package/dist/image-resizer.entity.functions.js +4 -0
- package/dist/image-resizer.entity.js +7 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +38 -0
- package/dist/local-system.entity.d.ts +47 -0
- package/dist/local-system.entity.functions.d.ts +53 -0
- package/dist/local-system.entity.functions.js +46 -0
- package/dist/local-system.entity.js +12 -0
- package/dist/plugin.adapter.d.ts +183 -0
- package/dist/plugin.adapter.js +38 -0
- package/dist/read-tools.adapter.d.ts +39 -0
- package/dist/read-tools.adapter.js +109 -0
- package/dist/ripgrep.adapter.d.ts +74 -0
- package/dist/ripgrep.adapter.js +56 -0
- package/dist/ripgrep.entity.d.ts +63 -0
- package/dist/ripgrep.entity.functions.d.ts +87 -0
- package/dist/ripgrep.entity.functions.js +62 -0
- package/dist/ripgrep.entity.js +10 -0
- package/dist/shell.entity.functions.d.ts +2 -0
- package/dist/shell.entity.functions.js +1 -0
- package/dist/skills/skills-directory.adapter.d.ts +68 -0
- package/dist/skills/skills-directory.adapter.js +30 -0
- package/dist/skills/skills-directory.entity.d.ts +99 -0
- package/dist/skills/skills-directory.entity.functions.d.ts +88 -0
- package/dist/skills/skills-directory.entity.functions.js +45 -0
- package/dist/skills/skills-directory.entity.js +35 -0
- package/dist/text-edit.entity.d.ts +32 -0
- package/dist/text-edit.entity.functions.d.ts +32 -0
- package/dist/text-edit.entity.functions.js +68 -0
- package/dist/text-edit.entity.js +15 -0
- package/dist/text-search.entity.d.ts +23 -0
- package/dist/text-search.entity.functions.d.ts +58 -0
- package/dist/text-search.entity.functions.js +30 -0
- package/dist/text-search.entity.js +10 -0
- package/dist/text-window.entity.d.ts +46 -0
- package/dist/text-window.entity.functions.d.ts +54 -0
- package/dist/text-window.entity.functions.js +88 -0
- package/dist/text-window.entity.js +21 -0
- package/dist/workspace-path.entity.d.ts +43 -0
- package/dist/workspace-path.entity.functions.d.ts +90 -0
- package/dist/workspace-path.entity.functions.js +88 -0
- package/dist/workspace-path.entity.js +9 -0
- package/dist/workspace-tools.adapter.d.ts +66 -0
- package/dist/workspace-tools.adapter.js +60 -0
- package/dist/workspace-tools.entity.d.ts +552 -0
- package/dist/workspace-tools.entity.functions.d.ts +126 -0
- package/dist/workspace-tools.entity.functions.js +180 -0
- package/dist/workspace-tools.entity.js +154 -0
- package/dist/write-tools.adapter.d.ts +12 -0
- package/dist/write-tools.adapter.js +79 -0
- package/package.json +30 -4
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
/**
|
|
3
|
+
* How `BashLive` (and `localSystemPlugin`) runs commands. `shell` is the
|
|
4
|
+
* program commands run with (`<shell> -c <command>`): a name looked up once,
|
|
5
|
+
* when the layer is built, on the parent environment's `PATH` (its absolute
|
|
6
|
+
* entries), or an absolute path; there is no fallback, so a host without
|
|
7
|
+
* bash sets `shell: "sh"` (or another path) itself. With `killBackground`, a
|
|
8
|
+
* command's whole process group is killed when the command returns, so
|
|
9
|
+
* nothing it started in the background outlives it (a timeout or an
|
|
10
|
+
* interrupted caller always kills the group). `exitGraceMs` is how long,
|
|
11
|
+
* after the command exits, its output may take to close (a background
|
|
12
|
+
* process that kept it open) before the stream ends without the rest. The
|
|
13
|
+
* output pipes close then, so a background process kept with
|
|
14
|
+
* `killBackground: false` must have its own output (`cmd > log 2>&1 &`):
|
|
15
|
+
* one still writing to the command's dies of `SIGPIPE` or gets `EPIPE`.
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
export declare const LocalSystemConfig: Schema.Struct<{
|
|
20
|
+
readonly shell: Schema.NonEmptyString;
|
|
21
|
+
readonly killBackground: Schema.Boolean;
|
|
22
|
+
readonly exitGraceMs: Schema.Int;
|
|
23
|
+
/** The host's base environment, or null to inherit from the agent process. Filtered per command by `resolveBashEnv`. */
|
|
24
|
+
readonly parentEnv: Schema.NullOr<Schema.$Record<Schema.String, Schema.String>>;
|
|
25
|
+
}>;
|
|
26
|
+
export type LocalSystemConfig = typeof LocalSystemConfig.Type;
|
|
27
|
+
/** @public */
|
|
28
|
+
export declare const localSystemDefaults: LocalSystemConfig;
|
|
29
|
+
/**
|
|
30
|
+
* `BashLive`'s options: the plugin's configuration, each part optional, and
|
|
31
|
+
* `parentEnv`, the environment commands inherit from (filtered by
|
|
32
|
+
* `resolveBashEnv`) and the shell is looked up on; `process.env` when not
|
|
33
|
+
* given or null.
|
|
34
|
+
*
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
export type BashLiveOptions = Partial<Omit<LocalSystemConfig, "parentEnv">> & {
|
|
38
|
+
readonly parentEnv?: Readonly<Record<string, string | undefined>> | null;
|
|
39
|
+
};
|
|
40
|
+
/** How a spawned process ended, as Node reports it, and whether the adapter killed it for running past its timeout. */
|
|
41
|
+
export declare const ProcessEnd: Schema.Struct<{
|
|
42
|
+
readonly code: Schema.NullOr<Schema.Int>;
|
|
43
|
+
readonly signal: Schema.NullOr<Schema.String>;
|
|
44
|
+
readonly timedOut: Schema.Boolean;
|
|
45
|
+
readonly durationMs: Schema.Number;
|
|
46
|
+
}>;
|
|
47
|
+
export type ProcessEnd = typeof ProcessEnd.Type;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Option, Result } from "effect";
|
|
2
|
+
import type { BashExit, FsReason } from "@xandreed/core";
|
|
3
|
+
import type { ProcessEnd } from "./local-system.entity.js";
|
|
4
|
+
/** A thrown value's message, for the `message` of the error it becomes. */
|
|
5
|
+
export declare const errorMessageOf: (error: unknown) => string;
|
|
6
|
+
/**
|
|
7
|
+
* The `FsReason` of a failed node:fs call, from its error code: `ENOENT`
|
|
8
|
+
* is `NotFound`, `EEXIST` `Exists`, `EISDIR` (and Node's `ERR_FS_EISDIR`)
|
|
9
|
+
* `IsDirectory`, `ENOTDIR` `NotDirectory`, `ENOTEMPTY` `NotEmpty`, `EACCES`
|
|
10
|
+
* and `EPERM` `Permission`; `EINVAL`, a symbolic link loop (`ELOOP`), a
|
|
11
|
+
* name too long and an unusable argument (a path with a NUL byte) are
|
|
12
|
+
* `Invalid`; anything else, or no code at all, is `Io`.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
export declare const fsReasonOf: (error: unknown) => FsReason;
|
|
17
|
+
export declare const rgReasonOf: (code: number) => FsReason;
|
|
18
|
+
/**
|
|
19
|
+
* Whether a write to a command's standard input failed only because the
|
|
20
|
+
* command no longer reads it: it exited or closed its input first (`EPIPE`),
|
|
21
|
+
* or the stream was already torn down (`ERR_STREAM_DESTROYED`). The command
|
|
22
|
+
* is free to ignore its input, so these are not failures.
|
|
23
|
+
*/
|
|
24
|
+
export declare const isClosedInput: (error: unknown) => boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Where to look for an executable: an absolute path is itself; a bare name
|
|
27
|
+
* is tried in each absolute directory of `path` (a `PATH` value), in order;
|
|
28
|
+
* relative and empty entries, which would depend on the current directory,
|
|
29
|
+
* are skipped, as is a relative name with a slash.
|
|
30
|
+
*/
|
|
31
|
+
export declare const executableCandidates: (name: string, path: Option.Option<string>) => ReadonlyArray<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Where `remove(path)` acts, or why it never does. Trailing slashes are
|
|
34
|
+
* ignored, so a link named with one is still the link. The root (`/`, `//`)
|
|
35
|
+
* is refused, and so is a path whose last segment is `.` or `..`: such a
|
|
36
|
+
* path names a directory only by where it leads (`<link>/..` can be the
|
|
37
|
+
* root), so it is never removed. Otherwise the place is the parent as
|
|
38
|
+
* written (to be resolved, links and all) and the last name, never followed.
|
|
39
|
+
*/
|
|
40
|
+
export declare const removalPlace: (path: string) => Result.Result<{
|
|
41
|
+
readonly parent: string;
|
|
42
|
+
readonly name: string;
|
|
43
|
+
}, string>;
|
|
44
|
+
/** `name` in the directory `dir` (an absolute, resolved path). */
|
|
45
|
+
export declare const pathIn: (dir: string, name: string) => string;
|
|
46
|
+
/**
|
|
47
|
+
* A process's end as the `BashExit` the stream closes with. A process the
|
|
48
|
+
* adapter killed for its timeout ends `timeout` (it has no exit code, and its
|
|
49
|
+
* signal is the one that killed it); one a signal killed ends `signal`;
|
|
50
|
+
* otherwise it exited, with its code. A process that exited on its own just
|
|
51
|
+
* as its timeout came keeps its exit code and ends `exit`.
|
|
52
|
+
*/
|
|
53
|
+
export declare const bashExitOf: (end: ProcessEnd) => BashExit;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Option, Result } from "effect";
|
|
2
|
+
import { errorCodeOf } from "@xandreed/core";
|
|
3
|
+
export const errorMessageOf = (error) => error instanceof Error ? error.message : String(error);
|
|
4
|
+
const reasons = new Map([
|
|
5
|
+
["ENOENT", "NotFound"],
|
|
6
|
+
["EEXIST", "Exists"],
|
|
7
|
+
["EISDIR", "IsDirectory"],
|
|
8
|
+
["ERR_FS_EISDIR", "IsDirectory"],
|
|
9
|
+
["ENOTDIR", "NotDirectory"],
|
|
10
|
+
["ENOTEMPTY", "NotEmpty"],
|
|
11
|
+
["EACCES", "Permission"],
|
|
12
|
+
["EPERM", "Permission"],
|
|
13
|
+
["EINVAL", "Invalid"],
|
|
14
|
+
["ELOOP", "Invalid"],
|
|
15
|
+
["ENAMETOOLONG", "Invalid"],
|
|
16
|
+
["ERR_INVALID_ARG_VALUE", "Invalid"]
|
|
17
|
+
]);
|
|
18
|
+
export const fsReasonOf = (error) => Option.match(errorCodeOf(error), {
|
|
19
|
+
onNone: () => "Io",
|
|
20
|
+
onSome: (code) => reasons.get(code) ?? "Io"
|
|
21
|
+
});
|
|
22
|
+
const rgReasons = new Map([
|
|
23
|
+
[1, "Permission"],
|
|
24
|
+
[13, "Permission"],
|
|
25
|
+
[2, "NotFound"],
|
|
26
|
+
[20, "NotDirectory"],
|
|
27
|
+
[21, "IsDirectory"],
|
|
28
|
+
[22, "Invalid"],
|
|
29
|
+
[36, "Invalid"],
|
|
30
|
+
[40, "Invalid"],
|
|
31
|
+
[62, "Invalid"],
|
|
32
|
+
[63, "Invalid"]
|
|
33
|
+
]);
|
|
34
|
+
export const rgReasonOf = (code) => rgReasons.get(code) ?? "Io", isClosedInput = (error) => Option.match(errorCodeOf(error), { onNone: () => !1, onSome: (code) => code === "EPIPE" || code === "ERR_STREAM_DESTROYED" }), executableCandidates = (name, path) => name.startsWith("/") ? [name] : name.includes("/") || name.length === 0 ? [] : Option.getOrElse(path, () => "").split(":").filter((dir) => dir.startsWith("/")).map((dir) => `${dir.endsWith("/") ? dir.slice(0, -1) : dir}/${name}`), removalPlace = (path) => {
|
|
35
|
+
const segments = path.split("/").filter((segment) => segment.length > 0);
|
|
36
|
+
return !path.startsWith("/") ? Result.fail(`paths must be absolute: ${path}`) : Option.match(Option.fromNullishOr(segments.at(-1)), {
|
|
37
|
+
onNone: () => Result.fail("the root directory is never removed"),
|
|
38
|
+
onSome: (name) => name === "." || name === ".." ? Result.fail(`a path whose last segment is ${name} is never removed: ${path}`) : Result.succeed({ parent: `/${segments.slice(0, -1).join("/")}`, name })
|
|
39
|
+
});
|
|
40
|
+
}, pathIn = (dir, name) => dir.endsWith("/") ? `${dir}${name}` : `${dir}/${name}`, bashExitOf = (end) => ({
|
|
41
|
+
_tag: "BashExit",
|
|
42
|
+
termination: end.code !== null ? "exit" : end.timedOut ? "timeout" : end.signal !== null ? "signal" : "exit",
|
|
43
|
+
exitCode: Option.fromNullishOr(end.code),
|
|
44
|
+
signal: Option.fromNullishOr(end.signal),
|
|
45
|
+
durationMs: end.durationMs
|
|
46
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
export const LocalSystemConfig = Schema.Struct({
|
|
3
|
+
shell: Schema.NonEmptyString,
|
|
4
|
+
killBackground: Schema.Boolean,
|
|
5
|
+
exitGraceMs: Schema.Int.pipe(Schema.check(Schema.isBetween({ minimum: 0, maximum: 60000 }))),
|
|
6
|
+
parentEnv: Schema.NullOr(Schema.Record(Schema.String, Schema.String))
|
|
7
|
+
}), localSystemDefaults = { shell: "bash", killBackground: !0, exitGraceMs: 500, parentEnv: null }, ProcessEnd = Schema.Struct({
|
|
8
|
+
code: Schema.NullOr(Schema.Int),
|
|
9
|
+
signal: Schema.NullOr(Schema.String),
|
|
10
|
+
timedOut: Schema.Boolean,
|
|
11
|
+
durationMs: Schema.Number
|
|
12
|
+
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { Layer } from "effect";
|
|
2
|
+
import { Bash, Capabilities, FileSystem, ImageResizer } from "@xandreed/core";
|
|
3
|
+
/**
|
|
4
|
+
* The machine the agent runs on as its `FileSystem` and `Bash`: the local
|
|
5
|
+
* disk (`FileSystemLive`) and commands run with the configured shell
|
|
6
|
+
* (`BashLive`), built once per agent. Activation fails when the shell is not
|
|
7
|
+
* installed. Commands inherit `parentEnv` (the host process's environment
|
|
8
|
+
* when null) through `resolveBashEnv`'s allowlist.
|
|
9
|
+
*/
|
|
10
|
+
export declare const localSystemPlugin: import("@xandreed/core").TypedPlugin<{
|
|
11
|
+
readonly shell: string;
|
|
12
|
+
readonly killBackground: boolean;
|
|
13
|
+
readonly exitGraceMs: number;
|
|
14
|
+
readonly parentEnv: {
|
|
15
|
+
readonly [x: string]: string;
|
|
16
|
+
} | null;
|
|
17
|
+
}, {
|
|
18
|
+
readonly shell: string;
|
|
19
|
+
readonly killBackground: boolean;
|
|
20
|
+
readonly exitGraceMs: number;
|
|
21
|
+
readonly parentEnv: {
|
|
22
|
+
readonly [x: string]: string;
|
|
23
|
+
} | null;
|
|
24
|
+
}, FileSystem | Bash, import("@xandreed/core").BashError, never>;
|
|
25
|
+
/**
|
|
26
|
+
* The local system as a typed layer: provides FileSystem and Bash; requires nothing.
|
|
27
|
+
*
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
export declare const LocalSystemPluginLive: (options?: {
|
|
31
|
+
readonly shell?: string;
|
|
32
|
+
readonly killBackground?: boolean;
|
|
33
|
+
readonly exitGraceMs?: number;
|
|
34
|
+
readonly parentEnv?: {
|
|
35
|
+
readonly [x: string]: string | undefined;
|
|
36
|
+
} | null;
|
|
37
|
+
} | undefined) => Layer.Layer<FileSystem | Bash, import("@xandreed/core").HarnessError | import("@xandreed/core").BashError, never>;
|
|
38
|
+
/**
|
|
39
|
+
* The workspace tools (`workspaceTools`): read, ls, find and grep always on,
|
|
40
|
+
* write and edit behind `fs.write`, bash behind `shell.exec`, over the
|
|
41
|
+
* `FileSystem`, `Bash` and `SessionEnvironment` of each call. Built once
|
|
42
|
+
* per agent, so two sessions editing one file wait for each other. `Bash`
|
|
43
|
+
* is optional: without it there is no `bash` tool. Hosts grant the
|
|
44
|
+
* permissions through tool discovery's `grants` or a turn's
|
|
45
|
+
* `PermissionGrants`.
|
|
46
|
+
*/
|
|
47
|
+
export declare const workspaceToolsPlugin: import("@xandreed/core").TypedPlugin<{
|
|
48
|
+
readonly tools: readonly ("find" | "write" | "read" | "grep" | "bash" | "ls" | "edit")[];
|
|
49
|
+
readonly always: readonly ("read" | "edit" | "shell")[];
|
|
50
|
+
readonly confine: boolean;
|
|
51
|
+
readonly workspaceAccess: "write" | "read";
|
|
52
|
+
readonly roots: readonly {
|
|
53
|
+
readonly path: string;
|
|
54
|
+
readonly access: "write" | "read";
|
|
55
|
+
}[];
|
|
56
|
+
readonly protect: readonly string[];
|
|
57
|
+
readonly exclude: readonly string[];
|
|
58
|
+
readonly maxLines: number;
|
|
59
|
+
readonly maxBytes: number;
|
|
60
|
+
readonly lsEntries: number;
|
|
61
|
+
readonly findResults: number;
|
|
62
|
+
readonly grepMatches: number;
|
|
63
|
+
readonly grepLineChars: number;
|
|
64
|
+
readonly grepEngine: "auto" | "ripgrep" | "builtin";
|
|
65
|
+
readonly grepTimeoutMs: number;
|
|
66
|
+
readonly grepIgnoreFiles: boolean;
|
|
67
|
+
readonly maxFileBytes: number;
|
|
68
|
+
readonly readImages: boolean;
|
|
69
|
+
readonly imageMaxSide: number;
|
|
70
|
+
readonly fuzzyEdit: boolean;
|
|
71
|
+
readonly bashTimeoutMs: number;
|
|
72
|
+
readonly bashMaxTimeoutMs: number;
|
|
73
|
+
readonly bashEnv: {
|
|
74
|
+
readonly [x: string]: string;
|
|
75
|
+
};
|
|
76
|
+
readonly bashInheritEnv: boolean;
|
|
77
|
+
readonly bashEnvAllow: readonly string[];
|
|
78
|
+
readonly bashSessionEnv: boolean;
|
|
79
|
+
readonly spillDir: string | null;
|
|
80
|
+
readonly spillKeep: number;
|
|
81
|
+
readonly progressMs: number;
|
|
82
|
+
readonly environmentSection: boolean;
|
|
83
|
+
readonly sectionOrder: number;
|
|
84
|
+
}, {
|
|
85
|
+
readonly tools: readonly ("find" | "write" | "read" | "grep" | "bash" | "ls" | "edit")[];
|
|
86
|
+
readonly always: readonly ("read" | "edit" | "shell")[];
|
|
87
|
+
readonly confine: boolean;
|
|
88
|
+
readonly workspaceAccess: "write" | "read";
|
|
89
|
+
readonly roots: readonly {
|
|
90
|
+
readonly path: string;
|
|
91
|
+
readonly access: "write" | "read";
|
|
92
|
+
}[];
|
|
93
|
+
readonly protect: readonly string[];
|
|
94
|
+
readonly exclude: readonly string[];
|
|
95
|
+
readonly maxLines: number;
|
|
96
|
+
readonly maxBytes: number;
|
|
97
|
+
readonly lsEntries: number;
|
|
98
|
+
readonly findResults: number;
|
|
99
|
+
readonly grepMatches: number;
|
|
100
|
+
readonly grepLineChars: number;
|
|
101
|
+
readonly grepEngine: "auto" | "ripgrep" | "builtin";
|
|
102
|
+
readonly grepTimeoutMs: number;
|
|
103
|
+
readonly grepIgnoreFiles: boolean;
|
|
104
|
+
readonly maxFileBytes: number;
|
|
105
|
+
readonly readImages: boolean;
|
|
106
|
+
readonly imageMaxSide: number;
|
|
107
|
+
readonly fuzzyEdit: boolean;
|
|
108
|
+
readonly bashTimeoutMs: number;
|
|
109
|
+
readonly bashMaxTimeoutMs: number;
|
|
110
|
+
readonly bashEnv: {
|
|
111
|
+
readonly [x: string]: string;
|
|
112
|
+
};
|
|
113
|
+
readonly bashInheritEnv: boolean;
|
|
114
|
+
readonly bashEnvAllow: readonly string[];
|
|
115
|
+
readonly bashSessionEnv: boolean;
|
|
116
|
+
readonly spillDir: string | null;
|
|
117
|
+
readonly spillKeep: number;
|
|
118
|
+
readonly progressMs: number;
|
|
119
|
+
readonly environmentSection: boolean;
|
|
120
|
+
readonly sectionOrder: number;
|
|
121
|
+
}, Capabilities, import("@xandreed/core").HarnessError, never>;
|
|
122
|
+
/**
|
|
123
|
+
* The workspace tools as a typed layer: contributes their capability; its handlers need FileSystem and SessionEnvironment (and Bash for bash) where the tools are opened.
|
|
124
|
+
*
|
|
125
|
+
* @public
|
|
126
|
+
*/
|
|
127
|
+
export declare const WorkspaceToolsPluginLive: (options?: {
|
|
128
|
+
readonly tools?: readonly ("find" | "write" | "read" | "grep" | "bash" | "ls" | "edit")[];
|
|
129
|
+
readonly always?: readonly ("read" | "edit" | "shell")[];
|
|
130
|
+
readonly confine?: boolean;
|
|
131
|
+
readonly workspaceAccess?: "write" | "read";
|
|
132
|
+
readonly roots?: readonly {
|
|
133
|
+
readonly path: string;
|
|
134
|
+
readonly access: "write" | "read";
|
|
135
|
+
}[];
|
|
136
|
+
readonly protect?: readonly string[];
|
|
137
|
+
readonly exclude?: readonly string[];
|
|
138
|
+
readonly maxLines?: number;
|
|
139
|
+
readonly maxBytes?: number;
|
|
140
|
+
readonly lsEntries?: number;
|
|
141
|
+
readonly findResults?: number;
|
|
142
|
+
readonly grepMatches?: number;
|
|
143
|
+
readonly grepLineChars?: number;
|
|
144
|
+
readonly grepEngine?: "auto" | "ripgrep" | "builtin";
|
|
145
|
+
readonly grepTimeoutMs?: number;
|
|
146
|
+
readonly grepIgnoreFiles?: boolean;
|
|
147
|
+
readonly maxFileBytes?: number;
|
|
148
|
+
readonly readImages?: boolean;
|
|
149
|
+
readonly imageMaxSide?: number;
|
|
150
|
+
readonly fuzzyEdit?: boolean;
|
|
151
|
+
readonly bashTimeoutMs?: number;
|
|
152
|
+
readonly bashMaxTimeoutMs?: number;
|
|
153
|
+
readonly bashEnv?: {
|
|
154
|
+
readonly [x: string]: string | undefined;
|
|
155
|
+
};
|
|
156
|
+
readonly bashInheritEnv?: boolean;
|
|
157
|
+
readonly bashEnvAllow?: readonly string[];
|
|
158
|
+
readonly bashSessionEnv?: boolean;
|
|
159
|
+
readonly spillDir?: string | null;
|
|
160
|
+
readonly spillKeep?: number;
|
|
161
|
+
readonly progressMs?: number;
|
|
162
|
+
readonly environmentSection?: boolean;
|
|
163
|
+
readonly sectionOrder?: number;
|
|
164
|
+
} | undefined) => Layer.Layer<Capabilities, import("@xandreed/core").HarnessError, never>;
|
|
165
|
+
/** Optional CLI image fitting on the host's matching Bash and FileSystem. @public */
|
|
166
|
+
export declare const imageResizerPlugin: import("@xandreed/core").TypedPlugin<{
|
|
167
|
+
readonly tool: "auto" | "magick" | "convert" | "vips" | "sips";
|
|
168
|
+
readonly tempDir: string | null;
|
|
169
|
+
readonly timeoutMs: number;
|
|
170
|
+
readonly maxSourcePixels: number;
|
|
171
|
+
}, {
|
|
172
|
+
readonly tool: "auto" | "magick" | "convert" | "vips" | "sips";
|
|
173
|
+
readonly tempDir: string | null;
|
|
174
|
+
readonly timeoutMs: number;
|
|
175
|
+
readonly maxSourcePixels: number;
|
|
176
|
+
}, ImageResizer, import("@xandreed/core").HarnessError, FileSystem | Bash>;
|
|
177
|
+
/** @public */
|
|
178
|
+
export declare const ImageResizerPluginLive: (options?: {
|
|
179
|
+
readonly tool?: "auto" | "magick" | "convert" | "vips" | "sips";
|
|
180
|
+
readonly tempDir?: string | null;
|
|
181
|
+
readonly timeoutMs?: number;
|
|
182
|
+
readonly maxSourcePixels?: number;
|
|
183
|
+
} | undefined) => Layer.Layer<ImageResizer, import("@xandreed/core").HarnessError, FileSystem | Bash>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Effect, Layer } from "effect";
|
|
2
|
+
import { Bash, Capabilities, definePlugin, FileSystem, ImageResizer, SessionEnvironment } from "@xandreed/core";
|
|
3
|
+
import { BashLive } from "./bash.adapter.js";
|
|
4
|
+
import { FileSystemLive } from "./file-system.adapter.js";
|
|
5
|
+
import { LocalSystemConfig, localSystemDefaults } from "./local-system.entity.js";
|
|
6
|
+
import { ImageResizerCliLive } from "./image-resizer.adapter.js";
|
|
7
|
+
import { ImageResizerConfig, imageResizerDefaults } from "./image-resizer.entity.js";
|
|
8
|
+
import { workspaceTools } from "./workspace-tools.adapter.js";
|
|
9
|
+
import { WorkspaceToolsConfig, workspaceToolsDefaults } from "./workspace-tools.entity.js";
|
|
10
|
+
export const localSystemPlugin = definePlugin({
|
|
11
|
+
id: "@xandreed/plugin-workspace/local",
|
|
12
|
+
version: "0.15.0",
|
|
13
|
+
scope: "runtime",
|
|
14
|
+
config: LocalSystemConfig,
|
|
15
|
+
defaults: localSystemDefaults,
|
|
16
|
+
provides: [FileSystem, Bash],
|
|
17
|
+
layer: (config) => Layer.merge(FileSystemLive, BashLive(config))
|
|
18
|
+
}), LocalSystemPluginLive = localSystemPlugin.live, workspaceToolsPlugin = definePlugin({
|
|
19
|
+
id: "@xandreed/plugin-workspace/tools",
|
|
20
|
+
version: "0.15.0",
|
|
21
|
+
scope: "runtime",
|
|
22
|
+
config: WorkspaceToolsConfig,
|
|
23
|
+
defaults: workspaceToolsDefaults,
|
|
24
|
+
requires: [FileSystem, SessionEnvironment],
|
|
25
|
+
optional: [Bash, ImageResizer],
|
|
26
|
+
provides: [],
|
|
27
|
+
contributes: [Capabilities],
|
|
28
|
+
layer: (config) => Layer.effect(Capabilities, workspaceTools(config).pipe(Effect.map((capability) => [capability])))
|
|
29
|
+
}), WorkspaceToolsPluginLive = workspaceToolsPlugin.live, imageResizerPlugin = definePlugin({
|
|
30
|
+
id: "@xandreed/plugin-workspace/images",
|
|
31
|
+
version: "0.15.0",
|
|
32
|
+
scope: "runtime",
|
|
33
|
+
config: ImageResizerConfig,
|
|
34
|
+
defaults: imageResizerDefaults,
|
|
35
|
+
requires: [FileSystem, Bash],
|
|
36
|
+
provides: [ImageResizer],
|
|
37
|
+
layer: (config) => ImageResizerCliLive(config)
|
|
38
|
+
}), ImageResizerPluginLive = imageResizerPlugin.live;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Path, RcMap, Semaphore } from "effect";
|
|
2
|
+
import { Failure, FileSystem, SessionEnvironment } from "@xandreed/core";
|
|
3
|
+
import type { RegisteredTool, Subject } from "@xandreed/core";
|
|
4
|
+
import type { ProtectRule } from "./workspace-path.entity.functions.js";
|
|
5
|
+
import type { WorkspaceToolsConfig } from "./workspace-tools.entity.js";
|
|
6
|
+
/** What every workspace tool is built with: the configuration, the path functions, the compiled `protect` globs and the per-file write locks. */
|
|
7
|
+
export interface WorkspaceToolContext {
|
|
8
|
+
readonly config: WorkspaceToolsConfig;
|
|
9
|
+
readonly path: Path.Path;
|
|
10
|
+
readonly protect: ReadonlyArray<ProtectRule>;
|
|
11
|
+
readonly locks: RcMap.RcMap<string, Semaphore.Semaphore>;
|
|
12
|
+
readonly resizesImages: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** The services every workspace tool reads on each call, and the `workspace.environment` section on each render: the file system, and the session's workspace on it (`bash` reads `Bash` too). */
|
|
15
|
+
export declare const WORKSPACE_SERVICES: readonly [typeof FileSystem, typeof SessionEnvironment];
|
|
16
|
+
export declare const failure: (error: string, message: string) => Failure;
|
|
17
|
+
/** The refusal of a path that is neither a regular file nor a directory: reading a pipe, socket or device could wait forever. */
|
|
18
|
+
export declare const notAFile: (display: string, tool: string) => Failure;
|
|
19
|
+
/** A file the result names, for memory to keep track of; none for a name memory could not store as an id. */
|
|
20
|
+
export declare const fileSubjects: (path: string) => ReadonlyArray<Subject>;
|
|
21
|
+
/** `read`: a window of a text file's lines, an image (`readImages`), or what a binary file is. */
|
|
22
|
+
export declare const readTool: ({ config, path, resizesImages }: WorkspaceToolContext) => RegisteredTool;
|
|
23
|
+
/** `ls`: a directory's entries by name, at most `lsEntries` of them. */
|
|
24
|
+
export declare const lsTool: ({ config, path }: WorkspaceToolContext) => RegisteredTool;
|
|
25
|
+
/** `find`: the files under a directory whose path matches a glob, as paths relative to the workspace root. */
|
|
26
|
+
export declare const findTool: ({ config, path }: WorkspaceToolContext) => RegisteredTool;
|
|
27
|
+
/**
|
|
28
|
+
* `grep`: the lines of the files under a path (or of one file) that match.
|
|
29
|
+
* Files come from a walk that never enters links or excluded names; each is
|
|
30
|
+
* resolved again before it is read and skipped when it leads outside the
|
|
31
|
+
* workspace, is not a regular file, is larger than `maxFileBytes` or is
|
|
32
|
+
* binary; one the file system refuses (permission denied, say) is named in
|
|
33
|
+
* `unreadable` with the reason, not counted as skipped. Lines are cut to `grepLineChars` before matching. Searching a
|
|
34
|
+
* directory, one file gives at most a quarter of the limit, so one large
|
|
35
|
+
* file cannot fill the result, however many files the directory holds (as
|
|
36
|
+
* with ripgrep, which cannot know that ahead). A pattern with a line break
|
|
37
|
+
* is refused (`singleLinePattern`) whichever engine runs.
|
|
38
|
+
*/
|
|
39
|
+
export declare const grepTool: ({ config, path }: WorkspaceToolContext) => RegisteredTool;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Tool } from "effect/ai";
|
|
2
|
+
import { Effect, Option, Result } from "effect";
|
|
3
|
+
import { Bash, defineTool, Failure, FileSystem, ImageResizer, SessionEnvironment, SubjectId } from "@xandreed/core";
|
|
4
|
+
import { searchRipgrep } from "./ripgrep.adapter.js";
|
|
5
|
+
import { compilePattern, searchText, singleLinePattern } from "./text-search.entity.functions.js";
|
|
6
|
+
import { decodeText, formatBytes, lineWindow, looksBinary, mediaTypeOf } from "./text-window.entity.functions.js";
|
|
7
|
+
import { displayOf, excludesBelow, fsFailure, isWithin, openWorkspace, resolveTarget, withinAnyRoot } from "./workspace-path.entity.functions.js";
|
|
8
|
+
import { FindResult, GrepResult, LsResult, ReadResult } from "./workspace-tools.entity.js";
|
|
9
|
+
import { binaryRead, compactFind, fileTooLargeHint, compactGrep, compactLs, compactRead, readMedia, renderFind, renderGrep, renderLs, renderRead, toolDescriptions, toolParameters } from "./workspace-tools.entity.functions.js";
|
|
10
|
+
export const WORKSPACE_SERVICES = [FileSystem, SessionEnvironment], failure = (error, message) => ({ error, message }), notAFile = (display, tool) => failure("NotAFile", `${display} is not a regular file (a pipe, socket or device); ${tool} works on regular files only, use bash for it`);
|
|
11
|
+
const walkExcludes = (access, target, excludes) => excludesBelow(excludes, isWithin(access.root, target.path) ? Option.some(access.path.relative(access.root, target.path)) : Option.none());
|
|
12
|
+
export const fileSubjects = (path) => path.length > 0 && path.trim() === path ? [{ kind: "file", id: SubjectId.make(path), label: Option.none(), data: Option.none() }] : [];
|
|
13
|
+
const ReadTool = (config, resizesImages) => Tool.make("read", { description: toolDescriptions(config, resizesImages).read, parameters: toolParameters(config).read, success: ReadResult, failure: Failure, failureMode: "return" }), LsTool = (config) => Tool.make("ls", { description: toolDescriptions(config).ls, parameters: toolParameters(config).ls, success: LsResult, failure: Failure, failureMode: "return" }), FindTool = (config) => Tool.make("find", { description: toolDescriptions(config).find, parameters: toolParameters(config).find, success: FindResult, failure: Failure, failureMode: "return" }), GrepTool = (config) => Tool.make("grep", { description: toolDescriptions(config).grep, parameters: toolParameters(config).grep, success: GrepResult, failure: Failure, failureMode: "return" });
|
|
14
|
+
export const readTool = ({ config, path, resizesImages }) => defineTool({
|
|
15
|
+
tool: ReadTool(config, resizesImages),
|
|
16
|
+
requires: resizesImages ? [...WORKSPACE_SERVICES, ImageResizer] : WORKSPACE_SERVICES,
|
|
17
|
+
handler: (params) => Effect.gen(function* () {
|
|
18
|
+
const access = yield* openWorkspace(path, config), target = yield* resolveTarget(access, params.path, { confine: config.confine }), info = yield* access.fs.stat(target.path).pipe(Effect.mapError(fsFailure(target.display)));
|
|
19
|
+
if (info.kind === "directory")
|
|
20
|
+
return yield* Effect.fail(failure("IsDirectory", `${target.display} is a directory; use ls to list it`));
|
|
21
|
+
if (info.kind !== "file")
|
|
22
|
+
return yield* Effect.fail(notAFile(target.display, "read"));
|
|
23
|
+
if (info.size > config.maxFileBytes)
|
|
24
|
+
return yield* Effect.fail(failure("FileTooLarge", `${target.display} is ${formatBytes(info.size)}, more than the ${formatBytes(config.maxFileBytes)} read loads; ${fileTooLargeHint(target.display, config.readImages)}`));
|
|
25
|
+
const bytes = yield* access.fs.readBytes(target.path).pipe(Effect.mapError(fsFailure(target.display)));
|
|
26
|
+
if (looksBinary(bytes))
|
|
27
|
+
return yield* binaryRead(target.display, bytes, config.readImages, Option.getOrNull(mediaTypeOf(target.display)), resizesImages ? Option.map(yield* Effect.serviceOption(ImageResizer), (resizer) => resizer.fit) : Option.none(), config.imageMaxSide);
|
|
28
|
+
const decoded = decodeText(bytes), window = lineWindow(decoded.text, { offset: params.offset ?? 1, limit: params.limit ?? config.maxLines, maxBytes: config.maxBytes });
|
|
29
|
+
return yield* Result.match(window, {
|
|
30
|
+
onFailure: (total) => Effect.fail(failure("OffsetPastEnd", `${target.display} has ${total} line${total === 1 ? "" : "s"}; offset must be at most ${Math.max(1, total)}`)),
|
|
31
|
+
onSuccess: (lines) => Effect.succeed({
|
|
32
|
+
_tag: "Text",
|
|
33
|
+
path: target.display,
|
|
34
|
+
text: lines.lines.join(`
|
|
35
|
+
`),
|
|
36
|
+
firstLine: lines.firstLine,
|
|
37
|
+
lastLine: lines.lastLine,
|
|
38
|
+
totalLines: lines.totalLines,
|
|
39
|
+
next: Option.getOrNull(lines.next),
|
|
40
|
+
clipped: lines.clipped,
|
|
41
|
+
lossy: decoded.lossy
|
|
42
|
+
})
|
|
43
|
+
});
|
|
44
|
+
}),
|
|
45
|
+
view: { version: "3", render: renderRead, compact: compactRead, subjects: (result) => fileSubjects(result.path), media: readMedia },
|
|
46
|
+
annotations: { readOnly: !0 }
|
|
47
|
+
}), lsTool = ({ config, path }) => defineTool({
|
|
48
|
+
tool: LsTool(config),
|
|
49
|
+
requires: WORKSPACE_SERVICES,
|
|
50
|
+
handler: (params) => Effect.gen(function* () {
|
|
51
|
+
const access = yield* openWorkspace(path, config), target = yield* resolveTarget(access, params.path ?? ".", { confine: config.confine }), entries = yield* access.fs.list(target.path).pipe(Effect.mapError((error) => error.reason === "NotDirectory" ? failure("NotDirectory", `${target.display} is not a directory; use read to see a file`) : fsFailure(target.display)(error))), limit = params.limit ?? config.lsEntries;
|
|
52
|
+
return { path: target.display, entries: entries.slice(0, limit).map((entry) => ({ name: entry.name, kind: entry.kind })), total: entries.length };
|
|
53
|
+
}),
|
|
54
|
+
view: { version: "2", render: renderLs, compact: compactLs },
|
|
55
|
+
annotations: { readOnly: !0 }
|
|
56
|
+
}), findTool = ({ config, path }) => defineTool({
|
|
57
|
+
tool: FindTool(config),
|
|
58
|
+
requires: WORKSPACE_SERVICES,
|
|
59
|
+
handler: (params) => Effect.gen(function* () {
|
|
60
|
+
const access = yield* openWorkspace(path, config), target = yield* resolveTarget(access, params.path ?? ".", { confine: config.confine }), limit = params.limit ?? config.findResults, found = yield* access.fs.glob(params.pattern, { cwd: target.path, exclude: walkExcludes(access, target, config.exclude), dot: !0, onlyFiles: !0, limit }).pipe(Effect.mapError((error) => error.reason === "Invalid" ? failure("InvalidPattern", `${params.pattern}: ${error.message}`) : fsFailure(target.display)(error)));
|
|
61
|
+
return { pattern: params.pattern, path: target.display, paths: found.paths.map((relative) => displayOf(access, access.path.join(target.path, relative))), truncated: found.truncated };
|
|
62
|
+
}),
|
|
63
|
+
view: { version: "2", render: (result) => renderFind(result, config.maxLines, config.maxBytes), compact: compactFind },
|
|
64
|
+
annotations: { readOnly: !0 }
|
|
65
|
+
}), grepTool = ({ config, path }) => defineTool({
|
|
66
|
+
tool: GrepTool(config),
|
|
67
|
+
requires: config.grepEngine === "ripgrep" ? [...WORKSPACE_SERVICES, Bash] : WORKSPACE_SERVICES,
|
|
68
|
+
handler: (params) => Effect.gen(function* () {
|
|
69
|
+
const access = yield* openWorkspace(path, config), target = yield* resolveTarget(access, params.path ?? ".", { confine: config.confine });
|
|
70
|
+
yield* Effect.fromResult(singleLinePattern(params.pattern));
|
|
71
|
+
const pattern = config.grepEngine === "ripgrep" ? Option.none() : Option.some(yield* Effect.fromResult(compilePattern(params.pattern, { literal: params.literal ?? !1, ignoreCase: params.ignoreCase ?? !1 }))), info = yield* access.fs.stat(target.path).pipe(Effect.mapError(fsFailure(target.display)));
|
|
72
|
+
if (info.kind !== "file" && info.kind !== "directory")
|
|
73
|
+
return yield* Effect.fail(notAFile(target.display, "grep"));
|
|
74
|
+
if (config.grepEngine === "ripgrep")
|
|
75
|
+
return yield* searchRipgrep(access, target, info.kind === "directory", params, config);
|
|
76
|
+
const glob = params.glob === void 0 ? "**/*" : params.glob.includes("/") ? params.glob : `**/${params.glob}`, files = info.kind === "file" ? [{ path: target.path, display: target.display }] : yield* access.fs.glob(glob, { cwd: target.path, exclude: walkExcludes(access, target, config.exclude), dot: !0, onlyFiles: !0 }).pipe(Effect.map((found) => found.paths.map((relative) => {
|
|
77
|
+
const absolute = access.path.join(target.path, relative);
|
|
78
|
+
return { path: absolute, display: displayOf(access, absolute) };
|
|
79
|
+
})), Effect.mapError((error) => error.reason === "Invalid" ? failure("InvalidPattern", `glob ${params.glob ?? glob}: ${error.message}`) : fsFailure(target.display)(error))), limit = params.limit ?? config.grepMatches, perFile = info.kind === "directory" ? Math.max(1, Math.ceil(limit / 4)) : limit, context = params.context ?? 0, skip = (state) => ({ ...state, skipped: state.skipped + 1 }), searched = yield* Effect.reduce(files, () => ({ lines: [], matches: 0, files: 0, skipped: 0, truncated: !1, unreadable: [] }), (state, file) => {
|
|
80
|
+
if (state.matches >= limit)
|
|
81
|
+
return Effect.succeed({ ...state, truncated: !0 });
|
|
82
|
+
return Effect.gen(function* () {
|
|
83
|
+
const real = yield* access.fs.realPath(file.path);
|
|
84
|
+
if (config.confine && !withinAnyRoot(access, real))
|
|
85
|
+
return Option.none();
|
|
86
|
+
const found = yield* access.fs.stat(real);
|
|
87
|
+
if (found.kind !== "file" || found.size > config.maxFileBytes)
|
|
88
|
+
return Option.none();
|
|
89
|
+
const bytes = yield* access.fs.readBytes(real);
|
|
90
|
+
return looksBinary(bytes) ? Option.none() : Option.some(bytes);
|
|
91
|
+
}).pipe(Effect.map(Option.match({
|
|
92
|
+
onNone: () => skip(state),
|
|
93
|
+
onSome: (content) => {
|
|
94
|
+
const found = searchText(decodeText(content).text, Option.getOrThrow(pattern), { lineChars: config.grepLineChars, context, cap: Math.min(perFile, limit - state.matches) });
|
|
95
|
+
return {
|
|
96
|
+
...state,
|
|
97
|
+
lines: [...state.lines, ...found.lines.map((line) => ({ path: file.display, ...line }))],
|
|
98
|
+
matches: state.matches + found.matches,
|
|
99
|
+
files: state.files + (found.matches > 0 ? 1 : 0),
|
|
100
|
+
truncated: state.truncated || found.capped
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
})), Effect.catch((error) => Effect.succeed({ ...state, unreadable: [...state.unreadable, { path: file.display, reason: error.reason }] })));
|
|
104
|
+
});
|
|
105
|
+
return { pattern: params.pattern, path: target.display, ...searched, timedOut: !1 };
|
|
106
|
+
}),
|
|
107
|
+
view: { version: "4", render: (result, params) => renderGrep(result, (params.context ?? 0) > 0, config.maxLines, config.maxBytes), compact: compactGrep },
|
|
108
|
+
annotations: { readOnly: !0 }
|
|
109
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { Bash } from "@xandreed/core";
|
|
3
|
+
import type { WorkspaceAccess } from "./workspace-path.entity.functions.js";
|
|
4
|
+
/** A bounded, interruptible search on the same machine as the Bash/FileSystem pair. */
|
|
5
|
+
export declare const searchRipgrep: (access: WorkspaceAccess, target: {
|
|
6
|
+
readonly path: string;
|
|
7
|
+
readonly display: string;
|
|
8
|
+
}, directory: boolean, params: {
|
|
9
|
+
readonly pattern: string;
|
|
10
|
+
readonly path?: string;
|
|
11
|
+
readonly limit?: number;
|
|
12
|
+
readonly context?: number;
|
|
13
|
+
readonly literal?: boolean;
|
|
14
|
+
readonly glob?: string;
|
|
15
|
+
readonly ignoreCase?: boolean;
|
|
16
|
+
}, config: {
|
|
17
|
+
readonly tools: readonly ("find" | "write" | "read" | "grep" | "bash" | "ls" | "edit")[];
|
|
18
|
+
readonly always: readonly ("read" | "edit" | "shell")[];
|
|
19
|
+
readonly confine: boolean;
|
|
20
|
+
readonly workspaceAccess: "write" | "read";
|
|
21
|
+
readonly roots: readonly {
|
|
22
|
+
readonly path: string;
|
|
23
|
+
readonly access: "write" | "read";
|
|
24
|
+
}[];
|
|
25
|
+
readonly protect: readonly string[];
|
|
26
|
+
readonly exclude: readonly string[];
|
|
27
|
+
readonly maxLines: number;
|
|
28
|
+
readonly maxBytes: number;
|
|
29
|
+
readonly lsEntries: number;
|
|
30
|
+
readonly findResults: number;
|
|
31
|
+
readonly grepMatches: number;
|
|
32
|
+
readonly grepLineChars: number;
|
|
33
|
+
readonly grepEngine: "auto" | "ripgrep" | "builtin";
|
|
34
|
+
readonly grepTimeoutMs: number;
|
|
35
|
+
readonly grepIgnoreFiles: boolean;
|
|
36
|
+
readonly maxFileBytes: number;
|
|
37
|
+
readonly readImages: boolean;
|
|
38
|
+
readonly imageMaxSide: number;
|
|
39
|
+
readonly fuzzyEdit: boolean;
|
|
40
|
+
readonly bashTimeoutMs: number;
|
|
41
|
+
readonly bashMaxTimeoutMs: number;
|
|
42
|
+
readonly bashEnv: {
|
|
43
|
+
readonly [x: string]: string;
|
|
44
|
+
};
|
|
45
|
+
readonly bashInheritEnv: boolean;
|
|
46
|
+
readonly bashEnvAllow: readonly string[];
|
|
47
|
+
readonly bashSessionEnv: boolean;
|
|
48
|
+
readonly spillDir: string | null;
|
|
49
|
+
readonly spillKeep: number;
|
|
50
|
+
readonly progressMs: number;
|
|
51
|
+
readonly environmentSection: boolean;
|
|
52
|
+
readonly sectionOrder: number;
|
|
53
|
+
}) => Effect.Effect<{
|
|
54
|
+
pattern: string;
|
|
55
|
+
path: string;
|
|
56
|
+
lines: readonly {
|
|
57
|
+
readonly path: string;
|
|
58
|
+
readonly line: number;
|
|
59
|
+
readonly text: string;
|
|
60
|
+
readonly match: boolean;
|
|
61
|
+
}[];
|
|
62
|
+
matches: number;
|
|
63
|
+
files: number;
|
|
64
|
+
skipped: number;
|
|
65
|
+
truncated: boolean;
|
|
66
|
+
unreadable: {
|
|
67
|
+
path: string;
|
|
68
|
+
reason: "Invalid" | "NotFound" | "Exists" | "IsDirectory" | "NotDirectory" | "NotEmpty" | "Permission" | "Io";
|
|
69
|
+
}[];
|
|
70
|
+
timedOut: boolean;
|
|
71
|
+
}, {
|
|
72
|
+
readonly error: string;
|
|
73
|
+
readonly message: string;
|
|
74
|
+
}, Bash>;
|