@jarkkojs/readseek-api 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,952 @@
1
+ // src/contract.ts
2
+ var DIGEST_FACETS = [
3
+ "metadata",
4
+ "content",
5
+ "map",
6
+ "diagnostics",
7
+ "identity"
8
+ ];
9
+ var VISION_MODES = ["none", "all", "ocr", "caption", "objects"];
10
+ var VISION_ANALYSIS_MODES = ["all", "ocr", "caption", "objects"];
11
+ var VISION_LEVELS = ["low", "medium", "high"];
12
+ var DOCUMENT_NODE_KINDS = [
13
+ "artifact",
14
+ "footer",
15
+ "header",
16
+ "heading",
17
+ "marginal_label",
18
+ "page",
19
+ "page_number",
20
+ "paragraph",
21
+ "section",
22
+ "structural_section"
23
+ ];
24
+ var EDIT_VARIANTS = [
25
+ "set_line",
26
+ "replace_lines",
27
+ "insert_after",
28
+ "replace",
29
+ "replace_symbol"
30
+ ];
31
+ var GIT_SELECTION_FLAGS = ["cached", "others", "ignored"];
32
+ var TOOL_NAMES = [
33
+ "read",
34
+ "edit",
35
+ "write",
36
+ "grep",
37
+ "digest",
38
+ "view",
39
+ "search",
40
+ "def",
41
+ "refs",
42
+ "rename"
43
+ ];
44
+ var PARAM_DESCRIPTIONS = {
45
+ path: "Absolute or relative file path",
46
+ searchPath: "File or directory to search; defaults to the current directory",
47
+ language: "Language override when auto-detection is ambiguous",
48
+ languageOverride: "Language override",
49
+ languageSymbol: "Language override for symbol replacement",
50
+ pattern: "AST pattern (ast-grep style), such as console.log($$$ARGS)",
51
+ grepPattern: "Regex pattern; set literal for exact text",
52
+ name: "Qualified or unqualified symbol name",
53
+ refsName: "Identifier to find references for",
54
+ scope: "Restrict results to the binding at the cursor",
55
+ line: "One-based cursor line; required with scope",
56
+ renameLine: "One-based cursor line of the symbol to rename",
57
+ column: "One-based cursor byte column for disambiguation",
58
+ cached: "In a Git repository, search tracked/indexed files",
59
+ others: "In a Git repository, search untracked files",
60
+ ignored: "With others=true, include ignored untracked files",
61
+ select: "Facets: metadata, content, map, diagnostics, identity (default content)",
62
+ selectComma: "Comma-separated facets: metadata, content, map, diagnostics, identity",
63
+ at: "Location: line:N[:COLUMN], hash:HASH[:COLUMN], or symbol:NAME",
64
+ end: "Last line to include",
65
+ limit: "Maximum number of lines to include",
66
+ offset: "One-based starting line",
67
+ readLimit: "Maximum lines to return",
68
+ symbolRead: "Symbol name to read",
69
+ bundle: "Include related symbols from the same file; requires symbol",
70
+ image: "Standalone image mode: none, all, ocr, caption, objects. Required for standalone images.",
71
+ visionModeDigest: "Standalone image mode: none, all, caption, objects, or ocr",
72
+ visionModeView: "Analyze selected assets: all, ocr, caption, or objects",
73
+ visionLevel: "Vision inference level: low (default), medium, or high",
74
+ node: "Node ID to use as the view root",
75
+ page: "One-based source page",
76
+ kind: "Node kind filter",
77
+ depth: "Maximum depth below selected roots",
78
+ outline: "Return outline nodes only",
79
+ edits: "Use set_line, replace_lines, insert_after, replace_symbol, or replace",
80
+ editVariant: "Exactly one nested edit variant",
81
+ setLine: 'Replace one line: {"set_line":{"anchor":"LINE:HASH","new_text":"..."}}',
82
+ replaceLines: 'Replace a range: {"replace_lines":{"start_anchor":"LINE:HASH","end_anchor":"LINE:HASH","new_text":"..."}}',
83
+ insertAfter: 'Insert text: {"insert_after":{"anchor":"LINE:HASH","new_text":"..."}}',
84
+ replace: 'Replace text: {"replace":{"old_text":"...","new_text":"..."}}',
85
+ replaceSymbol: 'Replace a symbol: {"replace_symbol":{"symbol":"name","new_body":"..."}}',
86
+ anchor: "Fresh LINE:HASH anchor for the line to replace",
87
+ startAnchor: "Fresh LINE:HASH anchor for the first line in the range",
88
+ endAnchor: "Fresh LINE:HASH anchor for the last line in the range",
89
+ insertAnchor: "Fresh LINE:HASH anchor for the line after which to insert",
90
+ newTextLine: "Replacement text; use an empty string to delete the line",
91
+ newTextRange: "Replacement text; use an empty string to delete the range",
92
+ newTextInsert: "Text to insert after the anchored line",
93
+ oldText: "Exact text to find",
94
+ newText: "Replacement text",
95
+ all: "Replace every exact match",
96
+ symbol: "Mapped symbol name",
97
+ newBody: "Complete replacement body for the symbol",
98
+ apply: "When supported by the host, apply the verified plan instead of previewing only",
99
+ planHash: "Require the apply plan to match this dry-run plan hash",
100
+ workspace: "Rename across the project",
101
+ to: "New symbol name",
102
+ postEditVerify: "Read back and verify persisted content",
103
+ content: "Complete text file content",
104
+ glob: "File-name glob, such as *.ts",
105
+ ignoreCase: "Ignore case",
106
+ literal: "Treat pattern literally",
107
+ context: "Surrounding lines for each match",
108
+ matchLimit: "Maximum matches to return",
109
+ summary: "Return per-file counts",
110
+ grepScope: "Group matches by enclosing symbol",
111
+ scopeContext: "Context lines within each symbol"
112
+ };
113
+ var TOOL_DESCRIPTIONS = {
114
+ read: "Read anchored text by range or symbol. Standalone images require an explicit mode. Use it before edits to obtain fresh `LINE:HASH` anchors; use `view` for documents and `digest` with the map facet for structural outlines.",
115
+ edit: 'Edit existing text files with fresh `LINE:HASH` anchors. Read or search first when no fresh anchors are available. Each `edits[]` item must use exactly one nested object: `{"set_line":{"anchor":"LINE:HASH","new_text":"..."}}`, `{"replace_lines":{"start_anchor":"LINE:HASH","end_anchor":"LINE:HASH","new_text":"..."}}`, `{"insert_after":{"anchor":"LINE:HASH","new_text":"..."}}`, `{"replace_symbol":{"symbol":"name","new_body":"..."}}`, or `{"replace":{"old_text":"...","new_text":"..."}}`.',
116
+ write: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit` for small changes to an existing file.",
117
+ grep: "Search text or regex in files and return edit-ready `LINE:HASH` anchors. Use for identifiers, strings, configuration, errors, comments, and documentation.",
118
+ digest: "Digest source content and images into the native CLI envelope. Returns metadata plus requested facets unchanged from `readseek digest`; use for structural maps, diagnostics, and cursor identity.",
119
+ view: "Digest a document's structure or selected content. Start with the overview, then narrow by page, node, kind, or depth instead of reading the whole document. PDF is the first supported document format.",
120
+ search: "Search code by AST structure using ast-grep-style patterns. Results include line hashes usable as `LINE:HASH` anchors. Use `grep` for identifiers, strings, comments, configuration, documentation, or other plain text and regex.",
121
+ def: "Go to a symbol definition or declaration by name. Accepts qualified or unqualified names; use this instead of text search for functions, methods, classes, types, and other declarations.",
122
+ refs: "Find references or usages of an identifier by name or cursor binding. Results include enclosing symbols and line hashes usable as `LINE:HASH` anchors. Without cursor scope, results are identifier-name matches; with scope, they follow one binding and exclude shadows. Use text search for literals, and use this before renaming or deleting a symbol.",
123
+ rename: "Rename a symbol at a source location. Resolvable same-named bindings are preserved; verified edits apply by default. Set `apply: false` to preview the plan before changing files."
124
+ };
125
+ function renderToolDescription(name, aliases = {}) {
126
+ return TOOL_NAMES.reduce((description, toolName) => description.replaceAll(`\`${toolName}\``, `\`${aliases[toolName] ?? toolName}\``), TOOL_DESCRIPTIONS[name]);
127
+ }
128
+ var gitFlagsSchema = {
129
+ cached: {
130
+ type: "boolean",
131
+ description: PARAM_DESCRIPTIONS.cached
132
+ },
133
+ others: {
134
+ type: "boolean",
135
+ description: PARAM_DESCRIPTIONS.others
136
+ },
137
+ ignored: {
138
+ type: "boolean",
139
+ description: PARAM_DESCRIPTIONS.ignored
140
+ }
141
+ };
142
+ var editItemSchema = {
143
+ type: "object",
144
+ additionalProperties: false,
145
+ minProperties: 1,
146
+ maxProperties: 1,
147
+ properties: {
148
+ set_line: {
149
+ type: "object",
150
+ additionalProperties: false,
151
+ required: ["anchor", "new_text"],
152
+ properties: {
153
+ anchor: { type: "string", description: PARAM_DESCRIPTIONS.anchor },
154
+ new_text: { type: "string", description: PARAM_DESCRIPTIONS.newTextLine }
155
+ },
156
+ description: PARAM_DESCRIPTIONS.setLine
157
+ },
158
+ replace_lines: {
159
+ type: "object",
160
+ additionalProperties: false,
161
+ required: ["start_anchor", "end_anchor", "new_text"],
162
+ properties: {
163
+ start_anchor: { type: "string", description: PARAM_DESCRIPTIONS.startAnchor },
164
+ end_anchor: { type: "string", description: PARAM_DESCRIPTIONS.endAnchor },
165
+ new_text: { type: "string", description: PARAM_DESCRIPTIONS.newTextRange }
166
+ },
167
+ description: PARAM_DESCRIPTIONS.replaceLines
168
+ },
169
+ insert_after: {
170
+ type: "object",
171
+ additionalProperties: false,
172
+ required: ["anchor", "new_text"],
173
+ properties: {
174
+ anchor: { type: "string", description: PARAM_DESCRIPTIONS.insertAnchor },
175
+ new_text: { type: "string", description: PARAM_DESCRIPTIONS.newTextInsert }
176
+ },
177
+ description: PARAM_DESCRIPTIONS.insertAfter
178
+ },
179
+ replace: {
180
+ type: "object",
181
+ additionalProperties: false,
182
+ required: ["old_text", "new_text"],
183
+ properties: {
184
+ old_text: { type: "string", description: PARAM_DESCRIPTIONS.oldText },
185
+ new_text: { type: "string", description: PARAM_DESCRIPTIONS.newText },
186
+ all: { type: "boolean", description: PARAM_DESCRIPTIONS.all }
187
+ },
188
+ description: PARAM_DESCRIPTIONS.replace
189
+ },
190
+ replace_symbol: {
191
+ type: "object",
192
+ additionalProperties: false,
193
+ required: ["symbol", "new_body"],
194
+ properties: {
195
+ symbol: { type: "string", description: PARAM_DESCRIPTIONS.symbol },
196
+ new_body: { type: "string", description: PARAM_DESCRIPTIONS.newBody }
197
+ },
198
+ description: PARAM_DESCRIPTIONS.replaceSymbol
199
+ }
200
+ },
201
+ description: PARAM_DESCRIPTIONS.editVariant
202
+ };
203
+ var TOOL_CONTRACTS = [
204
+ {
205
+ name: "read",
206
+ title: "Read anchored text",
207
+ description: TOOL_DESCRIPTIONS.read,
208
+ inputSchema: {
209
+ type: "object",
210
+ properties: {
211
+ path: { type: "string", description: PARAM_DESCRIPTIONS.path },
212
+ offset: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.offset },
213
+ limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.readLimit },
214
+ symbol: { type: "string", description: PARAM_DESCRIPTIONS.symbolRead },
215
+ bundle: {
216
+ type: "string",
217
+ enum: ["local"],
218
+ description: PARAM_DESCRIPTIONS.bundle
219
+ },
220
+ image: {
221
+ type: "string",
222
+ enum: [...VISION_MODES],
223
+ description: PARAM_DESCRIPTIONS.image
224
+ }
225
+ },
226
+ required: ["path"],
227
+ additionalProperties: false
228
+ }
229
+ },
230
+ {
231
+ name: "edit",
232
+ title: "Hash-verified edit",
233
+ description: TOOL_DESCRIPTIONS.edit,
234
+ inputSchema: {
235
+ type: "object",
236
+ properties: {
237
+ path: { type: "string", description: PARAM_DESCRIPTIONS.path },
238
+ edits: {
239
+ type: "array",
240
+ minItems: 1,
241
+ description: PARAM_DESCRIPTIONS.edits,
242
+ items: editItemSchema
243
+ },
244
+ language: { type: "string", description: PARAM_DESCRIPTIONS.languageSymbol },
245
+ apply: { type: "boolean", description: PARAM_DESCRIPTIONS.apply },
246
+ planHash: { type: "string", description: PARAM_DESCRIPTIONS.planHash },
247
+ postEditVerify: { type: "boolean", description: PARAM_DESCRIPTIONS.postEditVerify }
248
+ },
249
+ required: ["path", "edits"],
250
+ additionalProperties: false
251
+ }
252
+ },
253
+ {
254
+ name: "write",
255
+ title: "Write file",
256
+ description: TOOL_DESCRIPTIONS.write,
257
+ inputSchema: {
258
+ type: "object",
259
+ properties: {
260
+ path: { type: "string", description: PARAM_DESCRIPTIONS.path },
261
+ content: { type: "string", description: PARAM_DESCRIPTIONS.content }
262
+ },
263
+ required: ["path", "content"],
264
+ additionalProperties: false
265
+ }
266
+ },
267
+ {
268
+ name: "grep",
269
+ title: "Text search",
270
+ description: TOOL_DESCRIPTIONS.grep,
271
+ inputSchema: {
272
+ type: "object",
273
+ properties: {
274
+ pattern: { type: "string", description: PARAM_DESCRIPTIONS.grepPattern },
275
+ path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
276
+ glob: { type: "string", description: PARAM_DESCRIPTIONS.glob },
277
+ ignoreCase: { type: "boolean", description: PARAM_DESCRIPTIONS.ignoreCase },
278
+ literal: { type: "boolean", description: PARAM_DESCRIPTIONS.literal },
279
+ context: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.context },
280
+ limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.matchLimit },
281
+ summary: { type: "boolean", description: PARAM_DESCRIPTIONS.summary },
282
+ scope: {
283
+ type: "string",
284
+ enum: ["symbol"],
285
+ description: PARAM_DESCRIPTIONS.grepScope
286
+ },
287
+ scopeContext: {
288
+ type: "integer",
289
+ minimum: 0,
290
+ description: PARAM_DESCRIPTIONS.scopeContext
291
+ }
292
+ },
293
+ required: ["pattern"],
294
+ additionalProperties: false
295
+ }
296
+ },
297
+ {
298
+ name: "digest",
299
+ title: "Digest source",
300
+ description: TOOL_DESCRIPTIONS.digest,
301
+ inputSchema: {
302
+ type: "object",
303
+ properties: {
304
+ path: { type: "string", description: PARAM_DESCRIPTIONS.path },
305
+ select: { type: "string", description: PARAM_DESCRIPTIONS.select },
306
+ at: { type: "string", description: PARAM_DESCRIPTIONS.at },
307
+ end: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.end },
308
+ limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.limit },
309
+ language: { type: "string", description: PARAM_DESCRIPTIONS.languageOverride },
310
+ visionMode: {
311
+ type: "string",
312
+ enum: [...VISION_MODES],
313
+ description: PARAM_DESCRIPTIONS.visionModeDigest
314
+ },
315
+ visionLevel: {
316
+ type: "string",
317
+ enum: [...VISION_LEVELS],
318
+ description: PARAM_DESCRIPTIONS.visionLevel
319
+ }
320
+ },
321
+ required: ["path"],
322
+ additionalProperties: false
323
+ }
324
+ },
325
+ {
326
+ name: "view",
327
+ title: "View document",
328
+ description: TOOL_DESCRIPTIONS.view,
329
+ inputSchema: {
330
+ type: "object",
331
+ properties: {
332
+ path: { type: "string", description: PARAM_DESCRIPTIONS.path },
333
+ node: { type: "string", description: PARAM_DESCRIPTIONS.node },
334
+ page: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.page },
335
+ kind: {
336
+ type: "string",
337
+ enum: [...DOCUMENT_NODE_KINDS],
338
+ description: PARAM_DESCRIPTIONS.kind
339
+ },
340
+ depth: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.depth },
341
+ outline: { type: "boolean", description: PARAM_DESCRIPTIONS.outline },
342
+ visionMode: {
343
+ type: "string",
344
+ enum: [...VISION_ANALYSIS_MODES],
345
+ description: PARAM_DESCRIPTIONS.visionModeView
346
+ },
347
+ visionLevel: {
348
+ type: "string",
349
+ enum: [...VISION_LEVELS],
350
+ description: PARAM_DESCRIPTIONS.visionLevel
351
+ }
352
+ },
353
+ required: ["path"],
354
+ additionalProperties: false
355
+ }
356
+ },
357
+ {
358
+ name: "search",
359
+ title: "AST search",
360
+ description: TOOL_DESCRIPTIONS.search,
361
+ inputSchema: {
362
+ type: "object",
363
+ properties: {
364
+ pattern: { type: "string", description: PARAM_DESCRIPTIONS.pattern },
365
+ path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
366
+ language: { type: "string", description: PARAM_DESCRIPTIONS.language },
367
+ ...gitFlagsSchema
368
+ },
369
+ required: ["pattern"],
370
+ additionalProperties: false
371
+ }
372
+ },
373
+ {
374
+ name: "def",
375
+ title: "Find definition",
376
+ description: TOOL_DESCRIPTIONS.def,
377
+ inputSchema: {
378
+ type: "object",
379
+ properties: {
380
+ name: { type: "string", description: PARAM_DESCRIPTIONS.name },
381
+ path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
382
+ language: { type: "string", description: PARAM_DESCRIPTIONS.language },
383
+ ...gitFlagsSchema
384
+ },
385
+ required: ["name"],
386
+ additionalProperties: false
387
+ }
388
+ },
389
+ {
390
+ name: "refs",
391
+ title: "Find references",
392
+ description: TOOL_DESCRIPTIONS.refs,
393
+ inputSchema: {
394
+ type: "object",
395
+ properties: {
396
+ name: { type: "string", description: PARAM_DESCRIPTIONS.refsName },
397
+ path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
398
+ language: { type: "string", description: PARAM_DESCRIPTIONS.language },
399
+ scope: { type: "boolean", description: PARAM_DESCRIPTIONS.scope },
400
+ line: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.line },
401
+ column: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.column },
402
+ ...gitFlagsSchema
403
+ },
404
+ additionalProperties: false
405
+ }
406
+ },
407
+ {
408
+ name: "rename",
409
+ title: "Rename symbol",
410
+ description: TOOL_DESCRIPTIONS.rename,
411
+ inputSchema: {
412
+ type: "object",
413
+ properties: {
414
+ path: { type: "string", description: PARAM_DESCRIPTIONS.path },
415
+ line: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.renameLine },
416
+ column: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.column },
417
+ to: { type: "string", description: PARAM_DESCRIPTIONS.to },
418
+ workspace: { type: "boolean", description: PARAM_DESCRIPTIONS.workspace },
419
+ apply: { type: "boolean", description: PARAM_DESCRIPTIONS.apply },
420
+ planHash: { type: "string", description: PARAM_DESCRIPTIONS.planHash }
421
+ },
422
+ required: ["path", "line", "to"],
423
+ additionalProperties: false
424
+ }
425
+ }
426
+ ];
427
+ // src/validation.ts
428
+ function validateGitSelection(input) {
429
+ if (input.ignored && !input.others) {
430
+ return { ok: false, error: "ignored requires others=true" };
431
+ }
432
+ return { ok: true };
433
+ }
434
+ function validateRefsNameVsScope(input) {
435
+ if (input.scope) {
436
+ if (input.line === undefined) {
437
+ return { ok: false, error: "scope requires line" };
438
+ }
439
+ return { ok: true };
440
+ }
441
+ if (input.line !== undefined || input.column !== undefined) {
442
+ return { ok: false, error: "line/column require scope=true" };
443
+ }
444
+ if (typeof input.name !== "string" || input.name.length === 0) {
445
+ return { ok: false, error: "name is required when scope is not set" };
446
+ }
447
+ return { ok: true };
448
+ }
449
+ function validateVisionLevelVsMode(input) {
450
+ if (input.outline && (input.visionMode !== undefined || input.visionLevel !== undefined)) {
451
+ return { ok: false, error: "Cannot combine outline with visionMode or visionLevel" };
452
+ }
453
+ if (input.visionLevel !== undefined && input.visionMode === undefined) {
454
+ return { ok: false, error: "visionLevel requires visionMode" };
455
+ }
456
+ return { ok: true };
457
+ }
458
+ function validateExactlyOneEditVariant(edits) {
459
+ if (!Array.isArray(edits) || edits.length === 0) {
460
+ return { ok: false, error: "edits must contain at least one edit" };
461
+ }
462
+ const normalized = [];
463
+ for (let index = 0;index < edits.length; index++) {
464
+ const item = edits[index];
465
+ if (!item || typeof item !== "object" || Array.isArray(item)) {
466
+ return { ok: false, error: `edits[${index}] must be an edit object` };
467
+ }
468
+ const keys = Object.keys(item);
469
+ if (keys.length !== 1 || !EDIT_VARIANTS.includes(keys[0])) {
470
+ return {
471
+ ok: false,
472
+ error: `edits[${index}] must contain exactly one of: ${EDIT_VARIANTS.join(", ")}`
473
+ };
474
+ }
475
+ normalized.push(item);
476
+ }
477
+ return { ok: true, edits: normalized };
478
+ }
479
+ // src/argv.ts
480
+ function buildLineLocation(line, column) {
481
+ return column === undefined ? `line:${line}` : `line:${line}:${column}`;
482
+ }
483
+ function appendGitSelectionArgs(args, input) {
484
+ const validated = validateGitSelection(input);
485
+ if (!validated.ok) {
486
+ throw new Error(validated.error);
487
+ }
488
+ const selection = [
489
+ input.cached ? "cached" : undefined,
490
+ input.others ? "others" : undefined,
491
+ input.ignored ? "ignored" : undefined
492
+ ].filter((value) => value !== undefined);
493
+ if (selection.length > 0) {
494
+ args.push("--git", selection.join(","));
495
+ }
496
+ return args;
497
+ }
498
+ function pushOptionalString(args, flag, value) {
499
+ if (value !== undefined)
500
+ args.push(flag, value);
501
+ }
502
+ function pushOptionalNumber(args, flag, value) {
503
+ if (value !== undefined)
504
+ args.push(flag, String(value));
505
+ }
506
+ function appendApplyAndPlanHash(args, input) {
507
+ const order = input.flagOrder ?? "apply-first";
508
+ if (order === "plan-hash-first") {
509
+ pushOptionalString(args, "--plan-hash", input.planHash);
510
+ if (input.apply)
511
+ args.push("--apply");
512
+ return args;
513
+ }
514
+ if (input.apply)
515
+ args.push("--apply");
516
+ pushOptionalString(args, "--plan-hash", input.planHash);
517
+ return args;
518
+ }
519
+ function selectArg(select) {
520
+ if (select === undefined)
521
+ return;
522
+ return Array.isArray(select) ? select.join(",") : select;
523
+ }
524
+ function buildDigestArgs(input) {
525
+ const vision = validateVisionLevelVsMode({
526
+ visionMode: input.visionMode,
527
+ visionLevel: input.visionLevel
528
+ });
529
+ if (!vision.ok)
530
+ throw new Error(vision.error);
531
+ const args = [];
532
+ if (input.readseekDir !== undefined)
533
+ args.push("--readseek-dir", input.readseekDir);
534
+ args.push("digest", input.path);
535
+ const select = selectArg(input.select);
536
+ if (select !== undefined)
537
+ args.push("--select", select);
538
+ pushOptionalString(args, "--at", input.at);
539
+ pushOptionalNumber(args, "--end", input.end);
540
+ pushOptionalNumber(args, "--limit", input.limit);
541
+ pushOptionalString(args, "--language", input.language);
542
+ pushOptionalString(args, "--vision-mode", input.visionMode);
543
+ pushOptionalString(args, "--vision-level", input.visionLevel);
544
+ pushOptionalString(args, "--stdin-name", input.stdinName);
545
+ return args;
546
+ }
547
+ function buildViewArgs(input) {
548
+ const vision = validateVisionLevelVsMode({
549
+ visionMode: input.visionMode,
550
+ visionLevel: input.visionLevel,
551
+ outline: input.outline
552
+ });
553
+ if (!vision.ok)
554
+ throw new Error(vision.error);
555
+ const args = [];
556
+ if (input.readseekDir !== undefined)
557
+ args.push("--readseek-dir", input.readseekDir);
558
+ args.push("view", input.path);
559
+ if (input.format !== undefined)
560
+ args.push("--format", input.format);
561
+ pushOptionalString(args, "--node", input.node);
562
+ pushOptionalNumber(args, "--page", input.page);
563
+ pushOptionalString(args, "--kind", input.kind);
564
+ pushOptionalNumber(args, "--depth", input.depth);
565
+ if (input.outline)
566
+ args.push("--outline");
567
+ pushOptionalString(args, "--vision-mode", input.visionMode);
568
+ pushOptionalString(args, "--vision-level", input.visionLevel);
569
+ return args;
570
+ }
571
+ function buildSearchArgs(input) {
572
+ const args = ["search", input.path, input.pattern];
573
+ pushOptionalString(args, "--language", input.language);
574
+ return appendGitSelectionArgs(args, input);
575
+ }
576
+ function buildDefArgs(input) {
577
+ const args = ["def", input.path];
578
+ if (input.format !== undefined)
579
+ args.push("--format", input.format);
580
+ args.push(input.name);
581
+ pushOptionalString(args, "--language", input.language);
582
+ return appendGitSelectionArgs(args, input);
583
+ }
584
+ function buildRefsArgs(input) {
585
+ const validated = validateRefsNameVsScope(input);
586
+ if (!validated.ok)
587
+ throw new Error(validated.error);
588
+ const args = ["refs", input.path];
589
+ if (input.scope) {
590
+ args.push("--at", buildLineLocation(input.line, input.column));
591
+ } else if (input.name !== undefined) {
592
+ args.push(input.name);
593
+ }
594
+ if (input.format !== undefined)
595
+ args.push("--format", input.format);
596
+ pushOptionalString(args, "--language", input.language);
597
+ return appendGitSelectionArgs(args, input);
598
+ }
599
+ function buildEditArgs(input) {
600
+ const args = ["edit", input.path];
601
+ appendApplyAndPlanHash(args, {
602
+ apply: input.apply,
603
+ planHash: input.planHash,
604
+ flagOrder: input.flagOrder ?? "apply-first"
605
+ });
606
+ pushOptionalString(args, "--language", input.language);
607
+ if (input.requestFromStdin)
608
+ args.push("--request", "-");
609
+ return args;
610
+ }
611
+ function buildRenameArgs(input) {
612
+ const args = ["rename", input.path, "--at", buildLineLocation(input.line, input.column), "--to", input.to];
613
+ if (input.workspace !== undefined)
614
+ args.push("--workspace", input.workspace);
615
+ appendApplyAndPlanHash(args, {
616
+ apply: input.apply,
617
+ planHash: input.planHash,
618
+ flagOrder: input.flagOrder ?? "plan-hash-first"
619
+ });
620
+ pushOptionalString(args, "--language", input.language);
621
+ return appendGitSelectionArgs(args, input);
622
+ }
623
+ // src/platform.ts
624
+ import { createRequire } from "node:module";
625
+ import path from "node:path";
626
+ var READSEEK_PLATFORM_PACKAGES = {
627
+ "darwin-arm64": "@jarkkojs/readseek-darwin-arm64",
628
+ "linux-arm64": "@jarkkojs/readseek-linux-arm64",
629
+ "linux-x64": "@jarkkojs/readseek-linux-x64",
630
+ "win32-x64": "@jarkkojs/readseek-win32-x64"
631
+ };
632
+ var resolveFromPackage = createRequire(import.meta.url).resolve;
633
+ function readSeekBinaryName(platform = process.platform) {
634
+ return platform === "win32" ? "readseek.exe" : "readseek";
635
+ }
636
+ function readSeekPlatformKey(platform = process.platform, arch = process.arch) {
637
+ return `${platform}-${arch}`;
638
+ }
639
+ function resolveReadSeekBinaryFromPackages(resolver = resolveFromPackage, options = {}) {
640
+ const resolve = typeof resolver === "function" ? resolver : resolver.resolve.bind(resolver);
641
+ const platform = options.platform ?? process.platform;
642
+ const arch = options.arch ?? process.arch;
643
+ const key = readSeekPlatformKey(platform, arch);
644
+ const platformPackage = READSEEK_PLATFORM_PACKAGES[key];
645
+ if (!platformPackage) {
646
+ const supported = Object.keys(READSEEK_PLATFORM_PACKAGES).join(", ");
647
+ throw new Error(`@jarkkojs/readseek ships no binary for ${key}; it supports ${supported}`);
648
+ }
649
+ const metaPackage = options.metaPackage ?? "@jarkkojs/readseek";
650
+ const metaPackageJson = resolve(`${metaPackage}/package.json`);
651
+ const metaPackageDir = path.dirname(metaPackageJson);
652
+ const packageJsonPath = resolve(`${platformPackage}/package.json`, {
653
+ paths: [metaPackageDir]
654
+ });
655
+ return path.join(path.dirname(packageJsonPath), "bin", readSeekBinaryName(platform));
656
+ }
657
+ // src/json.ts
658
+ var READSEEK_USAGE_HINT = /\n\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
659
+ function cleanReadSeekErrorText(stderr) {
660
+ return stderr.replace(READSEEK_USAGE_HINT, "").replace(/^error:\s*/i, "").replace(/\s*\n\s*/g, " ").trim();
661
+ }
662
+ function parseReadSeekJson(stdout) {
663
+ try {
664
+ return JSON.parse(stdout);
665
+ } catch {
666
+ const preview = stdout.trim().slice(0, 200);
667
+ throw new Error(`readseek returned invalid JSON: ${preview}`);
668
+ }
669
+ }
670
+ // src/process.ts
671
+ var DEFAULT_READSEEK_TIMEOUT_MS = 120000;
672
+ var DEFAULT_MAX_OUTPUT_BYTES = 32 * 1024 * 1024;
673
+ function resolveProcessLimits(options = {}) {
674
+ return {
675
+ timeoutMs: options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS,
676
+ maxOutputBytes: options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
677
+ cancelable: options.cancelable !== false
678
+ };
679
+ }
680
+ function readSeekCancellationError(signal) {
681
+ const reason = signal?.reason;
682
+ return reason instanceof Error && reason.name !== "AbortError" ? reason : new Error("readseek cancelled");
683
+ }
684
+ function failureFromProcessResult(result, options = {}) {
685
+ if (options.overflow) {
686
+ return new Error(`readseek ${options.overflow} exceeded the ${options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES}-byte output limit`);
687
+ }
688
+ if (options.timedOut) {
689
+ return new Error(`readseek timed out after ${options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS} ms`);
690
+ }
691
+ const cleaned = cleanReadSeekErrorText(result.stderr);
692
+ if (cleaned)
693
+ return new Error(cleaned);
694
+ if (result.signal)
695
+ return new Error(`readseek killed by signal ${result.signal}`);
696
+ return new Error(`readseek exited with status ${result.exitCode}`);
697
+ }
698
+ function ensureSuccessfulStdout(result) {
699
+ if (result.exitCode === 0)
700
+ return result.stdout;
701
+ throw failureFromProcessResult(result);
702
+ }
703
+ function parseSuccessfulJson(result) {
704
+ return parseReadSeekJson(ensureSuccessfulStdout(result));
705
+ }
706
+ // src/process-node.ts
707
+ import { spawn } from "node:child_process";
708
+ import { StringDecoder } from "node:string_decoder";
709
+ function attachBoundedStream(stream, maxOutputBytes, onOverflow) {
710
+ const decoder = new StringDecoder("utf8");
711
+ let text = "";
712
+ let bytes = 0;
713
+ let overflowed = false;
714
+ stream.on("data", (chunk) => {
715
+ if (overflowed)
716
+ return;
717
+ const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
718
+ bytes += buffer.length;
719
+ if (bytes > maxOutputBytes) {
720
+ overflowed = true;
721
+ onOverflow();
722
+ return;
723
+ }
724
+ text += decoder.write(buffer);
725
+ });
726
+ return {
727
+ text: () => text + decoder.end()
728
+ };
729
+ }
730
+ async function runReadSeekNode(binaryPath, args, options = {}) {
731
+ const limits = resolveProcessLimits(options);
732
+ if (limits.cancelable && options.signal?.aborted) {
733
+ throw readSeekCancellationError(options.signal);
734
+ }
735
+ return await new Promise((resolve, reject) => {
736
+ let settled = false;
737
+ let child;
738
+ let timer;
739
+ const finish = (result) => {
740
+ if (settled)
741
+ return;
742
+ settled = true;
743
+ if (timer !== undefined)
744
+ clearTimeout(timer);
745
+ options.signal?.removeEventListener("abort", onAbort);
746
+ resolve(result);
747
+ };
748
+ const fail = (error) => {
749
+ if (settled)
750
+ return;
751
+ settled = true;
752
+ if (timer !== undefined)
753
+ clearTimeout(timer);
754
+ options.signal?.removeEventListener("abort", onAbort);
755
+ reject(error instanceof Error ? error : new Error(String(error)));
756
+ };
757
+ const terminate = (error) => {
758
+ if (settled)
759
+ return;
760
+ fail(error);
761
+ try {
762
+ child.kill("SIGKILL");
763
+ } catch {}
764
+ };
765
+ const onAbort = () => {
766
+ terminate(readSeekCancellationError(options.signal));
767
+ };
768
+ try {
769
+ child = spawn(binaryPath, [...args], {
770
+ cwd: options.cwd,
771
+ env: process.env,
772
+ shell: false,
773
+ stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
774
+ windowsHide: true
775
+ });
776
+ } catch (error) {
777
+ fail(error);
778
+ return;
779
+ }
780
+ const stdout = attachBoundedStream(child.stdout, limits.maxOutputBytes, () => {
781
+ terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: "stdout", maxOutputBytes: limits.maxOutputBytes }));
782
+ });
783
+ const stderr = attachBoundedStream(child.stderr, limits.maxOutputBytes, () => {
784
+ terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: "stderr", maxOutputBytes: limits.maxOutputBytes }));
785
+ });
786
+ timer = setTimeout(() => {
787
+ terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { timedOut: true, timeoutMs: limits.timeoutMs }));
788
+ }, limits.timeoutMs);
789
+ timer.unref?.();
790
+ if (limits.cancelable && options.signal) {
791
+ options.signal.addEventListener("abort", onAbort, { once: true });
792
+ }
793
+ if (options.stdin !== undefined) {
794
+ child.stdin.on("error", (error) => {
795
+ if (error.code !== "EPIPE")
796
+ fail(error);
797
+ });
798
+ child.stdin.end(options.stdin, "utf8");
799
+ }
800
+ child.on("error", fail);
801
+ child.on("close", (code, signal) => {
802
+ finish({
803
+ stdout: stdout.text(),
804
+ stderr: stderr.text(),
805
+ exitCode: code,
806
+ signal
807
+ });
808
+ });
809
+ });
810
+ }
811
+ async function runReadSeekNodeText(binaryPath, args, options = {}) {
812
+ return ensureSuccessfulStdout(await runReadSeekNode(binaryPath, args, options));
813
+ }
814
+ async function runReadSeekNodeJson(binaryPath, args, options = {}) {
815
+ return parseSuccessfulJson(await runReadSeekNode(binaryPath, args, options));
816
+ }
817
+ // src/process-bun.ts
818
+ function getBunSpawn() {
819
+ const bunGlobal = globalThis;
820
+ const spawn2 = bunGlobal.Bun?.spawn;
821
+ if (typeof spawn2 !== "function") {
822
+ throw new Error("Bun.spawn is unavailable in this runtime");
823
+ }
824
+ return spawn2;
825
+ }
826
+ async function readBoundedText(stream, streamName, maxOutputBytes, onOverflow) {
827
+ const reader = stream.getReader();
828
+ const chunks = [];
829
+ let totalBytes = 0;
830
+ try {
831
+ while (true) {
832
+ const { done, value } = await reader.read();
833
+ if (done)
834
+ break;
835
+ totalBytes += value.byteLength;
836
+ if (totalBytes > maxOutputBytes) {
837
+ onOverflow();
838
+ await reader.cancel().catch(() => {
839
+ return;
840
+ });
841
+ throw failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: streamName, maxOutputBytes });
842
+ }
843
+ chunks.push(value);
844
+ }
845
+ } finally {
846
+ reader.releaseLock();
847
+ }
848
+ const bytes = new Uint8Array(totalBytes);
849
+ let offset = 0;
850
+ for (const chunk of chunks) {
851
+ bytes.set(chunk, offset);
852
+ offset += chunk.byteLength;
853
+ }
854
+ return new TextDecoder().decode(bytes);
855
+ }
856
+ async function runReadSeekBun(binaryPath, args, options = {}) {
857
+ const limits = resolveProcessLimits(options);
858
+ if (limits.cancelable && options.signal?.aborted) {
859
+ throw readSeekCancellationError(options.signal);
860
+ }
861
+ const spawn2 = getBunSpawn();
862
+ const stdin = options.stdin === undefined ? undefined : new TextEncoder().encode(options.stdin);
863
+ const spawnOptions = {
864
+ cwd: options.cwd,
865
+ env: process.env,
866
+ stderr: "pipe",
867
+ stdin,
868
+ stdout: "pipe"
869
+ };
870
+ const child = spawn2([binaryPath, ...args], limits.cancelable && options.signal ? { ...spawnOptions, killSignal: "SIGKILL", signal: options.signal } : spawnOptions);
871
+ const killForOverflow = () => {
872
+ try {
873
+ child.kill("SIGKILL");
874
+ } catch {}
875
+ };
876
+ let timeout;
877
+ const timeoutResult = new Promise((_resolve, reject) => {
878
+ timeout = setTimeout(() => {
879
+ try {
880
+ child.kill("SIGKILL");
881
+ } catch {}
882
+ reject(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { timedOut: true, timeoutMs: limits.timeoutMs }));
883
+ }, limits.timeoutMs);
884
+ timeout.unref?.();
885
+ });
886
+ const pending = Promise.all([
887
+ readBoundedText(child.stdout, "stdout", limits.maxOutputBytes, killForOverflow),
888
+ readBoundedText(child.stderr, "stderr", limits.maxOutputBytes, killForOverflow),
889
+ child.exited
890
+ ]);
891
+ try {
892
+ const [stdout, stderr, exitCode] = await Promise.race([pending, timeoutResult]);
893
+ if (limits.cancelable && options.signal?.aborted)
894
+ throw readSeekCancellationError(options.signal);
895
+ return { stdout, stderr, exitCode, signal: null };
896
+ } finally {
897
+ if (timeout !== undefined)
898
+ clearTimeout(timeout);
899
+ }
900
+ }
901
+ async function runReadSeekBunText(binaryPath, args, options = {}) {
902
+ return ensureSuccessfulStdout(await runReadSeekBun(binaryPath, args, options));
903
+ }
904
+ async function runReadSeekBunJson(binaryPath, args, options = {}) {
905
+ return parseSuccessfulJson(await runReadSeekBun(binaryPath, args, options));
906
+ }
907
+ export {
908
+ validateVisionLevelVsMode,
909
+ validateRefsNameVsScope,
910
+ validateGitSelection,
911
+ validateExactlyOneEditVariant,
912
+ runReadSeekNodeText,
913
+ runReadSeekNodeJson,
914
+ runReadSeekNode,
915
+ runReadSeekBunText,
916
+ runReadSeekBunJson,
917
+ runReadSeekBun,
918
+ resolveReadSeekBinaryFromPackages,
919
+ resolveProcessLimits,
920
+ renderToolDescription,
921
+ readSeekPlatformKey,
922
+ readSeekBinaryName,
923
+ parseSuccessfulJson,
924
+ parseReadSeekJson,
925
+ failureFromProcessResult,
926
+ ensureSuccessfulStdout,
927
+ cleanReadSeekErrorText,
928
+ buildViewArgs,
929
+ buildSearchArgs,
930
+ buildRenameArgs,
931
+ buildRefsArgs,
932
+ buildLineLocation,
933
+ buildEditArgs,
934
+ buildDigestArgs,
935
+ buildDefArgs,
936
+ appendGitSelectionArgs,
937
+ appendApplyAndPlanHash,
938
+ VISION_MODES,
939
+ VISION_LEVELS,
940
+ VISION_ANALYSIS_MODES,
941
+ TOOL_NAMES,
942
+ TOOL_DESCRIPTIONS,
943
+ TOOL_CONTRACTS,
944
+ READSEEK_PLATFORM_PACKAGES,
945
+ PARAM_DESCRIPTIONS,
946
+ GIT_SELECTION_FLAGS,
947
+ EDIT_VARIANTS,
948
+ DOCUMENT_NODE_KINDS,
949
+ DIGEST_FACETS,
950
+ DEFAULT_READSEEK_TIMEOUT_MS,
951
+ DEFAULT_MAX_OUTPUT_BYTES
952
+ };