@clarvis/agent-tools 0.1.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.
Files changed (116) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/CODE_OF_CONDUCT.md +117 -0
  3. package/CONTRIBUTING.md +78 -0
  4. package/LICENSE +21 -0
  5. package/README.md +203 -0
  6. package/SECURITY.md +58 -0
  7. package/SPEC.md +266 -0
  8. package/dist/config.d.ts +29 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +145 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/core.d.ts +13 -0
  13. package/dist/core.d.ts.map +1 -0
  14. package/dist/core.js +43 -0
  15. package/dist/core.js.map +1 -0
  16. package/dist/errors.d.ts +9 -0
  17. package/dist/errors.d.ts.map +1 -0
  18. package/dist/errors.js +29 -0
  19. package/dist/errors.js.map +1 -0
  20. package/dist/index.d.ts +20 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +17 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/lib/atomic.d.ts +11 -0
  25. package/dist/lib/atomic.d.ts.map +1 -0
  26. package/dist/lib/atomic.js +307 -0
  27. package/dist/lib/atomic.js.map +1 -0
  28. package/dist/lib/binary.d.ts +3 -0
  29. package/dist/lib/binary.d.ts.map +1 -0
  30. package/dist/lib/binary.js +20 -0
  31. package/dist/lib/binary.js.map +1 -0
  32. package/dist/lib/files.d.ts +9 -0
  33. package/dist/lib/files.d.ts.map +1 -0
  34. package/dist/lib/files.js +49 -0
  35. package/dist/lib/files.js.map +1 -0
  36. package/dist/lib/ignore.d.ts +5 -0
  37. package/dist/lib/ignore.d.ts.map +1 -0
  38. package/dist/lib/ignore.js +106 -0
  39. package/dist/lib/ignore.js.map +1 -0
  40. package/dist/lib/log.d.ts +4 -0
  41. package/dist/lib/log.d.ts.map +1 -0
  42. package/dist/lib/log.js +11 -0
  43. package/dist/lib/log.js.map +1 -0
  44. package/dist/lib/match-cascade.d.ts +10 -0
  45. package/dist/lib/match-cascade.d.ts.map +1 -0
  46. package/dist/lib/match-cascade.js +153 -0
  47. package/dist/lib/match-cascade.js.map +1 -0
  48. package/dist/lib/output.d.ts +8 -0
  49. package/dist/lib/output.d.ts.map +1 -0
  50. package/dist/lib/output.js +77 -0
  51. package/dist/lib/output.js.map +1 -0
  52. package/dist/lib/paths.d.ts +3 -0
  53. package/dist/lib/paths.d.ts.map +1 -0
  54. package/dist/lib/paths.js +46 -0
  55. package/dist/lib/paths.js.map +1 -0
  56. package/dist/lib/rg.d.ts +22 -0
  57. package/dist/lib/rg.d.ts.map +1 -0
  58. package/dist/lib/rg.js +285 -0
  59. package/dist/lib/rg.js.map +1 -0
  60. package/dist/lib/text.d.ts +18 -0
  61. package/dist/lib/text.d.ts.map +1 -0
  62. package/dist/lib/text.js +129 -0
  63. package/dist/lib/text.js.map +1 -0
  64. package/dist/lib/textfile.d.ts +4 -0
  65. package/dist/lib/textfile.d.ts.map +1 -0
  66. package/dist/lib/textfile.js +55 -0
  67. package/dist/lib/textfile.js.map +1 -0
  68. package/dist/lib/token.d.ts +2 -0
  69. package/dist/lib/token.d.ts.map +1 -0
  70. package/dist/lib/token.js +6 -0
  71. package/dist/lib/token.js.map +1 -0
  72. package/dist/tools/apply-patch.d.ts +3 -0
  73. package/dist/tools/apply-patch.d.ts.map +1 -0
  74. package/dist/tools/apply-patch.js +221 -0
  75. package/dist/tools/apply-patch.js.map +1 -0
  76. package/dist/tools/bash.d.ts +3 -0
  77. package/dist/tools/bash.d.ts.map +1 -0
  78. package/dist/tools/bash.js +179 -0
  79. package/dist/tools/bash.js.map +1 -0
  80. package/dist/tools/edit-file.d.ts +15 -0
  81. package/dist/tools/edit-file.d.ts.map +1 -0
  82. package/dist/tools/edit-file.js +163 -0
  83. package/dist/tools/edit-file.js.map +1 -0
  84. package/dist/tools/glob.d.ts +3 -0
  85. package/dist/tools/glob.d.ts.map +1 -0
  86. package/dist/tools/glob.js +64 -0
  87. package/dist/tools/glob.js.map +1 -0
  88. package/dist/tools/grep.d.ts +3 -0
  89. package/dist/tools/grep.d.ts.map +1 -0
  90. package/dist/tools/grep.js +201 -0
  91. package/dist/tools/grep.js.map +1 -0
  92. package/dist/tools/list-dir.d.ts +3 -0
  93. package/dist/tools/list-dir.d.ts.map +1 -0
  94. package/dist/tools/list-dir.js +59 -0
  95. package/dist/tools/list-dir.js.map +1 -0
  96. package/dist/tools/multi-edit.d.ts +3 -0
  97. package/dist/tools/multi-edit.d.ts.map +1 -0
  98. package/dist/tools/multi-edit.js +86 -0
  99. package/dist/tools/multi-edit.js.map +1 -0
  100. package/dist/tools/read-file.d.ts +3 -0
  101. package/dist/tools/read-file.d.ts.map +1 -0
  102. package/dist/tools/read-file.js +102 -0
  103. package/dist/tools/read-file.js.map +1 -0
  104. package/dist/tools/registry.d.ts +6 -0
  105. package/dist/tools/registry.d.ts.map +1 -0
  106. package/dist/tools/registry.js +28 -0
  107. package/dist/tools/registry.js.map +1 -0
  108. package/dist/tools/types.d.ts +9 -0
  109. package/dist/tools/types.d.ts.map +1 -0
  110. package/dist/tools/types.js +2 -0
  111. package/dist/tools/types.js.map +1 -0
  112. package/dist/tools/write-file.d.ts +3 -0
  113. package/dist/tools/write-file.d.ts.map +1 -0
  114. package/dist/tools/write-file.js +61 -0
  115. package/dist/tools/write-file.js.map +1 -0
  116. package/package.json +70 -0
package/SPEC.md ADDED
@@ -0,0 +1,266 @@
1
+ # @clarvis/agent-tools — Specification
2
+
3
+ This document specifies the behavior of each tool exposed by
4
+ `@clarvis/agent-tools`. For installation, configuration, and the
5
+ security model, see [README.md](./README.md).
6
+
7
+ ## Conventions
8
+
9
+ - **Return value.** Each tool call returns `{ isError, text }`. On success
10
+ `text` is the tool's output; on failure `isError` is set and `text` is a JSON
11
+ error object (see [Errors](#errors)).
12
+ - **Result format.** On success every tool returns plain text **except `bash`**,
13
+ whose success result is a JSON object (`exit_code`, `stdout`, `stderr`,
14
+ `signal`, `timed_out`); a non-zero exit is a success (`isError` false), not an
15
+ error. Only spawn/timeout/output-limit failures of `bash` flag `isError`. All
16
+ failures, for every tool, are the JSON error object above.
17
+ - **Paths.** A path is resolved verbatim if absolute, otherwise against the
18
+ workspace root. By default the result is **confined to the workspace root**:
19
+ `../` traversal, absolute paths outside the root, and symlinks that resolve
20
+ outside it are rejected with `path_escape` (the existing portion of the path is
21
+ canonicalized with `realpath`, so symlink hops are caught). Confinement is
22
+ disabled by `ALLOW_OUTSIDE_WORKSPACE=1` / `--allow-outside-workspace`, which
23
+ restores unrestricted resolution (see README → Security).
24
+ - **Input size.** The file-reading tools (`read_file`, `edit_file`, `multi_edit`,
25
+ `apply_patch`, and the in-process `grep` backend) refuse a file larger than
26
+ `MAX_FILE_BYTES` (default 20000000): the read/edit tools fail with `too_large`,
27
+ and `grep` skips the oversized file.
28
+ - **Text & encoding.** Text tools operate on UTF-8. A UTF-16 file with a BOM (LE/BE)
29
+ is decoded by `read_file`, but the editing tools refuse it (`is_binary`) rather than
30
+ rewrite it as UTF-8. Other binary files (NUL byte in the first/last 8 KB, no UTF-16
31
+ BOM) are rejected by the text tools with `is_binary`.
32
+ - **Line endings & BOM.** On read, content is normalized to `\n` for matching.
33
+ On write, the editing tools (`edit_file`, `multi_edit`, `apply_patch` modify)
34
+ **preserve each untouched line's original terminator** (`\r\n`, `\n`, or lone
35
+ `\r`); newly inserted or edited lines use the file's dominant terminator. A
36
+ leading UTF-8 BOM is preserved.
37
+ - **Output bounding.** Every result is capped to `MAX_OUTPUT_BYTES` (default
38
+ 131072), truncated on a UTF-8 boundary with a trailing marker.
39
+ - **Atomicity.** All mutations write to a temp file and `rename` into place;
40
+ multi-file operations (`apply_patch`) are staged and committed transactionally
41
+ with rollback (a rename/move participates with both its source and destination). Writes to the same path are serialized by an in-process lock —
42
+ this is the single-process contract; concurrent processes or external
43
+ editors are **not** coordinated. A symlink target is refused (the link is not
44
+ followed and not replaced).
45
+ - **BOM.** A leading U+FEFF is treated as a byte-order mark: stripped from the
46
+ content returned by `read_file`/used for matching, and re-emitted on write.
47
+
48
+ ## Read-only surface
49
+
50
+ In `--read-only` mode only `read_file`, `list_dir`, `glob`, and `grep` are
51
+ exposed; the mutating tools are not registered.
52
+
53
+ ---
54
+
55
+ ## read_file
56
+
57
+ Read a text file. UTF-8 by default; a UTF-16 file with a byte-order mark (LE/BE) is
58
+ detected and decoded.
59
+
60
+ **Input:** `path` (string, required); `offset` (integer, 1-based start line,
61
+ default 1; a negative value counts from the end — `-N` reads the last N lines; 0 is
62
+ invalid); `limit` (integer, max lines, default tool-defined).
63
+
64
+ **Behavior:** Returns lines prefixed with right-aligned line numbers and a tab.
65
+ Empty file returns `(empty file)`. Over-long lines are truncated with a marker, and no
66
+ single emitted line exceeds `MAX_OUTPUT_BYTES` (a longer line is byte-truncated on a UTF-8
67
+ boundary). At least one line is always shown. When more lines remain, a continuation hint
68
+ with the next `offset` is appended.
69
+
70
+ **Errors:** `not_found`, `not_a_file` (directory), `is_binary`, `too_large`,
71
+ `path_escape`, `invalid_input` (offset 0, or a positive offset past EOF).
72
+
73
+ ## list_dir
74
+
75
+ List the entries of a directory.
76
+
77
+ **Input:** `path` (string, default workspace root).
78
+
79
+ **Behavior:** One entry per line; directories are marked. An empty directory
80
+ returns a sentinel line rather than an empty result.
81
+
82
+ **Errors:** `not_found`, `not_a_file` (path is a file), `path_escape`, `io_error`.
83
+
84
+ ## glob
85
+
86
+ Find files (not directories) by glob pattern.
87
+
88
+ **Input:** `pattern` (string, required, e.g. `**/*.ts`); `path` (base dir,
89
+ default workspace root); `respect_gitignore` (boolean, default true).
90
+
91
+ **Behavior:** Returns matching file paths, most-recently-modified first. Hidden
92
+ files are included; when `respect_gitignore` is true, files ignored by the git
93
+ ignore stack and the `.git/` directory are skipped. No matches returns
94
+ `(no matches)`.
95
+
96
+ **Errors:** `not_found`, `path_escape`, `invalid_input` (bad glob).
97
+
98
+ ## grep
99
+
100
+ Search file contents by regular expression, recursively.
101
+
102
+ **Input:** `pattern` (string, required, Rust/ripgrep regex syntax); `path` (file
103
+ or dir, default workspace root); `glob` (restrict to matching files);
104
+ `output_mode` (`files_with_matches` | `content` | `count`, default
105
+ `files_with_matches`); `ignore_case` (boolean, default false); `context`
106
+ (integer, both sides, content mode only, default 0); `before_context` /
107
+ `after_context` (integers, content mode only — `ripgrep` `-B`/`-A`; each overrides
108
+ `context` for that side); `head_limit` (integer ≥ 1, max results to return — files in
109
+ `files_with_matches`/`count`, matches in `content`; omit for unlimited, still
110
+ byte-bounded); `offset` (integer ≥ 0, 0-based number of leading results to skip —
111
+ **a result offset, not `read_file`'s line offset**); `multiline` (boolean, default
112
+ false — match across line boundaries, ripgrep `--multiline --multiline-dotall`).
113
+
114
+ **Behavior:** Uses ripgrep when available, otherwise an equivalent in-process
115
+ fallback. Both backends apply one consistent policy: **hidden files are
116
+ searched, the `.git/` directory is skipped, and the full git ignore stack is
117
+ respected** (nested `.gitignore`, parent directories up to the repository root,
118
+ `.git/info/exclude`, and the global excludes file). Binary files are skipped, as
119
+ are files larger than `MAX_FILE_BYTES` in the in-process backend. No matches
120
+ returns `(no matches)` (a success). Output is deterministic (sorted by path, then
121
+ line number). The pattern is validated by the active backend: ripgrep reports its
122
+ own syntax errors, and the in-process fallback compiles the pattern as a
123
+ JavaScript `RegExp`; a pattern valid for one engine but not the other is accepted
124
+ or rejected by whichever backend runs.
125
+
126
+ **Multiline.** With `multiline` true, `.` also matches newlines and `^`/`$` anchor at
127
+ line boundaries, so a single match may span several lines; in `content` mode it renders
128
+ as the spanned lines under the start line's `path:line:` prefix, and it still counts as
129
+ one match for `count` and as one unit for `head_limit`/`offset` paging. A match that
130
+ spans `\n` may differ between the two backends on CRLF files (the in-process backend
131
+ matches against `\n`-normalized text).
132
+
133
+ **Pagination.** `head_limit`/`offset` page over the result units (files, or matches in
134
+ content mode) **within the collected result window** — re-run with `offset` advanced to
135
+ get the next page. A footer reports state honestly: when the underlying scan was
136
+ truncated by the output cap it warns the result is **incomplete** and to narrow the
137
+ query (and does **not** suggest paging, since unscanned files cannot be reached);
138
+ otherwise, when more units remain it prints `showing A..B of N; call again with
139
+ offset=B for more`; an `offset` past the end on a complete scan returns
140
+ `(no results at offset N; M total)`.
141
+
142
+ **Errors:** `not_found`, `path_escape`, `invalid_input` (bad regex).
143
+
144
+ ## write_file
145
+
146
+ Create or overwrite a file with the given content (atomic).
147
+
148
+ **Input:** `path` (string, required); `content` (string, required).
149
+
150
+ **Behavior:** Writes `content` exactly. Missing parent directories are created.
151
+
152
+ **Errors:** `not_a_file`, `path_escape`, `io_error`.
153
+
154
+ ## edit_file
155
+
156
+ Replace one exact occurrence of `old_string` with `new_string`.
157
+
158
+ **Input:** `path` (required); `old_string` (required, matched literally, without
159
+ read_file's line-number prefixes); `new_string` (required, must differ);
160
+ `replace_all` (boolean, default false).
161
+
162
+ **Behavior:** `old_string` is first matched **literally and exactly**, and must be
163
+ unique unless `replace_all` is set. When an exact match is **not** found (single-
164
+ replacement path only — `replace_all` stays exact-only), a **whitespace-tolerant
165
+ cascade** runs: it tries, strictest→loosest, an indentation-flexible, a per-line-
166
+ trimmed, an all-whitespace-collapsed, and finally a trimmed-substring match. It applies
167
+ **only when it resolves to exactly one region** (otherwise `ambiguous_match`, never a
168
+ guess); `new_string` is substituted verbatim (no re-indentation), and the result message
169
+ discloses that a tolerant match was used. Line endings/BOM are preserved per the
170
+ conventions above.
171
+
172
+ Note the verbatim substitution replaces the **whole matched region, including its original
173
+ leading indentation**, with `new_string` exactly as given. So a tolerant match can change a
174
+ line's indentation if `new_string` does not itself carry it — the disclosed result message
175
+ flags this and re-reading the file is recommended. (This is a deliberate trade-off: the tool
176
+ never guesses re-indentation.)
177
+
178
+ **Errors:** `no_match`, `ambiguous_match` (multiple exact or multiple tolerant
179
+ matches without `replace_all`), `invalid_input` (identical strings), `not_found`,
180
+ `is_binary`, `too_large`, `path_escape`.
181
+
182
+ ## multi_edit
183
+
184
+ Apply several `edit_file`-style edits to ONE file in a single atomic call.
185
+
186
+ **Input:** `path` (required); `edits` (array of `{ old_string, new_string,
187
+ replace_all? }`, required).
188
+
189
+ **Behavior:** Edits run in order, each operating on the result of the previous, and
190
+ each inherits `edit_file`'s exact-then-whitespace-tolerant matching. The whole batch is
191
+ applied atomically; any failing edit aborts the call with its index and nothing is
192
+ written.
193
+
194
+ **Errors:** as `edit_file`, prefixed with the failing edit index.
195
+
196
+ ## apply_patch
197
+
198
+ Apply a unified diff across one or more files atomically.
199
+
200
+ **Input:** `patch` (string, required, unified diff). A `/dev/null` source (`---`)
201
+ denotes a create and a `/dev/null` target (`+++`) a delete. A block whose old and new
202
+ paths differ is a **rename/move**: with hunks it moves and edits in one step, without
203
+ hunks (or with a content-reproducing hunk) it is a pure rename that preserves the file's
204
+ exact bytes.
205
+
206
+ **Behavior:** Each file block is validated and applied; all changes commit
207
+ together or roll back together (a rename participates with both its endpoints). Creating
208
+ an existing path, a rename whose destination already exists, or multiple blocks naming
209
+ the same path (including either endpoint of a rename), is rejected. Modified and
210
+ moved-and-edited files preserve line endings/BOM per the conventions; created files use
211
+ LF; a pure rename leaves the bytes and mode untouched. The file tools operate on UTF-8:
212
+ a UTF-16 file is **read** by `read_file` but the editing tools (`edit_file`,
213
+ `multi_edit`, `apply_patch`) refuse it (`is_binary`) rather than silently rewrite it as
214
+ UTF-8.
215
+
216
+ **Errors:** `patch_failed` (hunk did not apply; reports the file), `invalid_input`,
217
+ `not_found`, `not_a_file`, `is_binary`, `too_large`, `path_escape`, `io_error`.
218
+
219
+ ## bash
220
+
221
+ Run a shell command via `sh -c` and return stdout, stderr, and exit code.
222
+
223
+ **Input:** `command` (string, required); `cwd` (string, default workspace root);
224
+ `timeout_ms` (integer, default `BASH_TIMEOUT_MS` = 120000; may be raised up to
225
+ `BASH_TIMEOUT_MAX_MS` = 600000 for a long build/test/install — a larger request is
226
+ clamped to the ceiling, not rejected).
227
+
228
+ **Behavior:** The command runs to completion and **blocks** until it exits;
229
+ stdin is closed. A long-lived process (dev server, watcher) must be backgrounded
230
+ with its output redirected, e.g. `npm start > /tmp/out.log 2>&1 &`. On success
231
+ the result is a JSON object `{ exit_code, stdout, stderr, signal, timed_out }`.
232
+ A non-zero exit is a normal result, not an error.
233
+
234
+ stdout and stderr are budgeted against a shared `MAX_OUTPUT_BYTES`; overflow is
235
+ written to a `.clarvis/` spill file referenced in the result. There is a hard
236
+ per-stream in-memory ceiling: a command producing unbounded output is killed
237
+ (process group) and the call fails with `output_limit`.
238
+
239
+ On timeout the process group is killed and `timeout` is returned (with the
240
+ partial stdout/stderr).
241
+
242
+ **Errors:** `timeout`, `output_limit`, `not_found`/`not_a_file` (bad `cwd`),
243
+ `path_escape` (`cwd` outside the workspace), `io_error` (spawn failure).
244
+
245
+ ---
246
+
247
+ ## Errors
248
+
249
+ Errors are returned as a JSON object `{ "error": <code>, "message": <string>, ...fields }`.
250
+ The codes are:
251
+
252
+ | Code | Meaning |
253
+ | ----------------- | --------------------------------------------------------- |
254
+ | `invalid_input` | Arguments failed schema or semantic validation. |
255
+ | `not_found` | Path does not exist. |
256
+ | `not_a_file` | Path was a directory where a file was expected (or vice). |
257
+ | `is_binary` | File appears to be binary; text tools refuse it. |
258
+ | `no_match` | `edit_file`/`multi_edit` could not find `old_string`. |
259
+ | `ambiguous_match` | `old_string` matched more than once without `replace_all`.|
260
+ | `patch_failed` | A patch hunk did not apply cleanly. |
261
+ | `timeout` | `bash` command exceeded its timeout. |
262
+ | `output_limit` | `bash` output exceeded the hard capture ceiling. |
263
+ | `too_large` | Input file exceeded `MAX_FILE_BYTES`. |
264
+ | `path_escape` | Path resolved outside the confined workspace root. |
265
+ | `io_error` | An underlying filesystem/process error. |
266
+ | `internal` | Unexpected internal error. |
@@ -0,0 +1,29 @@
1
+ export interface ServerConfig {
2
+ workspaceRoot: string;
3
+ maxOutputBytes: number;
4
+ maxFileBytes: number;
5
+ bashTimeoutMs: number;
6
+ bashTimeoutMaxMs: number;
7
+ ripgrepAvailable: boolean;
8
+ readOnly: boolean;
9
+ confineToWorkspace: boolean;
10
+ }
11
+ export declare const DEFAULT_MAX_OUTPUT_BYTES = 131072;
12
+ export declare const DEFAULT_MAX_FILE_BYTES = 20000000;
13
+ export declare const DEFAULT_BASH_TIMEOUT_MS = 120000;
14
+ export declare const DEFAULT_BASH_TIMEOUT_MAX_MS = 600000;
15
+ export declare class StartupError extends Error {
16
+ }
17
+ export interface AgentToolsOptions {
18
+ workspaceRoot: string;
19
+ readOnly?: boolean;
20
+ confineToWorkspace?: boolean;
21
+ maxOutputBytes?: number;
22
+ maxFileBytes?: number;
23
+ bashTimeoutMs?: number;
24
+ bashTimeoutMaxMs?: number;
25
+ probeRipgrep?: () => boolean;
26
+ }
27
+ export declare function resolveConfig(options: AgentToolsOptions): ServerConfig;
28
+ export declare function buildConfig(argv: string[], env: NodeJS.ProcessEnv, probe?: () => boolean): ServerConfig;
29
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,CAAC;IAEtB,cAAc,EAAE,MAAM,CAAC;IAEvB,YAAY,EAAE,MAAM,CAAC;IAErB,aAAa,EAAE,MAAM,CAAC;IAEtB,gBAAgB,EAAE,MAAM,CAAC;IAEzB,gBAAgB,EAAE,OAAO,CAAC;IAE1B,QAAQ,EAAE,OAAO,CAAC;IAElB,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,eAAO,MAAM,wBAAwB,SAAS,CAAC;AAC/C,eAAO,MAAM,sBAAsB,WAAa,CAAC;AACjD,eAAO,MAAM,uBAAuB,SAAS,CAAC;AAC9C,eAAO,MAAM,2BAA2B,SAAS,CAAC;AAIlD,qBAAa,YAAa,SAAQ,KAAK;CAAG;AAoG1C,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IAEtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC;CAC9B;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,YAAY,CAoCtE;AAED,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,KAAK,GAAE,MAAM,OAAsB,GAClC,YAAY,CAsCd"}
package/dist/config.js ADDED
@@ -0,0 +1,145 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { statSync } from "node:fs";
3
+ import path from "node:path";
4
+ export const DEFAULT_MAX_OUTPUT_BYTES = 131072;
5
+ export const DEFAULT_MAX_FILE_BYTES = 20_000_000;
6
+ export const DEFAULT_BASH_TIMEOUT_MS = 120000;
7
+ export const DEFAULT_BASH_TIMEOUT_MAX_MS = 600000;
8
+ const MIN_OUTPUT_BYTES = 1024;
9
+ const MIN_FILE_BYTES = 1024;
10
+ export class StartupError extends Error {
11
+ }
12
+ function parseWorkspaceArg(argv) {
13
+ for (let i = 0; i < argv.length; i++) {
14
+ const arg = argv[i];
15
+ if (arg === undefined)
16
+ continue;
17
+ if (arg === "--workspace") {
18
+ const value = argv[i + 1];
19
+ if (value === undefined || value.startsWith("--")) {
20
+ throw new StartupError("--workspace requires a path argument");
21
+ }
22
+ return value;
23
+ }
24
+ if (arg.startsWith("--workspace=")) {
25
+ const value = arg.slice("--workspace=".length);
26
+ if (value === "")
27
+ throw new StartupError("--workspace requires a path argument");
28
+ return value;
29
+ }
30
+ }
31
+ return undefined;
32
+ }
33
+ const BOOL_TRUE = new Set(["1", "true", "yes", "on"]);
34
+ const BOOL_FALSE = new Set(["0", "false", "no", "off", ""]);
35
+ function resolveReadOnly(argv, env) {
36
+ if (argv.includes("--read-only"))
37
+ return true;
38
+ const raw = env.READ_ONLY;
39
+ if (raw === undefined)
40
+ return false;
41
+ const norm = raw.trim().toLowerCase();
42
+ if (BOOL_TRUE.has(norm))
43
+ return true;
44
+ if (BOOL_FALSE.has(norm))
45
+ return false;
46
+ throw new StartupError(`READ_ONLY must be one of 1/true/yes/on or 0/false/no/off, got: ${raw}`);
47
+ }
48
+ function resolveConfine(argv, env) {
49
+ if (argv.includes("--allow-outside-workspace"))
50
+ return false;
51
+ const raw = env.ALLOW_OUTSIDE_WORKSPACE;
52
+ if (raw === undefined)
53
+ return true;
54
+ const norm = raw.trim().toLowerCase();
55
+ if (BOOL_TRUE.has(norm))
56
+ return false;
57
+ if (BOOL_FALSE.has(norm))
58
+ return true;
59
+ throw new StartupError(`ALLOW_OUTSIDE_WORKSPACE must be one of 1/true/yes/on or 0/false/no/off, got: ${raw}`);
60
+ }
61
+ function parsePositiveInt(value, fallback, name, min = 1) {
62
+ if (value === undefined || value === "")
63
+ return fallback;
64
+ if (!/^\d+$/.test(value)) {
65
+ throw new StartupError(`${name} must be a positive integer, got: ${value}`);
66
+ }
67
+ const n = Number(value);
68
+ if (!Number.isSafeInteger(n) || n < min) {
69
+ throw new StartupError(`${name} must be an integer >= ${min}, got: ${value}`);
70
+ }
71
+ return n;
72
+ }
73
+ function probeRipgrep() {
74
+ try {
75
+ const res = spawnSync("rg", ["--version"], { stdio: "ignore" });
76
+ return res.status === 0;
77
+ }
78
+ catch {
79
+ return false;
80
+ }
81
+ }
82
+ function validateWorkspace(rawRoot) {
83
+ const workspaceRoot = path.resolve(rawRoot);
84
+ let stat;
85
+ try {
86
+ stat = statSync(workspaceRoot);
87
+ }
88
+ catch {
89
+ throw new StartupError(`Workspace root does not exist: ${workspaceRoot}`);
90
+ }
91
+ if (!stat.isDirectory()) {
92
+ throw new StartupError(`Workspace root is not a directory: ${workspaceRoot}`);
93
+ }
94
+ return workspaceRoot;
95
+ }
96
+ function requireMin(n, min, name) {
97
+ if (!Number.isSafeInteger(n) || n < min) {
98
+ throw new StartupError(`${name} must be an integer >= ${min}, got: ${String(n)}`);
99
+ }
100
+ return n;
101
+ }
102
+ function assertTimeoutOrder(min, max, minLabel, maxLabel) {
103
+ if (max < min) {
104
+ throw new StartupError(`${maxLabel} (${max}) must be >= ${minLabel} (${min}).`);
105
+ }
106
+ }
107
+ export function resolveConfig(options) {
108
+ if (!options.workspaceRoot) {
109
+ throw new StartupError("No workspace root: options.workspaceRoot is required.");
110
+ }
111
+ const workspaceRoot = validateWorkspace(options.workspaceRoot);
112
+ const bashTimeoutMs = requireMin(options.bashTimeoutMs ?? DEFAULT_BASH_TIMEOUT_MS, 1, "bashTimeoutMs");
113
+ const bashTimeoutMaxMs = requireMin(options.bashTimeoutMaxMs ?? DEFAULT_BASH_TIMEOUT_MAX_MS, 1, "bashTimeoutMaxMs");
114
+ assertTimeoutOrder(bashTimeoutMs, bashTimeoutMaxMs, "bashTimeoutMs", "bashTimeoutMaxMs");
115
+ return {
116
+ workspaceRoot,
117
+ maxOutputBytes: requireMin(options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES, MIN_OUTPUT_BYTES, "maxOutputBytes"),
118
+ maxFileBytes: requireMin(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES, MIN_FILE_BYTES, "maxFileBytes"),
119
+ bashTimeoutMs,
120
+ bashTimeoutMaxMs,
121
+ ripgrepAvailable: (options.probeRipgrep ?? probeRipgrep)(),
122
+ readOnly: options.readOnly ?? false,
123
+ confineToWorkspace: options.confineToWorkspace ?? true,
124
+ };
125
+ }
126
+ export function buildConfig(argv, env, probe = probeRipgrep) {
127
+ const rawRoot = parseWorkspaceArg(argv) ?? env.WORKSPACE_ROOT;
128
+ if (!rawRoot) {
129
+ throw new StartupError("No workspace root: pass --workspace <path> or set WORKSPACE_ROOT.");
130
+ }
131
+ const bashTimeoutMs = parsePositiveInt(env.BASH_TIMEOUT_MS, DEFAULT_BASH_TIMEOUT_MS, "BASH_TIMEOUT_MS");
132
+ const bashTimeoutMaxMs = parsePositiveInt(env.BASH_TIMEOUT_MAX_MS, DEFAULT_BASH_TIMEOUT_MAX_MS, "BASH_TIMEOUT_MAX_MS");
133
+ assertTimeoutOrder(bashTimeoutMs, bashTimeoutMaxMs, "BASH_TIMEOUT_MS", "BASH_TIMEOUT_MAX_MS");
134
+ return resolveConfig({
135
+ workspaceRoot: rawRoot,
136
+ maxOutputBytes: parsePositiveInt(env.MAX_OUTPUT_BYTES, DEFAULT_MAX_OUTPUT_BYTES, "MAX_OUTPUT_BYTES", MIN_OUTPUT_BYTES),
137
+ maxFileBytes: parsePositiveInt(env.MAX_FILE_BYTES, DEFAULT_MAX_FILE_BYTES, "MAX_FILE_BYTES", MIN_FILE_BYTES),
138
+ bashTimeoutMs,
139
+ bashTimeoutMaxMs,
140
+ readOnly: resolveReadOnly(argv, env),
141
+ confineToWorkspace: resolveConfine(argv, env),
142
+ probeRipgrep: probe,
143
+ });
144
+ }
145
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/core.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { ServerConfig } from "./config.js";
2
+ export interface DispatchResult {
3
+ isError: boolean;
4
+ text: string;
5
+ }
6
+ export interface ToolInfo {
7
+ name: string;
8
+ description: string;
9
+ inputSchema: Record<string, unknown>;
10
+ }
11
+ export declare function listTools(config: ServerConfig): ToolInfo[];
12
+ export declare function dispatch(name: string, args: Record<string, unknown>, config: ServerConfig): Promise<DispatchResult>;
13
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAmBhD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,QAAQ,EAAE,CAM1D;AAED,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,cAAc,CAAC,CAyBzB"}
package/dist/core.js ADDED
@@ -0,0 +1,43 @@
1
+ import { createRequire } from "node:module";
2
+ import { ToolError, serializeError } from "./errors.js";
3
+ import { bound } from "./lib/output.js";
4
+ import { tools, getTool, selectSurface } from "./tools/registry.js";
5
+ const Ajv = createRequire(import.meta.url)("ajv").default;
6
+ const ajv = new Ajv({ allErrors: true, useDefaults: true });
7
+ const validators = new Map();
8
+ for (const tool of tools) {
9
+ validators.set(tool.name, ajv.compile(tool.inputSchema));
10
+ }
11
+ export function listTools(config) {
12
+ return selectSurface(config.readOnly).map((t) => ({
13
+ name: t.name,
14
+ description: t.description,
15
+ inputSchema: t.inputSchema,
16
+ }));
17
+ }
18
+ export async function dispatch(name, args, config) {
19
+ const tool = getTool(name, selectSurface(config.readOnly));
20
+ if (!tool) {
21
+ return {
22
+ isError: true,
23
+ text: serializeError(new ToolError("not_found", `Unknown tool: ${name}`)),
24
+ };
25
+ }
26
+ const validate = validators.get(name);
27
+ const filled = structuredClone(args);
28
+ if (!validate(filled)) {
29
+ const detail = ajv.errorsText(validate.errors, { separator: "; " });
30
+ return {
31
+ isError: true,
32
+ text: serializeError(new ToolError("invalid_input", detail || "invalid arguments")),
33
+ };
34
+ }
35
+ try {
36
+ const text = await tool.handler(filled, config);
37
+ return { isError: false, text: tool.bounded ? text : bound(text, config.maxOutputBytes) };
38
+ }
39
+ catch (err) {
40
+ return { isError: true, text: serializeError(err) };
41
+ }
42
+ }
43
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAYpE,MAAM,GAAG,GAAI,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAe,CAAC,OAAO,CAAC;AAEzE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;AAC5D,MAAM,UAAU,GAAG,IAAI,GAAG,EAA4B,CAAC;AACvD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;IACzB,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;AAC3D,CAAC;AAaD,MAAM,UAAU,SAAS,CAAC,MAAoB;IAC5C,OAAO,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAChD,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,WAAW,EAAE,CAAC,CAAC,WAAW;KAC3B,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAY,EACZ,IAA6B,EAC7B,MAAoB;IAEpB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC3D,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI,EAAE,cAAc,CAAC,IAAI,SAAS,CAAC,WAAW,EAAE,iBAAiB,IAAI,EAAE,CAAC,CAAC;SAC1E,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC;IACvC,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACpE,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI,EAAE,cAAc,CAAC,IAAI,SAAS,CAAC,eAAe,EAAE,MAAM,IAAI,mBAAmB,CAAC,CAAC;SACpF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC;IAC5F,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;IACtD,CAAC;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ export type ErrorCode = "invalid_input" | "not_found" | "not_a_file" | "is_binary" | "no_match" | "ambiguous_match" | "patch_failed" | "io_error" | "timeout" | "output_limit" | "too_large" | "path_escape" | "internal";
2
+ export declare class ToolError extends Error {
3
+ readonly code: ErrorCode;
4
+ readonly fields: Record<string, unknown>;
5
+ constructor(code: ErrorCode, message: string, fields?: Record<string, unknown>);
6
+ }
7
+ export declare function serializeError(err: unknown): string;
8
+ export declare function fsError(err: NodeJS.ErrnoException, path: string): ToolError;
9
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GACjB,eAAe,GACf,WAAW,GACX,YAAY,GACZ,WAAW,GACX,UAAU,GACV,iBAAiB,GACjB,cAAc,GACd,UAAU,GACV,SAAS,GACT,cAAc,GACd,WAAW,GACX,aAAa,GACb,UAAU,CAAC;AAEf,qBAAa,SAAU,SAAQ,KAAK;IAClC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAE7B,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;CAMnF;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAOnD;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAO3E"}
package/dist/errors.js ADDED
@@ -0,0 +1,29 @@
1
+ import { warn } from "./lib/log.js";
2
+ export class ToolError extends Error {
3
+ code;
4
+ fields;
5
+ constructor(code, message, fields = {}) {
6
+ super(message);
7
+ this.name = "ToolError";
8
+ this.code = code;
9
+ this.fields = fields;
10
+ }
11
+ }
12
+ export function serializeError(err) {
13
+ if (err instanceof ToolError) {
14
+ return JSON.stringify({ error: err.code, message: err.message, ...err.fields });
15
+ }
16
+ const detail = err instanceof Error ? (err.stack ?? err.message) : String(err);
17
+ warn(`clarvis-agent-tools: internal error: ${detail}\n`);
18
+ return JSON.stringify({ error: "internal", message: "internal error" });
19
+ }
20
+ export function fsError(err, path) {
21
+ if (err.code === "ENOENT")
22
+ return new ToolError("not_found", `No such file: ${path}`, { path });
23
+ if (err.code === "EISDIR")
24
+ return new ToolError("not_a_file", `Path is a directory: ${path}`, { path });
25
+ if (err.code === "ENOTDIR")
26
+ return new ToolError("not_a_file", `Not a directory: ${path}`, { path });
27
+ return new ToolError("io_error", `${err.code ?? "EIO"}: ${err.message}`, { path });
28
+ }
29
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAiBpC,MAAM,OAAO,SAAU,SAAQ,KAAK;IACzB,IAAI,CAAY;IAChB,MAAM,CAA0B;IAEzC,YAAY,IAAe,EAAE,OAAe,EAAE,SAAkC,EAAE;QAChF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,MAAM,UAAU,cAAc,CAAC,GAAY;IACzC,IAAI,GAAG,YAAY,SAAS,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAClF,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/E,IAAI,CAAC,wCAAwC,MAAM,IAAI,CAAC,CAAC;IACzD,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,GAA0B,EAAE,IAAY;IAC9D,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,SAAS,CAAC,WAAW,EAAE,iBAAiB,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAChG,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ;QACvB,OAAO,IAAI,SAAS,CAAC,YAAY,EAAE,wBAAwB,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/E,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS;QACxB,OAAO,IAAI,SAAS,CAAC,YAAY,EAAE,oBAAoB,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3E,OAAO,IAAI,SAAS,CAAC,UAAU,EAAE,GAAG,GAAG,CAAC,IAAI,IAAI,KAAK,KAAK,GAAG,CAAC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;AACrF,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { AgentToolsOptions, ServerConfig } from "./config.js";
2
+ import type { DispatchResult, ToolInfo } from "./core.js";
3
+ export interface AgentTools {
4
+ readonly config: ServerConfig;
5
+ listTools(): ToolInfo[];
6
+ callTool(name: string, args?: Record<string, unknown>): Promise<DispatchResult>;
7
+ }
8
+ export declare function createAgentTools(options: AgentToolsOptions): AgentTools;
9
+ export { dispatch, listTools } from "./core.js";
10
+ export type { DispatchResult, ToolInfo } from "./core.js";
11
+ export { resolveConfig, buildConfig, StartupError, DEFAULT_MAX_OUTPUT_BYTES, DEFAULT_MAX_FILE_BYTES, DEFAULT_BASH_TIMEOUT_MS, DEFAULT_BASH_TIMEOUT_MAX_MS, } from "./config.js";
12
+ export type { ServerConfig, AgentToolsOptions } from "./config.js";
13
+ export { tools, readOnlyTools, getTool, selectSurface } from "./tools/registry.js";
14
+ export type { ToolDef } from "./tools/types.js";
15
+ export { ToolError, serializeError, fsError } from "./errors.js";
16
+ export type { ErrorCode } from "./errors.js";
17
+ export { sweepSpillDir } from "./lib/output.js";
18
+ export { setWarnSink } from "./lib/log.js";
19
+ export type { WarnSink } from "./lib/log.js";
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE1D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAE9B,SAAS,IAAI,QAAQ,EAAE,CAAC;IAExB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACjF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAOvE;AAED,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAChD,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE1D,OAAO,EACL,aAAa,EACb,WAAW,EACX,YAAY,EACZ,wBAAwB,EACxB,sBAAsB,EACtB,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACnF,YAAY,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACjE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ import { resolveConfig } from "./config.js";
2
+ import { dispatch, listTools } from "./core.js";
3
+ export function createAgentTools(options) {
4
+ const config = resolveConfig(options);
5
+ return {
6
+ config,
7
+ listTools: () => listTools(config),
8
+ callTool: (name, args = {}) => dispatch(name, args, config),
9
+ };
10
+ }
11
+ export { dispatch, listTools } from "./core.js";
12
+ export { resolveConfig, buildConfig, StartupError, DEFAULT_MAX_OUTPUT_BYTES, DEFAULT_MAX_FILE_BYTES, DEFAULT_BASH_TIMEOUT_MS, DEFAULT_BASH_TIMEOUT_MAX_MS, } from "./config.js";
13
+ export { tools, readOnlyTools, getTool, selectSurface } from "./tools/registry.js";
14
+ export { ToolError, serializeError, fsError } from "./errors.js";
15
+ export { sweepSpillDir } from "./lib/output.js";
16
+ export { setWarnSink } from "./lib/log.js";
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAYhD,MAAM,UAAU,gBAAgB,CAAC,OAA0B;IACzD,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACtC,OAAO;QACL,MAAM;QACN,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC;QAClC,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC;KAC5D,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGhD,OAAO,EACL,aAAa,EACb,WAAW,EACX,YAAY,EACZ,wBAAwB,EACxB,sBAAsB,EACtB,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGnF,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAGjE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,11 @@
1
+ export declare function withFileLock<T>(absPath: string, fn: () => Promise<T>): Promise<T>;
2
+ export declare function withFileLocks<T>(paths: string[], fn: () => Promise<T>): Promise<T>;
3
+ export declare function writeAtomic(target: string, content: string): Promise<void>;
4
+ export interface FileOp {
5
+ type: "create" | "modify" | "delete" | "rename";
6
+ path: string;
7
+ from?: string;
8
+ content?: string;
9
+ }
10
+ export declare function applyOpsAtomic(ops: FileOp[]): Promise<void>;
11
+ //# sourceMappingURL=atomic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomic.d.ts","sourceRoot":"","sources":["../../src/lib/atomic.ts"],"names":[],"mappings":"AAOA,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAYjF;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAGlF;AAiED,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAahF;AAED,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAoLD,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCjE"}