@link-assistant/agent 0.0.9 → 0.0.12

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 +36 -0
  2. package/MODELS.md +72 -24
  3. package/README.md +59 -2
  4. package/TOOLS.md +20 -0
  5. package/package.json +35 -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 +469 -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 +144 -119
  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 +39 -24
  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 +346 -199
  38. package/src/json-standard/index.ts +67 -51
  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,49 +1,51 @@
1
- import { Log } from "../util/log"
2
- import path from "path"
3
- import fs from "fs/promises"
4
- import { Global } from "../global"
5
- import { lazy } from "../util/lazy"
6
- import { Lock } from "../util/lock"
7
- import { $ } from "bun"
8
- import { NamedError } from "../util/error"
9
- import z from "zod"
1
+ import { Log } from '../util/log';
2
+ import path from 'path';
3
+ import fs from 'fs/promises';
4
+ import { Global } from '../global';
5
+ import { lazy } from '../util/lazy';
6
+ import { Lock } from '../util/lock';
7
+ import { $ } from 'bun';
8
+ import { NamedError } from '../util/error';
9
+ import z from 'zod';
10
10
 
11
11
  export namespace Storage {
12
- const log = Log.create({ service: "storage" })
12
+ const log = Log.create({ service: 'storage' });
13
13
 
14
- type Migration = (dir: string) => Promise<void>
14
+ type Migration = (dir: string) => Promise<void>;
15
15
 
16
16
  export const NotFoundError = NamedError.create(
17
- "NotFoundError",
17
+ 'NotFoundError',
18
18
  z.object({
19
19
  message: z.string(),
20
- }),
21
- )
20
+ })
21
+ );
22
22
 
23
23
  const MIGRATIONS: Migration[] = [
24
24
  async (dir) => {
25
- const project = path.resolve(dir, "../project")
26
- if (!fs.exists(project)) return
27
- for await (const projectDir of new Bun.Glob("*").scan({
25
+ const project = path.resolve(dir, '../project');
26
+ if (!fs.exists(project)) return;
27
+ for await (const projectDir of new Bun.Glob('*').scan({
28
28
  cwd: project,
29
29
  onlyFiles: false,
30
30
  })) {
31
- log.info(`migrating project ${projectDir}`)
32
- let projectID = projectDir
33
- const fullProjectDir = path.join(project, projectDir)
34
- let worktree = "/"
31
+ log.info(`migrating project ${projectDir}`);
32
+ let projectID = projectDir;
33
+ const fullProjectDir = path.join(project, projectDir);
34
+ let worktree = '/';
35
35
 
36
- if (projectID !== "global") {
37
- for await (const msgFile of new Bun.Glob("storage/session/message/*/*.json").scan({
36
+ if (projectID !== 'global') {
37
+ for await (const msgFile of new Bun.Glob(
38
+ 'storage/session/message/*/*.json'
39
+ ).scan({
38
40
  cwd: path.join(project, projectDir),
39
41
  absolute: true,
40
42
  })) {
41
- const json = await Bun.file(msgFile).json()
42
- worktree = json.path?.root
43
- if (worktree) break
43
+ const json = await Bun.file(msgFile).json();
44
+ worktree = json.path?.root;
45
+ if (worktree) break;
44
46
  }
45
- if (!worktree) continue
46
- if (!(await fs.exists(worktree))) continue
47
+ if (!worktree) continue;
48
+ if (!(await fs.exists(worktree))) continue;
47
49
  const [id] = await $`git rev-list --max-parents=0 --all`
48
50
  .quiet()
49
51
  .nothrow()
@@ -51,66 +53,85 @@ export namespace Storage {
51
53
  .text()
52
54
  .then((x) =>
53
55
  x
54
- .split("\n")
56
+ .split('\n')
55
57
  .filter(Boolean)
56
58
  .map((x) => x.trim())
57
- .toSorted(),
58
- )
59
- if (!id) continue
60
- projectID = id
59
+ .toSorted()
60
+ );
61
+ if (!id) continue;
62
+ projectID = id;
61
63
 
62
64
  await Bun.write(
63
- path.join(dir, "project", projectID + ".json"),
65
+ path.join(dir, 'project', projectID + '.json'),
64
66
  JSON.stringify({
65
67
  id,
66
- vcs: "git",
68
+ vcs: 'git',
67
69
  worktree,
68
70
  time: {
69
71
  created: Date.now(),
70
72
  initialized: Date.now(),
71
73
  },
72
- }),
73
- )
74
+ })
75
+ );
74
76
 
75
- log.info(`migrating sessions for project ${projectID}`)
76
- for await (const sessionFile of new Bun.Glob("storage/session/info/*.json").scan({
77
+ log.info(`migrating sessions for project ${projectID}`);
78
+ for await (const sessionFile of new Bun.Glob(
79
+ 'storage/session/info/*.json'
80
+ ).scan({
77
81
  cwd: fullProjectDir,
78
82
  absolute: true,
79
83
  })) {
80
- const dest = path.join(dir, "session", projectID, path.basename(sessionFile))
81
- log.info("copying", {
84
+ const dest = path.join(
85
+ dir,
86
+ 'session',
87
+ projectID,
88
+ path.basename(sessionFile)
89
+ );
90
+ log.info('copying', {
82
91
  sessionFile,
83
92
  dest,
84
- })
85
- const session = await Bun.file(sessionFile).json()
86
- await Bun.write(dest, JSON.stringify(session))
87
- log.info(`migrating messages for session ${session.id}`)
88
- for await (const msgFile of new Bun.Glob(`storage/session/message/${session.id}/*.json`).scan({
93
+ });
94
+ const session = await Bun.file(sessionFile).json();
95
+ await Bun.write(dest, JSON.stringify(session));
96
+ log.info(`migrating messages for session ${session.id}`);
97
+ for await (const msgFile of new Bun.Glob(
98
+ `storage/session/message/${session.id}/*.json`
99
+ ).scan({
89
100
  cwd: fullProjectDir,
90
101
  absolute: true,
91
102
  })) {
92
- const dest = path.join(dir, "message", session.id, path.basename(msgFile))
93
- log.info("copying", {
103
+ const dest = path.join(
104
+ dir,
105
+ 'message',
106
+ session.id,
107
+ path.basename(msgFile)
108
+ );
109
+ log.info('copying', {
94
110
  msgFile,
95
111
  dest,
96
- })
97
- const message = await Bun.file(msgFile).json()
98
- await Bun.write(dest, JSON.stringify(message))
112
+ });
113
+ const message = await Bun.file(msgFile).json();
114
+ await Bun.write(dest, JSON.stringify(message));
99
115
 
100
- log.info(`migrating parts for message ${message.id}`)
101
- for await (const partFile of new Bun.Glob(`storage/session/part/${session.id}/${message.id}/*.json`).scan(
102
- {
103
- cwd: fullProjectDir,
104
- absolute: true,
105
- },
106
- )) {
107
- const dest = path.join(dir, "part", message.id, path.basename(partFile))
108
- const part = await Bun.file(partFile).json()
109
- log.info("copying", {
116
+ log.info(`migrating parts for message ${message.id}`);
117
+ for await (const partFile of new Bun.Glob(
118
+ `storage/session/part/${session.id}/${message.id}/*.json`
119
+ ).scan({
120
+ cwd: fullProjectDir,
121
+ absolute: true,
122
+ })) {
123
+ const dest = path.join(
124
+ dir,
125
+ 'part',
126
+ message.id,
127
+ path.basename(partFile)
128
+ );
129
+ const part = await Bun.file(partFile).json();
130
+ log.info('copying', {
110
131
  partFile,
111
132
  dest,
112
- })
113
- await Bun.write(dest, JSON.stringify(part))
133
+ });
134
+ await Bun.write(dest, JSON.stringify(part));
114
135
  }
115
136
  }
116
137
  }
@@ -118,109 +139,125 @@ export namespace Storage {
118
139
  }
119
140
  },
120
141
  async (dir) => {
121
- for await (const item of new Bun.Glob("session/*/*.json").scan({
142
+ for await (const item of new Bun.Glob('session/*/*.json').scan({
122
143
  cwd: dir,
123
144
  absolute: true,
124
145
  })) {
125
- const session = await Bun.file(item).json()
126
- if (!session.projectID) continue
127
- if (!session.summary?.diffs) continue
128
- const { diffs } = session.summary
129
- await Bun.file(path.join(dir, "session_diff", session.id + ".json")).write(JSON.stringify(diffs))
130
- await Bun.file(path.join(dir, "session", session.projectID, session.id + ".json")).write(
146
+ const session = await Bun.file(item).json();
147
+ if (!session.projectID) continue;
148
+ if (!session.summary?.diffs) continue;
149
+ const { diffs } = session.summary;
150
+ await Bun.file(
151
+ path.join(dir, 'session_diff', session.id + '.json')
152
+ ).write(JSON.stringify(diffs));
153
+ await Bun.file(
154
+ path.join(dir, 'session', session.projectID, session.id + '.json')
155
+ ).write(
131
156
  JSON.stringify({
132
157
  ...session,
133
158
  summary: {
134
- additions: diffs.reduce((sum: any, x: any) => sum + x.additions, 0),
135
- deletions: diffs.reduce((sum: any, x: any) => sum + x.deletions, 0),
159
+ additions: diffs.reduce(
160
+ (sum: any, x: any) => sum + x.additions,
161
+ 0
162
+ ),
163
+ deletions: diffs.reduce(
164
+ (sum: any, x: any) => sum + x.deletions,
165
+ 0
166
+ ),
136
167
  },
137
- }),
138
- )
168
+ })
169
+ );
139
170
  }
140
171
  },
141
- ]
172
+ ];
142
173
 
143
174
  const state = lazy(async () => {
144
- const dir = path.join(Global.Path.data, "storage")
145
- const migration = await Bun.file(path.join(dir, "migration"))
175
+ const dir = path.join(Global.Path.data, 'storage');
176
+ const migration = await Bun.file(path.join(dir, 'migration'))
146
177
  .json()
147
178
  .then((x) => parseInt(x))
148
- .catch(() => 0)
179
+ .catch(() => 0);
149
180
  for (let index = migration; index < MIGRATIONS.length; index++) {
150
- log.info("running migration", { index })
151
- const migration = MIGRATIONS[index]
152
- await migration(dir).catch(() => log.error("failed to run migration", { index }))
153
- await Bun.write(path.join(dir, "migration"), (index + 1).toString())
181
+ log.info('running migration', { index });
182
+ const migration = MIGRATIONS[index];
183
+ await migration(dir).catch(() =>
184
+ log.error('failed to run migration', { index })
185
+ );
186
+ await Bun.write(path.join(dir, 'migration'), (index + 1).toString());
154
187
  }
155
188
  return {
156
189
  dir,
157
- }
158
- })
190
+ };
191
+ });
159
192
 
160
193
  export async function remove(key: string[]) {
161
- const dir = await state().then((x) => x.dir)
162
- const target = path.join(dir, ...key) + ".json"
194
+ const dir = await state().then((x) => x.dir);
195
+ const target = path.join(dir, ...key) + '.json';
163
196
  return withErrorHandling(async () => {
164
- await fs.unlink(target).catch(() => {})
165
- })
197
+ await fs.unlink(target).catch(() => {});
198
+ });
166
199
  }
167
200
 
168
201
  export async function read<T>(key: string[]) {
169
- const dir = await state().then((x) => x.dir)
170
- const target = path.join(dir, ...key) + ".json"
202
+ const dir = await state().then((x) => x.dir);
203
+ const target = path.join(dir, ...key) + '.json';
171
204
  return withErrorHandling(async () => {
172
- using _ = await Lock.read(target)
173
- const result = await Bun.file(target).json()
174
- return result as T
175
- })
205
+ using _ = await Lock.read(target);
206
+ const result = await Bun.file(target).json();
207
+ return result as T;
208
+ });
176
209
  }
177
210
 
178
211
  export async function update<T>(key: string[], fn: (draft: T) => void) {
179
- const dir = await state().then((x) => x.dir)
180
- const target = path.join(dir, ...key) + ".json"
212
+ const dir = await state().then((x) => x.dir);
213
+ const target = path.join(dir, ...key) + '.json';
181
214
  return withErrorHandling(async () => {
182
- using _ = await Lock.write(target)
183
- const content = await Bun.file(target).json()
184
- fn(content)
185
- await Bun.write(target, JSON.stringify(content, null, 2))
186
- return content as T
187
- })
215
+ using _ = await Lock.write(target);
216
+ const content = await Bun.file(target).json();
217
+ fn(content);
218
+ await Bun.write(target, JSON.stringify(content, null, 2));
219
+ return content as T;
220
+ });
188
221
  }
189
222
 
190
223
  export async function write<T>(key: string[], content: T) {
191
- const dir = await state().then((x) => x.dir)
192
- const target = path.join(dir, ...key) + ".json"
224
+ const dir = await state().then((x) => x.dir);
225
+ const target = path.join(dir, ...key) + '.json';
193
226
  return withErrorHandling(async () => {
194
- using _ = await Lock.write(target)
195
- await Bun.write(target, JSON.stringify(content, null, 2))
196
- })
227
+ using _ = await Lock.write(target);
228
+ await Bun.write(target, JSON.stringify(content, null, 2));
229
+ });
197
230
  }
198
231
 
199
232
  async function withErrorHandling<T>(body: () => Promise<T>) {
200
233
  return body().catch((e) => {
201
- if (!(e instanceof Error)) throw e
202
- const errnoException = e as NodeJS.ErrnoException
203
- if (errnoException.code === "ENOENT") {
204
- throw new NotFoundError({ message: `Resource not found: ${errnoException.path}` })
234
+ if (!(e instanceof Error)) throw e;
235
+ const errnoException = e as NodeJS.ErrnoException;
236
+ if (errnoException.code === 'ENOENT') {
237
+ throw new NotFoundError({
238
+ message: `Resource not found: ${errnoException.path}`,
239
+ });
205
240
  }
206
- throw e
207
- })
241
+ throw e;
242
+ });
208
243
  }
209
244
 
210
- const glob = new Bun.Glob("**/*")
245
+ const glob = new Bun.Glob('**/*');
211
246
  export async function list(prefix: string[]) {
212
- const dir = await state().then((x) => x.dir)
247
+ const dir = await state().then((x) => x.dir);
213
248
  try {
214
249
  const result = await Array.fromAsync(
215
250
  glob.scan({
216
251
  cwd: path.join(dir, ...prefix),
217
252
  onlyFiles: true,
218
- }),
219
- ).then((results) => results.map((x) => [...prefix, ...x.slice(0, -5).split(path.sep)]))
220
- result.sort()
221
- return result
253
+ })
254
+ ).then((results) =>
255
+ results.map((x) => [...prefix, ...x.slice(0, -5).split(path.sep)])
256
+ );
257
+ result.sort();
258
+ return result;
222
259
  } catch {
223
- return []
260
+ return [];
224
261
  }
225
262
  }
226
263
  }