@link-assistant/agent 0.0.8 → 0.0.11

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 (104) hide show
  1. package/EXAMPLES.md +80 -1
  2. package/MODELS.md +72 -24
  3. package/README.md +95 -2
  4. package/TOOLS.md +20 -0
  5. package/package.json +36 -2
  6. package/src/agent/agent.ts +68 -54
  7. package/src/auth/claude-oauth.ts +426 -0
  8. package/src/auth/index.ts +28 -26
  9. package/src/auth/plugins.ts +876 -0
  10. package/src/bun/index.ts +53 -43
  11. package/src/bus/global.ts +5 -5
  12. package/src/bus/index.ts +59 -53
  13. package/src/cli/bootstrap.js +12 -12
  14. package/src/cli/bootstrap.ts +6 -6
  15. package/src/cli/cmd/agent.ts +97 -92
  16. package/src/cli/cmd/auth.ts +468 -0
  17. package/src/cli/cmd/cmd.ts +2 -2
  18. package/src/cli/cmd/export.ts +41 -41
  19. package/src/cli/cmd/mcp.ts +210 -53
  20. package/src/cli/cmd/models.ts +30 -29
  21. package/src/cli/cmd/run.ts +269 -213
  22. package/src/cli/cmd/stats.ts +185 -146
  23. package/src/cli/error.ts +17 -13
  24. package/src/cli/ui.ts +78 -0
  25. package/src/command/index.ts +26 -26
  26. package/src/config/config.ts +528 -288
  27. package/src/config/markdown.ts +15 -15
  28. package/src/file/ripgrep.ts +201 -169
  29. package/src/file/time.ts +21 -18
  30. package/src/file/watcher.ts +51 -42
  31. package/src/file.ts +1 -1
  32. package/src/flag/flag.ts +26 -11
  33. package/src/format/formatter.ts +206 -162
  34. package/src/format/index.ts +61 -61
  35. package/src/global/index.ts +21 -21
  36. package/src/id/id.ts +47 -33
  37. package/src/index.js +554 -332
  38. package/src/json-standard/index.ts +173 -0
  39. package/src/mcp/index.ts +135 -128
  40. package/src/patch/index.ts +336 -267
  41. package/src/project/bootstrap.ts +15 -15
  42. package/src/project/instance.ts +43 -36
  43. package/src/project/project.ts +47 -47
  44. package/src/project/state.ts +37 -33
  45. package/src/provider/models-macro.ts +5 -5
  46. package/src/provider/models.ts +32 -32
  47. package/src/provider/opencode.js +19 -19
  48. package/src/provider/provider.ts +518 -277
  49. package/src/provider/transform.ts +143 -102
  50. package/src/server/project.ts +21 -21
  51. package/src/server/server.ts +111 -105
  52. package/src/session/agent.js +66 -60
  53. package/src/session/compaction.ts +136 -111
  54. package/src/session/index.ts +189 -156
  55. package/src/session/message-v2.ts +312 -268
  56. package/src/session/message.ts +73 -57
  57. package/src/session/processor.ts +180 -166
  58. package/src/session/prompt.ts +678 -533
  59. package/src/session/retry.ts +26 -23
  60. package/src/session/revert.ts +76 -62
  61. package/src/session/status.ts +26 -26
  62. package/src/session/summary.ts +97 -76
  63. package/src/session/system.ts +77 -63
  64. package/src/session/todo.ts +22 -16
  65. package/src/snapshot/index.ts +92 -76
  66. package/src/storage/storage.ts +157 -120
  67. package/src/tool/bash.ts +116 -106
  68. package/src/tool/batch.ts +73 -59
  69. package/src/tool/codesearch.ts +60 -53
  70. package/src/tool/edit.ts +319 -263
  71. package/src/tool/glob.ts +32 -28
  72. package/src/tool/grep.ts +72 -53
  73. package/src/tool/invalid.ts +7 -7
  74. package/src/tool/ls.ts +77 -64
  75. package/src/tool/multiedit.ts +30 -21
  76. package/src/tool/patch.ts +121 -94
  77. package/src/tool/read.ts +140 -122
  78. package/src/tool/registry.ts +38 -38
  79. package/src/tool/task.ts +93 -60
  80. package/src/tool/todo.ts +16 -16
  81. package/src/tool/tool.ts +45 -36
  82. package/src/tool/webfetch.ts +97 -74
  83. package/src/tool/websearch.ts +78 -64
  84. package/src/tool/write.ts +21 -15
  85. package/src/util/binary.ts +27 -19
  86. package/src/util/context.ts +8 -8
  87. package/src/util/defer.ts +7 -5
  88. package/src/util/error.ts +24 -19
  89. package/src/util/eventloop.ts +16 -10
  90. package/src/util/filesystem.ts +37 -33
  91. package/src/util/fn.ts +11 -8
  92. package/src/util/iife.ts +1 -1
  93. package/src/util/keybind.ts +44 -44
  94. package/src/util/lazy.ts +7 -7
  95. package/src/util/locale.ts +20 -16
  96. package/src/util/lock.ts +43 -38
  97. package/src/util/log.ts +95 -85
  98. package/src/util/queue.ts +8 -8
  99. package/src/util/rpc.ts +35 -23
  100. package/src/util/scrap.ts +4 -4
  101. package/src/util/signal.ts +5 -5
  102. package/src/util/timeout.ts +6 -6
  103. package/src/util/token.ts +2 -2
  104. package/src/util/wildcard.ts +38 -27
@@ -1,41 +1,41 @@
1
- import { NamedError } from "../util/error"
2
- import matter from "gray-matter"
3
- import { z } from "zod"
1
+ import { NamedError } from '../util/error';
2
+ import matter from 'gray-matter';
3
+ import { z } from 'zod';
4
4
 
5
5
  export namespace ConfigMarkdown {
6
- export const FILE_REGEX = /(?<![\w`])@(\.?[^\s`,.]*(?:\.[^\s`,.]+)*)/g
7
- export const SHELL_REGEX = /!`([^`]+)`/g
6
+ export const FILE_REGEX = /(?<![\w`])@(\.?[^\s`,.]*(?:\.[^\s`,.]+)*)/g;
7
+ export const SHELL_REGEX = /!`([^`]+)`/g;
8
8
 
9
9
  export function files(template: string) {
10
- return Array.from(template.matchAll(FILE_REGEX))
10
+ return Array.from(template.matchAll(FILE_REGEX));
11
11
  }
12
12
 
13
13
  export function shell(template: string) {
14
- return Array.from(template.matchAll(SHELL_REGEX))
14
+ return Array.from(template.matchAll(SHELL_REGEX));
15
15
  }
16
16
 
17
17
  export async function parse(filePath: string) {
18
- const template = await Bun.file(filePath).text()
18
+ const template = await Bun.file(filePath).text();
19
19
 
20
20
  try {
21
- const md = matter(template)
22
- return md
21
+ const md = matter(template);
22
+ return md;
23
23
  } catch (err) {
24
24
  throw new FrontmatterError(
25
25
  {
26
26
  path: filePath,
27
27
  message: `Failed to parse YAML frontmatter: ${err instanceof Error ? err.message : String(err)}`,
28
28
  },
29
- { cause: err },
30
- )
29
+ { cause: err }
30
+ );
31
31
  }
32
32
  }
33
33
 
34
34
  export const FrontmatterError = NamedError.create(
35
- "ConfigFrontmatterError",
35
+ 'ConfigFrontmatterError',
36
36
  z.object({
37
37
  path: z.string(),
38
38
  message: z.string(),
39
- }),
40
- )
39
+ })
40
+ );
41
41
  }
@@ -1,17 +1,17 @@
1
1
  // Ripgrep utility functions
2
- import path from "path"
3
- import { Global } from "../global"
4
- import fs from "fs/promises"
5
- import z from "zod"
6
- import { NamedError } from "../util/error"
7
- import { lazy } from "../util/lazy"
8
- import { $ } from "bun"
2
+ import path from 'path';
3
+ import { Global } from '../global';
4
+ import fs from 'fs/promises';
5
+ import z from 'zod';
6
+ import { NamedError } from '../util/error';
7
+ import { lazy } from '../util/lazy';
8
+ import { $ } from 'bun';
9
9
 
10
- import { ZipReader, BlobReader, BlobWriter } from "@zip.js/zip.js"
11
- import { Log } from "../util/log"
10
+ import { ZipReader, BlobReader, BlobWriter } from '@zip.js/zip.js';
11
+ import { Log } from '../util/log';
12
12
 
13
13
  export namespace Ripgrep {
14
- const log = Log.create({ service: "ripgrep" })
14
+ const log = Log.create({ service: 'ripgrep' });
15
15
  const Stats = z.object({
16
16
  elapsed: z.object({
17
17
  secs: z.number(),
@@ -24,19 +24,19 @@ export namespace Ripgrep {
24
24
  bytes_printed: z.number(),
25
25
  matched_lines: z.number(),
26
26
  matches: z.number(),
27
- })
27
+ });
28
28
 
29
29
  const Begin = z.object({
30
- type: z.literal("begin"),
30
+ type: z.literal('begin'),
31
31
  data: z.object({
32
32
  path: z.object({
33
33
  text: z.string(),
34
34
  }),
35
35
  }),
36
- })
36
+ });
37
37
 
38
38
  export const Match = z.object({
39
- type: z.literal("match"),
39
+ type: z.literal('match'),
40
40
  data: z.object({
41
41
  path: z.object({
42
42
  text: z.string(),
@@ -53,13 +53,13 @@ export namespace Ripgrep {
53
53
  }),
54
54
  start: z.number(),
55
55
  end: z.number(),
56
- }),
56
+ })
57
57
  ),
58
58
  }),
59
- })
59
+ });
60
60
 
61
61
  const End = z.object({
62
- type: z.literal("end"),
62
+ type: z.literal('end'),
63
63
  data: z.object({
64
64
  path: z.object({
65
65
  text: z.string(),
@@ -67,10 +67,10 @@ export namespace Ripgrep {
67
67
  binary_offset: z.number().nullable(),
68
68
  stats: Stats,
69
69
  }),
70
- })
70
+ });
71
71
 
72
72
  const Summary = z.object({
73
- type: z.literal("summary"),
73
+ type: z.literal('summary'),
74
74
  data: z.object({
75
75
  elapsed_total: z.object({
76
76
  human: z.string(),
@@ -79,313 +79,345 @@ export namespace Ripgrep {
79
79
  }),
80
80
  stats: Stats,
81
81
  }),
82
- })
82
+ });
83
83
 
84
- const Result = z.union([Begin, Match, End, Summary])
84
+ const Result = z.union([Begin, Match, End, Summary]);
85
85
 
86
- export type Result = z.infer<typeof Result>
87
- export type Match = z.infer<typeof Match>
88
- export type Begin = z.infer<typeof Begin>
89
- export type End = z.infer<typeof End>
90
- export type Summary = z.infer<typeof Summary>
86
+ export type Result = z.infer<typeof Result>;
87
+ export type Match = z.infer<typeof Match>;
88
+ export type Begin = z.infer<typeof Begin>;
89
+ export type End = z.infer<typeof End>;
90
+ export type Summary = z.infer<typeof Summary>;
91
91
  const PLATFORM = {
92
- "arm64-darwin": { platform: "aarch64-apple-darwin", extension: "tar.gz" },
93
- "arm64-linux": {
94
- platform: "aarch64-unknown-linux-gnu",
95
- extension: "tar.gz",
92
+ 'arm64-darwin': { platform: 'aarch64-apple-darwin', extension: 'tar.gz' },
93
+ 'arm64-linux': {
94
+ platform: 'aarch64-unknown-linux-gnu',
95
+ extension: 'tar.gz',
96
96
  },
97
- "x64-darwin": { platform: "x86_64-apple-darwin", extension: "tar.gz" },
98
- "x64-linux": { platform: "x86_64-unknown-linux-musl", extension: "tar.gz" },
99
- "x64-win32": { platform: "x86_64-pc-windows-msvc", extension: "zip" },
100
- } as const
97
+ 'x64-darwin': { platform: 'x86_64-apple-darwin', extension: 'tar.gz' },
98
+ 'x64-linux': { platform: 'x86_64-unknown-linux-musl', extension: 'tar.gz' },
99
+ 'x64-win32': { platform: 'x86_64-pc-windows-msvc', extension: 'zip' },
100
+ } as const;
101
101
 
102
102
  export const ExtractionFailedError = NamedError.create(
103
- "RipgrepExtractionFailedError",
103
+ 'RipgrepExtractionFailedError',
104
104
  z.object({
105
105
  filepath: z.string(),
106
106
  stderr: z.string(),
107
- }),
108
- )
107
+ })
108
+ );
109
109
 
110
110
  export const UnsupportedPlatformError = NamedError.create(
111
- "RipgrepUnsupportedPlatformError",
111
+ 'RipgrepUnsupportedPlatformError',
112
112
  z.object({
113
113
  platform: z.string(),
114
- }),
115
- )
114
+ })
115
+ );
116
116
 
117
117
  export const DownloadFailedError = NamedError.create(
118
- "RipgrepDownloadFailedError",
118
+ 'RipgrepDownloadFailedError',
119
119
  z.object({
120
120
  url: z.string(),
121
121
  status: z.number(),
122
- }),
123
- )
122
+ })
123
+ );
124
124
 
125
125
  const state = lazy(async () => {
126
- let filepath = Bun.which("rg")
127
- if (filepath) return { filepath }
128
- filepath = path.join(Global.Path.bin, "rg" + (process.platform === "win32" ? ".exe" : ""))
129
-
130
- const file = Bun.file(filepath)
126
+ let filepath = Bun.which('rg');
127
+ if (filepath) return { filepath };
128
+ filepath = path.join(
129
+ Global.Path.bin,
130
+ 'rg' + (process.platform === 'win32' ? '.exe' : '')
131
+ );
132
+
133
+ const file = Bun.file(filepath);
131
134
  if (!(await file.exists())) {
132
- const platformKey = `${process.arch}-${process.platform}` as keyof typeof PLATFORM
133
- const config = PLATFORM[platformKey]
134
- if (!config) throw new UnsupportedPlatformError({ platform: platformKey })
135
+ const platformKey =
136
+ `${process.arch}-${process.platform}` as keyof typeof PLATFORM;
137
+ const config = PLATFORM[platformKey];
138
+ if (!config)
139
+ throw new UnsupportedPlatformError({ platform: platformKey });
135
140
 
136
- const version = "14.1.1"
137
- const filename = `ripgrep-${version}-${config.platform}.${config.extension}`
138
- const url = `https://github.com/BurntSushi/ripgrep/releases/download/${version}/${filename}`
141
+ const version = '14.1.1';
142
+ const filename = `ripgrep-${version}-${config.platform}.${config.extension}`;
143
+ const url = `https://github.com/BurntSushi/ripgrep/releases/download/${version}/${filename}`;
139
144
 
140
- const response = await fetch(url)
141
- if (!response.ok) throw new DownloadFailedError({ url, status: response.status })
145
+ const response = await fetch(url);
146
+ if (!response.ok)
147
+ throw new DownloadFailedError({ url, status: response.status });
142
148
 
143
- const buffer = await response.arrayBuffer()
144
- const archivePath = path.join(Global.Path.bin, filename)
145
- await Bun.write(archivePath, buffer)
146
- if (config.extension === "tar.gz") {
147
- const args = ["tar", "-xzf", archivePath, "--strip-components=1"]
149
+ const buffer = await response.arrayBuffer();
150
+ const archivePath = path.join(Global.Path.bin, filename);
151
+ await Bun.write(archivePath, buffer);
152
+ if (config.extension === 'tar.gz') {
153
+ const args = ['tar', '-xzf', archivePath, '--strip-components=1'];
148
154
 
149
- if (platformKey.endsWith("-darwin")) args.push("--include=*/rg")
150
- if (platformKey.endsWith("-linux")) args.push("--wildcards", "*/rg")
155
+ if (platformKey.endsWith('-darwin')) args.push('--include=*/rg');
156
+ if (platformKey.endsWith('-linux')) args.push('--wildcards', '*/rg');
151
157
 
152
158
  const proc = Bun.spawn(args, {
153
159
  cwd: Global.Path.bin,
154
- stderr: "pipe",
155
- stdout: "pipe",
156
- })
157
- await proc.exited
160
+ stderr: 'pipe',
161
+ stdout: 'pipe',
162
+ });
163
+ await proc.exited;
158
164
  if (proc.exitCode !== 0)
159
165
  throw new ExtractionFailedError({
160
166
  filepath,
161
167
  stderr: await Bun.readableStreamToText(proc.stderr),
162
- })
168
+ });
163
169
  }
164
- if (config.extension === "zip") {
165
- if (config.extension === "zip") {
166
- const zipFileReader = new ZipReader(new BlobReader(new Blob([await Bun.file(archivePath).arrayBuffer()])))
167
- const entries = await zipFileReader.getEntries()
168
- let rgEntry: any
170
+ if (config.extension === 'zip') {
171
+ if (config.extension === 'zip') {
172
+ const zipFileReader = new ZipReader(
173
+ new BlobReader(
174
+ new Blob([await Bun.file(archivePath).arrayBuffer()])
175
+ )
176
+ );
177
+ const entries = await zipFileReader.getEntries();
178
+ let rgEntry: any;
169
179
  for (const entry of entries) {
170
- if (entry.filename.endsWith("rg.exe")) {
171
- rgEntry = entry
172
- break
180
+ if (entry.filename.endsWith('rg.exe')) {
181
+ rgEntry = entry;
182
+ break;
173
183
  }
174
184
  }
175
185
 
176
186
  if (!rgEntry) {
177
187
  throw new ExtractionFailedError({
178
188
  filepath: archivePath,
179
- stderr: "rg.exe not found in zip archive",
180
- })
189
+ stderr: 'rg.exe not found in zip archive',
190
+ });
181
191
  }
182
192
 
183
- const rgBlob = await rgEntry.getData(new BlobWriter())
193
+ const rgBlob = await rgEntry.getData(new BlobWriter());
184
194
  if (!rgBlob) {
185
195
  throw new ExtractionFailedError({
186
196
  filepath: archivePath,
187
- stderr: "Failed to extract rg.exe from zip archive",
188
- })
197
+ stderr: 'Failed to extract rg.exe from zip archive',
198
+ });
189
199
  }
190
- await Bun.write(filepath, await rgBlob.arrayBuffer())
191
- await zipFileReader.close()
200
+ await Bun.write(filepath, await rgBlob.arrayBuffer());
201
+ await zipFileReader.close();
192
202
  }
193
203
  }
194
- await fs.unlink(archivePath)
195
- if (!platformKey.endsWith("-win32")) await fs.chmod(filepath, 0o755)
204
+ await fs.unlink(archivePath);
205
+ if (!platformKey.endsWith('-win32')) await fs.chmod(filepath, 0o755);
196
206
  }
197
207
 
198
208
  return {
199
209
  filepath,
200
- }
201
- })
210
+ };
211
+ });
202
212
 
203
213
  export async function filepath() {
204
- const { filepath } = await state()
205
- return filepath
214
+ const { filepath } = await state();
215
+ return filepath;
206
216
  }
207
217
 
208
218
  export async function* files(input: { cwd: string; glob?: string[] }) {
209
- const args = [await filepath(), "--files", "--follow", "--hidden", "--glob=!.git/*"]
219
+ const args = [
220
+ await filepath(),
221
+ '--files',
222
+ '--follow',
223
+ '--hidden',
224
+ '--glob=!.git/*',
225
+ ];
210
226
  if (input.glob) {
211
227
  for (const g of input.glob) {
212
- args.push(`--glob=${g}`)
228
+ args.push(`--glob=${g}`);
213
229
  }
214
230
  }
215
231
 
216
232
  // Bun.spawn should throw this, but it incorrectly reports that the executable does not exist.
217
233
  // See https://github.com/oven-sh/bun/issues/24012
218
234
  if (!(await fs.stat(input.cwd).catch(() => undefined))?.isDirectory()) {
219
- throw Object.assign(new Error(`No such file or directory: '${input.cwd}'`), {
220
- code: "ENOENT",
221
- errno: -2,
222
- path: input.cwd,
223
- })
235
+ throw Object.assign(
236
+ new Error(`No such file or directory: '${input.cwd}'`),
237
+ {
238
+ code: 'ENOENT',
239
+ errno: -2,
240
+ path: input.cwd,
241
+ }
242
+ );
224
243
  }
225
244
 
226
245
  const proc = Bun.spawn(args, {
227
246
  cwd: input.cwd,
228
- stdout: "pipe",
229
- stderr: "ignore",
247
+ stdout: 'pipe',
248
+ stderr: 'ignore',
230
249
  maxBuffer: 1024 * 1024 * 20,
231
- })
250
+ });
232
251
 
233
- const reader = proc.stdout.getReader()
234
- const decoder = new TextDecoder()
235
- let buffer = ""
252
+ const reader = proc.stdout.getReader();
253
+ const decoder = new TextDecoder();
254
+ let buffer = '';
236
255
 
237
256
  try {
238
257
  while (true) {
239
- const { done, value } = await reader.read()
240
- if (done) break
258
+ const { done, value } = await reader.read();
259
+ if (done) break;
241
260
 
242
- buffer += decoder.decode(value, { stream: true })
243
- const lines = buffer.split("\n")
244
- buffer = lines.pop() || ""
261
+ buffer += decoder.decode(value, { stream: true });
262
+ const lines = buffer.split('\n');
263
+ buffer = lines.pop() || '';
245
264
 
246
265
  for (const line of lines) {
247
- if (line) yield line
266
+ if (line) yield line;
248
267
  }
249
268
  }
250
269
 
251
- if (buffer) yield buffer
270
+ if (buffer) yield buffer;
252
271
  } finally {
253
- reader.releaseLock()
254
- await proc.exited
272
+ reader.releaseLock();
273
+ await proc.exited;
255
274
  }
256
275
  }
257
276
 
258
277
  export async function tree(input: { cwd: string; limit?: number }) {
259
- log.info("tree", input)
260
- const files = await Array.fromAsync(Ripgrep.files({ cwd: input.cwd }))
278
+ log.info('tree', input);
279
+ const files = await Array.fromAsync(Ripgrep.files({ cwd: input.cwd }));
261
280
  interface Node {
262
- path: string[]
263
- children: Node[]
281
+ path: string[];
282
+ children: Node[];
264
283
  }
265
284
 
266
285
  function getPath(node: Node, parts: string[], create: boolean) {
267
- if (parts.length === 0) return node
268
- let current = node
286
+ if (parts.length === 0) return node;
287
+ let current = node;
269
288
  for (const part of parts) {
270
- let existing = current.children.find((x) => x.path.at(-1) === part)
289
+ let existing = current.children.find((x) => x.path.at(-1) === part);
271
290
  if (!existing) {
272
- if (!create) return
291
+ if (!create) return;
273
292
  existing = {
274
293
  path: current.path.concat(part),
275
294
  children: [],
276
- }
277
- current.children.push(existing)
295
+ };
296
+ current.children.push(existing);
278
297
  }
279
- current = existing
298
+ current = existing;
280
299
  }
281
- return current
300
+ return current;
282
301
  }
283
302
 
284
303
  const root: Node = {
285
304
  path: [],
286
305
  children: [],
287
- }
306
+ };
288
307
  for (const file of files) {
289
- if (file.includes(".opencode")) continue
290
- const parts = file.split(path.sep)
291
- getPath(root, parts, true)
308
+ if (file.includes('.opencode')) continue;
309
+ const parts = file.split(path.sep);
310
+ getPath(root, parts, true);
292
311
  }
293
312
 
294
313
  function sort(node: Node) {
295
314
  node.children.sort((a, b) => {
296
- if (!a.children.length && b.children.length) return 1
297
- if (!b.children.length && a.children.length) return -1
298
- return a.path.at(-1)!.localeCompare(b.path.at(-1)!)
299
- })
315
+ if (!a.children.length && b.children.length) return 1;
316
+ if (!b.children.length && a.children.length) return -1;
317
+ return a.path.at(-1)!.localeCompare(b.path.at(-1)!);
318
+ });
300
319
  for (const child of node.children) {
301
- sort(child)
320
+ sort(child);
302
321
  }
303
322
  }
304
- sort(root)
323
+ sort(root);
305
324
 
306
- let current = [root]
325
+ let current = [root];
307
326
  const result: Node = {
308
327
  path: [],
309
328
  children: [],
310
- }
329
+ };
311
330
 
312
- let processed = 0
313
- const limit = input.limit ?? 50
331
+ let processed = 0;
332
+ const limit = input.limit ?? 50;
314
333
  while (current.length > 0) {
315
- const next = []
334
+ const next = [];
316
335
  for (const node of current) {
317
- if (node.children.length) next.push(...node.children)
336
+ if (node.children.length) next.push(...node.children);
318
337
  }
319
- const max = Math.max(...current.map((x) => x.children.length))
338
+ const max = Math.max(...current.map((x) => x.children.length));
320
339
  for (let i = 0; i < max && processed < limit; i++) {
321
340
  for (const node of current) {
322
- const child = node.children[i]
323
- if (!child) continue
324
- getPath(result, child.path, true)
325
- processed++
326
- if (processed >= limit) break
341
+ const child = node.children[i];
342
+ if (!child) continue;
343
+ getPath(result, child.path, true);
344
+ processed++;
345
+ if (processed >= limit) break;
327
346
  }
328
347
  }
329
348
  if (processed >= limit) {
330
349
  for (const node of [...current, ...next]) {
331
- const compare = getPath(result, node.path, false)
332
- if (!compare) continue
350
+ const compare = getPath(result, node.path, false);
351
+ if (!compare) continue;
333
352
  if (compare?.children.length !== node.children.length) {
334
- const diff = node.children.length - compare.children.length
353
+ const diff = node.children.length - compare.children.length;
335
354
  compare.children.push({
336
355
  path: compare.path.concat(`[${diff} truncated]`),
337
356
  children: [],
338
- })
357
+ });
339
358
  }
340
359
  }
341
- break
360
+ break;
342
361
  }
343
- current = next
362
+ current = next;
344
363
  }
345
364
 
346
- const lines: string[] = []
365
+ const lines: string[] = [];
347
366
 
348
367
  function render(node: Node, depth: number) {
349
- const indent = "\t".repeat(depth)
350
- lines.push(indent + node.path.at(-1) + (node.children.length ? "/" : ""))
368
+ const indent = '\t'.repeat(depth);
369
+ lines.push(indent + node.path.at(-1) + (node.children.length ? '/' : ''));
351
370
  for (const child of node.children) {
352
- render(child, depth + 1)
371
+ render(child, depth + 1);
353
372
  }
354
373
  }
355
- result.children.map((x) => render(x, 0))
374
+ result.children.map((x) => render(x, 0));
356
375
 
357
- return lines.join("\n")
376
+ return lines.join('\n');
358
377
  }
359
378
 
360
- export async function search(input: { cwd: string; pattern: string; glob?: string[]; limit?: number }) {
361
- const args = [`${await filepath()}`, "--json", "--hidden", "--glob='!.git/*'"]
379
+ export async function search(input: {
380
+ cwd: string;
381
+ pattern: string;
382
+ glob?: string[];
383
+ limit?: number;
384
+ }) {
385
+ const args = [
386
+ `${await filepath()}`,
387
+ '--json',
388
+ '--hidden',
389
+ "--glob='!.git/*'",
390
+ ];
362
391
 
363
392
  if (input.glob) {
364
393
  for (const g of input.glob) {
365
- args.push(`--glob=${g}`)
394
+ args.push(`--glob=${g}`);
366
395
  }
367
396
  }
368
397
 
369
398
  if (input.limit) {
370
- args.push(`--max-count=${input.limit}`)
399
+ args.push(`--max-count=${input.limit}`);
371
400
  }
372
401
 
373
- args.push("--")
374
- args.push(input.pattern)
402
+ args.push('--');
403
+ args.push(input.pattern);
375
404
 
376
- const command = args.join(" ")
377
- const result = await $`${{ raw: command }}`.cwd(input.cwd).quiet().nothrow()
405
+ const command = args.join(' ');
406
+ const result = await $`${{ raw: command }}`
407
+ .cwd(input.cwd)
408
+ .quiet()
409
+ .nothrow();
378
410
  if (result.exitCode !== 0) {
379
- return []
411
+ return [];
380
412
  }
381
413
 
382
- const lines = result.text().trim().split("\n").filter(Boolean)
414
+ const lines = result.text().trim().split('\n').filter(Boolean);
383
415
  // Parse JSON lines from ripgrep output
384
416
 
385
417
  return lines
386
418
  .map((line) => JSON.parse(line))
387
419
  .map((parsed) => Result.parse(parsed))
388
- .filter((r) => r.type === "match")
389
- .map((r) => r.data)
420
+ .filter((r) => r.type === 'match')
421
+ .map((r) => r.data);
390
422
  }
391
423
  }