@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,130 +1,147 @@
1
- import { Log } from "../util/log"
2
- import path from "path"
3
- import os from "os"
4
- import z from "zod"
5
- import { Filesystem } from "../util/filesystem"
6
- import { ModelsDev } from "../provider/models"
7
- import { mergeDeep, pipe } from "remeda"
8
- import { Global } from "../global"
9
- import fs from "fs/promises"
10
- import { lazy } from "../util/lazy"
11
- import { NamedError } from "../util/error"
12
- import { Flag } from "../flag/flag"
13
- import { Auth } from "../auth"
14
- import { type ParseError as JsoncParseError, parse as parseJsonc, printParseErrorCode } from "jsonc-parser"
15
- import { Instance } from "../project/instance"
16
- import { ConfigMarkdown } from "./markdown"
1
+ import { Log } from '../util/log';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import z from 'zod';
5
+ import { Filesystem } from '../util/filesystem';
6
+ import { ModelsDev } from '../provider/models';
7
+ import { mergeDeep, pipe } from 'remeda';
8
+ import { Global } from '../global';
9
+ import fs from 'fs/promises';
10
+ import { lazy } from '../util/lazy';
11
+ import { NamedError } from '../util/error';
12
+ import { Flag } from '../flag/flag';
13
+ import { Auth } from '../auth';
14
+ import {
15
+ type ParseError as JsoncParseError,
16
+ parse as parseJsonc,
17
+ printParseErrorCode,
18
+ } from 'jsonc-parser';
19
+ import { Instance } from '../project/instance';
20
+ import { ConfigMarkdown } from './markdown';
17
21
 
18
22
  export namespace Config {
19
- const log = Log.create({ service: "config" })
23
+ const log = Log.create({ service: 'config' });
20
24
 
21
25
  export const state = Instance.state(async () => {
22
- const auth = await Auth.all()
23
- let result = await global()
26
+ const auth = await Auth.all();
27
+ let result = await global();
24
28
 
25
29
  // Override with custom config if provided
26
30
  if (Flag.OPENCODE_CONFIG) {
27
- result = mergeDeep(result, await loadFile(Flag.OPENCODE_CONFIG))
28
- log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG })
31
+ result = mergeDeep(result, await loadFile(Flag.OPENCODE_CONFIG));
32
+ log.debug('loaded custom config', { path: Flag.OPENCODE_CONFIG });
29
33
  }
30
34
 
31
- for (const file of ["opencode.jsonc", "opencode.json"]) {
32
- const found = await Filesystem.findUp(file, Instance.directory, Instance.worktree)
35
+ for (const file of ['opencode.jsonc', 'opencode.json']) {
36
+ const found = await Filesystem.findUp(
37
+ file,
38
+ Instance.directory,
39
+ Instance.worktree
40
+ );
33
41
  for (const resolved of found.toReversed()) {
34
- result = mergeDeep(result, await loadFile(resolved))
42
+ result = mergeDeep(result, await loadFile(resolved));
35
43
  }
36
44
  }
37
45
 
38
46
  if (Flag.OPENCODE_CONFIG_CONTENT) {
39
- result = mergeDeep(result, JSON.parse(Flag.OPENCODE_CONFIG_CONTENT))
40
- log.debug("loaded custom config from OPENCODE_CONFIG_CONTENT")
47
+ result = mergeDeep(result, JSON.parse(Flag.OPENCODE_CONFIG_CONTENT));
48
+ log.debug('loaded custom config from OPENCODE_CONFIG_CONTENT');
41
49
  }
42
50
 
43
51
  for (const [key, value] of Object.entries(auth)) {
44
- if (value.type === "wellknown") {
45
- process.env[value.key] = value.token
46
- const wellknown = (await fetch(`${key}/.well-known/opencode`).then((x) => x.json())) as any
47
- result = mergeDeep(result, await load(JSON.stringify(wellknown.config ?? {}), process.cwd()))
52
+ if (value.type === 'wellknown') {
53
+ process.env[value.key] = value.token;
54
+ const wellknown = (await fetch(`${key}/.well-known/opencode`).then(
55
+ (x) => x.json()
56
+ )) as any;
57
+ result = mergeDeep(
58
+ result,
59
+ await load(JSON.stringify(wellknown.config ?? {}), process.cwd())
60
+ );
48
61
  }
49
62
  }
50
63
 
51
- result.agent = result.agent || {}
52
- result.mode = result.mode || {}
64
+ result.agent = result.agent || {};
65
+ result.mode = result.mode || {};
53
66
 
54
67
  const directories = [
55
68
  Global.Path.config,
56
69
  ...(await Array.fromAsync(
57
70
  Filesystem.up({
58
- targets: [".opencode"],
71
+ targets: ['.opencode'],
59
72
  start: Instance.directory,
60
73
  stop: Instance.worktree,
61
- }),
74
+ })
62
75
  )),
63
- ]
76
+ ];
64
77
 
65
78
  if (Flag.OPENCODE_CONFIG_DIR) {
66
- directories.push(Flag.OPENCODE_CONFIG_DIR)
67
- log.debug("loading config from OPENCODE_CONFIG_DIR", { path: Flag.OPENCODE_CONFIG_DIR })
79
+ directories.push(Flag.OPENCODE_CONFIG_DIR);
80
+ log.debug('loading config from OPENCODE_CONFIG_DIR', {
81
+ path: Flag.OPENCODE_CONFIG_DIR,
82
+ });
68
83
  }
69
84
 
70
- const promises: Promise<void>[] = []
85
+ const promises: Promise<void>[] = [];
71
86
  for (const dir of directories) {
72
- await assertValid(dir)
87
+ await assertValid(dir);
73
88
 
74
- if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) {
75
- for (const file of ["opencode.jsonc", "opencode.json"]) {
76
- log.debug(`loading config from ${path.join(dir, file)}`)
77
- result = mergeDeep(result, await loadFile(path.join(dir, file)))
89
+ if (dir.endsWith('.opencode') || dir === Flag.OPENCODE_CONFIG_DIR) {
90
+ for (const file of ['opencode.jsonc', 'opencode.json']) {
91
+ log.debug(`loading config from ${path.join(dir, file)}`);
92
+ result = mergeDeep(result, await loadFile(path.join(dir, file)));
78
93
  // to satisy the type checker
79
- result.agent ??= {}
80
- result.mode ??= {}
94
+ result.agent ??= {};
95
+ result.mode ??= {};
81
96
  }
82
97
  }
83
98
 
84
- promises.push(installDependencies(dir))
85
- result.command = mergeDeep(result.command ?? {}, await loadCommand(dir))
86
- result.agent = mergeDeep(result.agent, await loadAgent(dir))
87
- result.agent = mergeDeep(result.agent, await loadMode(dir))
99
+ promises.push(installDependencies(dir));
100
+ result.command = mergeDeep(result.command ?? {}, await loadCommand(dir));
101
+ result.agent = mergeDeep(result.agent, await loadAgent(dir));
102
+ result.agent = mergeDeep(result.agent, await loadMode(dir));
88
103
  }
89
- await Promise.allSettled(promises)
104
+ await Promise.allSettled(promises);
90
105
 
91
106
  // Migrate deprecated mode field to agent field
92
107
  for (const [name, mode] of Object.entries(result.mode)) {
93
108
  result.agent = mergeDeep(result.agent ?? {}, {
94
109
  [name]: {
95
110
  ...mode,
96
- mode: "primary" as const,
111
+ mode: 'primary' as const,
97
112
  },
98
- })
113
+ });
99
114
  }
100
115
 
101
116
  // Permission system removed
102
117
  // Share/autoshare removed - no sharing support
103
118
 
104
- if (!result.username) result.username = os.userInfo().username
119
+ if (!result.username) result.username = os.userInfo().username;
105
120
 
106
- if (!result.keybinds) result.keybinds = Info.shape.keybinds.parse({})
121
+ if (!result.keybinds) result.keybinds = Info.shape.keybinds.parse({});
107
122
 
108
123
  return {
109
124
  config: result,
110
125
  directories,
111
- }
112
- })
126
+ };
127
+ });
113
128
 
114
- const INVALID_DIRS = new Bun.Glob(`{${["agents", "commands", "tools"].join(",")}}/`)
129
+ const INVALID_DIRS = new Bun.Glob(
130
+ `{${['agents', 'commands', 'tools'].join(',')}}/`
131
+ );
115
132
  async function assertValid(dir: string) {
116
133
  const invalid = await Array.fromAsync(
117
134
  INVALID_DIRS.scan({
118
135
  onlyFiles: false,
119
136
  cwd: dir,
120
- }),
121
- )
137
+ })
138
+ );
122
139
  for (const item of invalid) {
123
140
  throw new ConfigDirectoryTypoError({
124
141
  path: dir,
125
142
  dir: item,
126
143
  suggestion: item.substring(0, item.length - 1),
127
- })
144
+ });
128
145
  }
129
146
  }
130
147
 
@@ -132,47 +149,47 @@ export namespace Config {
132
149
  // Dependency installation removed - no plugin support
133
150
  }
134
151
 
135
- const COMMAND_GLOB = new Bun.Glob("command/**/*.md")
152
+ const COMMAND_GLOB = new Bun.Glob('command/**/*.md');
136
153
  async function loadCommand(dir: string) {
137
- const result: Record<string, Command> = {}
154
+ const result: Record<string, Command> = {};
138
155
  for await (const item of COMMAND_GLOB.scan({
139
156
  absolute: true,
140
157
  followSymlinks: true,
141
158
  dot: true,
142
159
  cwd: dir,
143
160
  })) {
144
- const md = await ConfigMarkdown.parse(item)
145
- if (!md.data) continue
161
+ const md = await ConfigMarkdown.parse(item);
162
+ if (!md.data) continue;
146
163
 
147
164
  const name = (() => {
148
- const patterns = ["/.opencode/command/", "/command/"]
149
- const pattern = patterns.find((p) => item.includes(p))
165
+ const patterns = ['/.opencode/command/', '/command/'];
166
+ const pattern = patterns.find((p) => item.includes(p));
150
167
 
151
168
  if (pattern) {
152
- const index = item.indexOf(pattern)
153
- return item.slice(index + pattern.length, -3)
169
+ const index = item.indexOf(pattern);
170
+ return item.slice(index + pattern.length, -3);
154
171
  }
155
- return path.basename(item, ".md")
156
- })()
172
+ return path.basename(item, '.md');
173
+ })();
157
174
 
158
175
  const config = {
159
176
  name,
160
177
  ...md.data,
161
178
  template: md.content.trim(),
162
- }
163
- const parsed = Command.safeParse(config)
179
+ };
180
+ const parsed = Command.safeParse(config);
164
181
  if (parsed.success) {
165
- result[config.name] = parsed.data
166
- continue
182
+ result[config.name] = parsed.data;
183
+ continue;
167
184
  }
168
- throw new InvalidError({ path: item }, { cause: parsed.error })
185
+ throw new InvalidError({ path: item }, { cause: parsed.error });
169
186
  }
170
- return result
187
+ return result;
171
188
  }
172
189
 
173
- const AGENT_GLOB = new Bun.Glob("agent/**/*.md")
190
+ const AGENT_GLOB = new Bun.Glob('agent/**/*.md');
174
191
  async function loadAgent(dir: string) {
175
- const result: Record<string, Agent> = {}
192
+ const result: Record<string, Agent> = {};
176
193
 
177
194
  for await (const item of AGENT_GLOB.scan({
178
195
  absolute: true,
@@ -180,116 +197,131 @@ export namespace Config {
180
197
  dot: true,
181
198
  cwd: dir,
182
199
  })) {
183
- const md = await ConfigMarkdown.parse(item)
184
- if (!md.data) continue
200
+ const md = await ConfigMarkdown.parse(item);
201
+ if (!md.data) continue;
185
202
 
186
203
  // Extract relative path from agent folder for nested agents
187
- let agentName = path.basename(item, ".md")
188
- const agentFolderPath = item.includes("/.opencode/agent/")
189
- ? item.split("/.opencode/agent/")[1]
190
- : item.includes("/agent/")
191
- ? item.split("/agent/")[1]
192
- : agentName + ".md"
204
+ let agentName = path.basename(item, '.md');
205
+ const agentFolderPath = item.includes('/.opencode/agent/')
206
+ ? item.split('/.opencode/agent/')[1]
207
+ : item.includes('/agent/')
208
+ ? item.split('/agent/')[1]
209
+ : agentName + '.md';
193
210
 
194
211
  // If agent is in a subfolder, include folder path in name
195
- if (agentFolderPath.includes("/")) {
196
- const relativePath = agentFolderPath.replace(".md", "")
197
- const pathParts = relativePath.split("/")
198
- agentName = pathParts.slice(0, -1).join("/") + "/" + pathParts[pathParts.length - 1]
212
+ if (agentFolderPath.includes('/')) {
213
+ const relativePath = agentFolderPath.replace('.md', '');
214
+ const pathParts = relativePath.split('/');
215
+ agentName =
216
+ pathParts.slice(0, -1).join('/') +
217
+ '/' +
218
+ pathParts[pathParts.length - 1];
199
219
  }
200
220
 
201
221
  const config = {
202
222
  name: agentName,
203
223
  ...md.data,
204
224
  prompt: md.content.trim(),
205
- }
206
- const parsed = Agent.safeParse(config)
225
+ };
226
+ const parsed = Agent.safeParse(config);
207
227
  if (parsed.success) {
208
- result[config.name] = parsed.data
209
- continue
228
+ result[config.name] = parsed.data;
229
+ continue;
210
230
  }
211
- throw new InvalidError({ path: item }, { cause: parsed.error })
231
+ throw new InvalidError({ path: item }, { cause: parsed.error });
212
232
  }
213
- return result
233
+ return result;
214
234
  }
215
235
 
216
- const MODE_GLOB = new Bun.Glob("mode/*.md")
236
+ const MODE_GLOB = new Bun.Glob('mode/*.md');
217
237
  async function loadMode(dir: string) {
218
- const result: Record<string, Agent> = {}
238
+ const result: Record<string, Agent> = {};
219
239
  for await (const item of MODE_GLOB.scan({
220
240
  absolute: true,
221
241
  followSymlinks: true,
222
242
  dot: true,
223
243
  cwd: dir,
224
244
  })) {
225
- const md = await ConfigMarkdown.parse(item)
226
- if (!md.data) continue
245
+ const md = await ConfigMarkdown.parse(item);
246
+ if (!md.data) continue;
227
247
 
228
248
  const config = {
229
- name: path.basename(item, ".md"),
249
+ name: path.basename(item, '.md'),
230
250
  ...md.data,
231
251
  prompt: md.content.trim(),
232
- }
233
- const parsed = Agent.safeParse(config)
252
+ };
253
+ const parsed = Agent.safeParse(config);
234
254
  if (parsed.success) {
235
255
  result[config.name] = {
236
256
  ...parsed.data,
237
- mode: "primary" as const,
238
- }
239
- continue
257
+ mode: 'primary' as const,
258
+ };
259
+ continue;
240
260
  }
241
261
  }
242
- return result
262
+ return result;
243
263
  }
244
264
 
245
265
  export const McpLocal = z
246
266
  .object({
247
- type: z.literal("local").describe("Type of MCP server connection"),
248
- command: z.string().array().describe("Command and arguments to run the MCP server"),
267
+ type: z.literal('local').describe('Type of MCP server connection'),
268
+ command: z
269
+ .string()
270
+ .array()
271
+ .describe('Command and arguments to run the MCP server'),
249
272
  environment: z
250
273
  .record(z.string(), z.string())
251
274
  .optional()
252
- .describe("Environment variables to set when running the MCP server"),
253
- enabled: z.boolean().optional().describe("Enable or disable the MCP server on startup"),
275
+ .describe('Environment variables to set when running the MCP server'),
276
+ enabled: z
277
+ .boolean()
278
+ .optional()
279
+ .describe('Enable or disable the MCP server on startup'),
254
280
  timeout: z
255
281
  .number()
256
282
  .int()
257
283
  .positive()
258
284
  .optional()
259
285
  .describe(
260
- "Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.",
286
+ 'Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.'
261
287
  ),
262
288
  })
263
289
  .strict()
264
290
  .meta({
265
- ref: "McpLocalConfig",
266
- })
291
+ ref: 'McpLocalConfig',
292
+ });
267
293
 
268
294
  export const McpRemote = z
269
295
  .object({
270
- type: z.literal("remote").describe("Type of MCP server connection"),
271
- url: z.string().describe("URL of the remote MCP server"),
272
- enabled: z.boolean().optional().describe("Enable or disable the MCP server on startup"),
273
- headers: z.record(z.string(), z.string()).optional().describe("Headers to send with the request"),
296
+ type: z.literal('remote').describe('Type of MCP server connection'),
297
+ url: z.string().describe('URL of the remote MCP server'),
298
+ enabled: z
299
+ .boolean()
300
+ .optional()
301
+ .describe('Enable or disable the MCP server on startup'),
302
+ headers: z
303
+ .record(z.string(), z.string())
304
+ .optional()
305
+ .describe('Headers to send with the request'),
274
306
  timeout: z
275
307
  .number()
276
308
  .int()
277
309
  .positive()
278
310
  .optional()
279
311
  .describe(
280
- "Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.",
312
+ 'Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.'
281
313
  ),
282
314
  })
283
315
  .strict()
284
316
  .meta({
285
- ref: "McpRemoteConfig",
286
- })
317
+ ref: 'McpRemoteConfig',
318
+ });
287
319
 
288
- export const Mcp = z.discriminatedUnion("type", [McpLocal, McpRemote])
289
- export type Mcp = z.infer<typeof Mcp>
320
+ export const Mcp = z.discriminatedUnion('type', [McpLocal, McpRemote]);
321
+ export type Mcp = z.infer<typeof Mcp>;
290
322
 
291
- export const Permission = z.enum(["ask", "allow", "deny"])
292
- export type Permission = z.infer<typeof Permission>
323
+ export const Permission = z.enum(['ask', 'allow', 'deny']);
324
+ export type Permission = z.infer<typeof Permission>;
293
325
 
294
326
  export const Command = z.object({
295
327
  template: z.string(),
@@ -297,8 +329,8 @@ export namespace Config {
297
329
  agent: z.string().optional(),
298
330
  model: z.string().optional(),
299
331
  subtask: z.boolean().optional(),
300
- })
301
- export type Command = z.infer<typeof Command>
332
+ });
333
+ export type Command = z.infer<typeof Command>;
302
334
 
303
335
  export const Agent = z
304
336
  .object({
@@ -308,17 +340,22 @@ export namespace Config {
308
340
  prompt: z.string().optional(),
309
341
  tools: z.record(z.string(), z.boolean()).optional(),
310
342
  disable: z.boolean().optional(),
311
- description: z.string().optional().describe("Description of when to use the agent"),
312
- mode: z.enum(["subagent", "primary", "all"]).optional(),
343
+ description: z
344
+ .string()
345
+ .optional()
346
+ .describe('Description of when to use the agent'),
347
+ mode: z.enum(['subagent', 'primary', 'all']).optional(),
313
348
  color: z
314
349
  .string()
315
- .regex(/^#[0-9a-fA-F]{6}$/, "Invalid hex color format")
350
+ .regex(/^#[0-9a-fA-F]{6}$/, 'Invalid hex color format')
316
351
  .optional()
317
- .describe("Hex color code for the agent (e.g., #FF5733)"),
352
+ .describe('Hex color code for the agent (e.g., #FF5733)'),
318
353
  permission: z
319
354
  .object({
320
355
  edit: Permission.optional(),
321
- bash: z.union([Permission, z.record(z.string(), Permission)]).optional(),
356
+ bash: z
357
+ .union([Permission, z.record(z.string(), Permission)])
358
+ .optional(),
322
359
  webfetch: Permission.optional(),
323
360
  doom_loop: Permission.optional(),
324
361
  external_directory: Permission.optional(),
@@ -327,89 +364,242 @@ export namespace Config {
327
364
  })
328
365
  .catchall(z.any())
329
366
  .meta({
330
- ref: "AgentConfig",
331
- })
332
- export type Agent = z.infer<typeof Agent>
367
+ ref: 'AgentConfig',
368
+ });
369
+ export type Agent = z.infer<typeof Agent>;
333
370
 
334
371
  export const Keybinds = z
335
372
  .object({
336
- leader: z.string().optional().default("ctrl+x").describe("Leader key for keybind combinations"),
337
- app_exit: z.string().optional().default("ctrl+c,ctrl+d,<leader>q").describe("Exit the application"),
338
- editor_open: z.string().optional().default("<leader>e").describe("Open external editor"),
339
- theme_list: z.string().optional().default("<leader>t").describe("List available themes"),
340
- sidebar_toggle: z.string().optional().default("<leader>b").describe("Toggle sidebar"),
341
- status_view: z.string().optional().default("<leader>s").describe("View status"),
342
- session_export: z.string().optional().default("<leader>x").describe("Export session to editor"),
343
- session_new: z.string().optional().default("<leader>n").describe("Create a new session"),
344
- session_list: z.string().optional().default("<leader>l").describe("List all sessions"),
345
- session_timeline: z.string().optional().default("<leader>g").describe("Show session timeline"),
346
- session_interrupt: z.string().optional().default("escape").describe("Interrupt current session"),
347
- session_compact: z.string().optional().default("<leader>c").describe("Compact the session"),
348
- messages_page_up: z.string().optional().default("pageup").describe("Scroll messages up by one page"),
349
- messages_page_down: z.string().optional().default("pagedown").describe("Scroll messages down by one page"),
350
- messages_half_page_up: z.string().optional().default("ctrl+alt+u").describe("Scroll messages up by half page"),
373
+ leader: z
374
+ .string()
375
+ .optional()
376
+ .default('ctrl+x')
377
+ .describe('Leader key for keybind combinations'),
378
+ app_exit: z
379
+ .string()
380
+ .optional()
381
+ .default('ctrl+c,ctrl+d,<leader>q')
382
+ .describe('Exit the application'),
383
+ editor_open: z
384
+ .string()
385
+ .optional()
386
+ .default('<leader>e')
387
+ .describe('Open external editor'),
388
+ theme_list: z
389
+ .string()
390
+ .optional()
391
+ .default('<leader>t')
392
+ .describe('List available themes'),
393
+ sidebar_toggle: z
394
+ .string()
395
+ .optional()
396
+ .default('<leader>b')
397
+ .describe('Toggle sidebar'),
398
+ status_view: z
399
+ .string()
400
+ .optional()
401
+ .default('<leader>s')
402
+ .describe('View status'),
403
+ session_export: z
404
+ .string()
405
+ .optional()
406
+ .default('<leader>x')
407
+ .describe('Export session to editor'),
408
+ session_new: z
409
+ .string()
410
+ .optional()
411
+ .default('<leader>n')
412
+ .describe('Create a new session'),
413
+ session_list: z
414
+ .string()
415
+ .optional()
416
+ .default('<leader>l')
417
+ .describe('List all sessions'),
418
+ session_timeline: z
419
+ .string()
420
+ .optional()
421
+ .default('<leader>g')
422
+ .describe('Show session timeline'),
423
+ session_interrupt: z
424
+ .string()
425
+ .optional()
426
+ .default('escape')
427
+ .describe('Interrupt current session'),
428
+ session_compact: z
429
+ .string()
430
+ .optional()
431
+ .default('<leader>c')
432
+ .describe('Compact the session'),
433
+ messages_page_up: z
434
+ .string()
435
+ .optional()
436
+ .default('pageup')
437
+ .describe('Scroll messages up by one page'),
438
+ messages_page_down: z
439
+ .string()
440
+ .optional()
441
+ .default('pagedown')
442
+ .describe('Scroll messages down by one page'),
443
+ messages_half_page_up: z
444
+ .string()
445
+ .optional()
446
+ .default('ctrl+alt+u')
447
+ .describe('Scroll messages up by half page'),
351
448
  messages_half_page_down: z
352
449
  .string()
353
450
  .optional()
354
- .default("ctrl+alt+d")
355
- .describe("Scroll messages down by half page"),
356
- messages_first: z.string().optional().default("ctrl+g,home").describe("Navigate to first message"),
357
- messages_last: z.string().optional().default("ctrl+alt+g,end").describe("Navigate to last message"),
358
- messages_copy: z.string().optional().default("<leader>y").describe("Copy message"),
359
- messages_undo: z.string().optional().default("<leader>u").describe("Undo message"),
360
- messages_redo: z.string().optional().default("<leader>r").describe("Redo message"),
451
+ .default('ctrl+alt+d')
452
+ .describe('Scroll messages down by half page'),
453
+ messages_first: z
454
+ .string()
455
+ .optional()
456
+ .default('ctrl+g,home')
457
+ .describe('Navigate to first message'),
458
+ messages_last: z
459
+ .string()
460
+ .optional()
461
+ .default('ctrl+alt+g,end')
462
+ .describe('Navigate to last message'),
463
+ messages_copy: z
464
+ .string()
465
+ .optional()
466
+ .default('<leader>y')
467
+ .describe('Copy message'),
468
+ messages_undo: z
469
+ .string()
470
+ .optional()
471
+ .default('<leader>u')
472
+ .describe('Undo message'),
473
+ messages_redo: z
474
+ .string()
475
+ .optional()
476
+ .default('<leader>r')
477
+ .describe('Redo message'),
361
478
  messages_toggle_conceal: z
362
479
  .string()
363
480
  .optional()
364
- .default("<leader>h")
365
- .describe("Toggle code block concealment in messages"),
366
- model_list: z.string().optional().default("<leader>m").describe("List available models"),
367
- model_cycle_recent: z.string().optional().default("f2").describe("Next recently used model"),
368
- model_cycle_recent_reverse: z.string().optional().default("shift+f2").describe("Previous recently used model"),
369
- command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
370
- agent_list: z.string().optional().default("<leader>a").describe("List agents"),
371
- agent_cycle: z.string().optional().default("tab").describe("Next agent"),
372
- agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
373
- input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
374
- input_forward_delete: z.string().optional().default("ctrl+d").describe("Forward delete"),
375
- input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
376
- input_submit: z.string().optional().default("return").describe("Submit input"),
377
- input_newline: z.string().optional().default("shift+return,ctrl+j").describe("Insert newline in input"),
378
- history_previous: z.string().optional().default("up").describe("Previous history item"),
379
- history_next: z.string().optional().default("down").describe("Next history item"),
380
- session_child_cycle: z.string().optional().default("ctrl+right").describe("Next child session"),
381
- session_child_cycle_reverse: z.string().optional().default("ctrl+left").describe("Previous child session"),
481
+ .default('<leader>h')
482
+ .describe('Toggle code block concealment in messages'),
483
+ model_list: z
484
+ .string()
485
+ .optional()
486
+ .default('<leader>m')
487
+ .describe('List available models'),
488
+ model_cycle_recent: z
489
+ .string()
490
+ .optional()
491
+ .default('f2')
492
+ .describe('Next recently used model'),
493
+ model_cycle_recent_reverse: z
494
+ .string()
495
+ .optional()
496
+ .default('shift+f2')
497
+ .describe('Previous recently used model'),
498
+ command_list: z
499
+ .string()
500
+ .optional()
501
+ .default('ctrl+p')
502
+ .describe('List available commands'),
503
+ agent_list: z
504
+ .string()
505
+ .optional()
506
+ .default('<leader>a')
507
+ .describe('List agents'),
508
+ agent_cycle: z.string().optional().default('tab').describe('Next agent'),
509
+ agent_cycle_reverse: z
510
+ .string()
511
+ .optional()
512
+ .default('shift+tab')
513
+ .describe('Previous agent'),
514
+ input_clear: z
515
+ .string()
516
+ .optional()
517
+ .default('ctrl+c')
518
+ .describe('Clear input field'),
519
+ input_forward_delete: z
520
+ .string()
521
+ .optional()
522
+ .default('ctrl+d')
523
+ .describe('Forward delete'),
524
+ input_paste: z
525
+ .string()
526
+ .optional()
527
+ .default('ctrl+v')
528
+ .describe('Paste from clipboard'),
529
+ input_submit: z
530
+ .string()
531
+ .optional()
532
+ .default('return')
533
+ .describe('Submit input'),
534
+ input_newline: z
535
+ .string()
536
+ .optional()
537
+ .default('shift+return,ctrl+j')
538
+ .describe('Insert newline in input'),
539
+ history_previous: z
540
+ .string()
541
+ .optional()
542
+ .default('up')
543
+ .describe('Previous history item'),
544
+ history_next: z
545
+ .string()
546
+ .optional()
547
+ .default('down')
548
+ .describe('Next history item'),
549
+ session_child_cycle: z
550
+ .string()
551
+ .optional()
552
+ .default('ctrl+right')
553
+ .describe('Next child session'),
554
+ session_child_cycle_reverse: z
555
+ .string()
556
+ .optional()
557
+ .default('ctrl+left')
558
+ .describe('Previous child session'),
382
559
  })
383
560
  .strict()
384
561
  .meta({
385
- ref: "KeybindsConfig",
386
- })
562
+ ref: 'KeybindsConfig',
563
+ });
387
564
 
388
565
  export const TUI = z.object({
389
- scroll_speed: z.number().min(0.001).optional().default(1).describe("TUI scroll speed"),
566
+ scroll_speed: z
567
+ .number()
568
+ .min(0.001)
569
+ .optional()
570
+ .default(1)
571
+ .describe('TUI scroll speed'),
390
572
  scroll_acceleration: z
391
573
  .object({
392
- enabled: z.boolean().describe("Enable scroll acceleration"),
574
+ enabled: z.boolean().describe('Enable scroll acceleration'),
393
575
  })
394
576
  .optional()
395
- .describe("Scroll acceleration settings"),
396
- })
577
+ .describe('Scroll acceleration settings'),
578
+ });
397
579
 
398
- export const Layout = z.enum(["auto", "stretch"]).meta({
399
- ref: "LayoutConfig",
400
- })
401
- export type Layout = z.infer<typeof Layout>
580
+ export const Layout = z.enum(['auto', 'stretch']).meta({
581
+ ref: 'LayoutConfig',
582
+ });
583
+ export type Layout = z.infer<typeof Layout>;
402
584
 
403
585
  export const Info = z
404
586
  .object({
405
- $schema: z.string().optional().describe("JSON schema reference for configuration validation"),
406
- theme: z.string().optional().describe("Theme name to use for the interface"),
407
- keybinds: Keybinds.optional().describe("Custom keybind configurations"),
408
- tui: TUI.optional().describe("TUI specific settings"),
587
+ $schema: z
588
+ .string()
589
+ .optional()
590
+ .describe('JSON schema reference for configuration validation'),
591
+ theme: z
592
+ .string()
593
+ .optional()
594
+ .describe('Theme name to use for the interface'),
595
+ keybinds: Keybinds.optional().describe('Custom keybind configurations'),
596
+ tui: TUI.optional().describe('TUI specific settings'),
409
597
  command: z
410
598
  .record(z.string(), Command)
411
599
  .optional()
412
- .describe("Command configuration, see https://opencode.ai/docs/commands"),
600
+ .describe(
601
+ 'Command configuration, see https://opencode.ai/docs/commands'
602
+ ),
413
603
  watcher: z
414
604
  .object({
415
605
  ignore: z.array(z.string()).optional(),
@@ -417,17 +607,32 @@ export namespace Config {
417
607
  .optional(),
418
608
  snapshot: z.boolean().optional(),
419
609
  // share and autoshare fields removed - no sharing support
420
- autoupdate: z.boolean().optional().describe("Automatically update to the latest version"),
421
- disabled_providers: z.array(z.string()).optional().describe("Disable providers that are loaded automatically"),
422
- model: z.string().describe("Model to use in the format of provider/model, eg anthropic/claude-2").optional(),
610
+ autoupdate: z
611
+ .boolean()
612
+ .optional()
613
+ .describe('Automatically update to the latest version'),
614
+ disabled_providers: z
615
+ .array(z.string())
616
+ .optional()
617
+ .describe('Disable providers that are loaded automatically'),
618
+ model: z
619
+ .string()
620
+ .describe(
621
+ 'Model to use in the format of provider/model, eg anthropic/claude-2'
622
+ )
623
+ .optional(),
423
624
  small_model: z
424
625
  .string()
425
- .describe("Small model to use for tasks like title generation in the format of provider/model")
626
+ .describe(
627
+ 'Small model to use for tasks like title generation in the format of provider/model'
628
+ )
426
629
  .optional(),
427
630
  username: z
428
631
  .string()
429
632
  .optional()
430
- .describe("Custom username to display in conversations instead of system username"),
633
+ .describe(
634
+ 'Custom username to display in conversations instead of system username'
635
+ ),
431
636
  mode: z
432
637
  .object({
433
638
  build: Agent.optional(),
@@ -435,7 +640,7 @@ export namespace Config {
435
640
  })
436
641
  .catchall(Agent)
437
642
  .optional()
438
- .describe("@deprecated Use `agent` field instead."),
643
+ .describe('@deprecated Use `agent` field instead.'),
439
644
  agent: z
440
645
  .object({
441
646
  plan: Agent.optional(),
@@ -444,18 +649,25 @@ export namespace Config {
444
649
  })
445
650
  .catchall(Agent)
446
651
  .optional()
447
- .describe("Agent configuration, see https://opencode.ai/docs/agent"),
652
+ .describe('Agent configuration, see https://opencode.ai/docs/agent'),
448
653
  provider: z
449
654
  .record(
450
655
  z.string(),
451
656
  ModelsDev.Provider.partial()
452
657
  .extend({
453
- models: z.record(z.string(), ModelsDev.Model.partial()).optional(),
658
+ models: z
659
+ .record(z.string(), ModelsDev.Model.partial())
660
+ .optional(),
454
661
  options: z
455
662
  .object({
456
663
  apiKey: z.string().optional(),
457
664
  baseURL: z.string().optional(),
458
- enterpriseUrl: z.string().optional().describe("GitHub Enterprise URL for copilot authentication"),
665
+ enterpriseUrl: z
666
+ .string()
667
+ .optional()
668
+ .describe(
669
+ 'GitHub Enterprise URL for copilot authentication'
670
+ ),
459
671
  timeout: z
460
672
  .union([
461
673
  z
@@ -463,23 +675,30 @@ export namespace Config {
463
675
  .int()
464
676
  .positive()
465
677
  .describe(
466
- "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
678
+ 'Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.'
679
+ ),
680
+ z
681
+ .literal(false)
682
+ .describe(
683
+ 'Disable timeout for this provider entirely.'
467
684
  ),
468
- z.literal(false).describe("Disable timeout for this provider entirely."),
469
685
  ])
470
686
  .optional()
471
687
  .describe(
472
- "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
688
+ 'Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.'
473
689
  ),
474
690
  })
475
691
  .catchall(z.any())
476
692
  .optional(),
477
693
  })
478
- .strict(),
694
+ .strict()
479
695
  )
480
696
  .optional()
481
- .describe("Custom provider configurations and model overrides"),
482
- mcp: z.record(z.string(), Mcp).optional().describe("MCP (Model Context Protocol) server configurations"),
697
+ .describe('Custom provider configurations and model overrides'),
698
+ mcp: z
699
+ .record(z.string(), Mcp)
700
+ .optional()
701
+ .describe('MCP (Model Context Protocol) server configurations'),
483
702
  formatter: z
484
703
  .union([
485
704
  z.literal(false),
@@ -490,16 +709,23 @@ export namespace Config {
490
709
  command: z.array(z.string()).optional(),
491
710
  environment: z.record(z.string(), z.string()).optional(),
492
711
  extensions: z.array(z.string()).optional(),
493
- }),
712
+ })
494
713
  ),
495
714
  ])
496
715
  .optional(),
497
- instructions: z.array(z.string()).optional().describe("Additional instruction files or patterns to include"),
498
- layout: Layout.optional().describe("@deprecated Always uses stretch layout."),
716
+ instructions: z
717
+ .array(z.string())
718
+ .optional()
719
+ .describe('Additional instruction files or patterns to include'),
720
+ layout: Layout.optional().describe(
721
+ '@deprecated Always uses stretch layout.'
722
+ ),
499
723
  permission: z
500
724
  .object({
501
725
  edit: Permission.optional(),
502
- bash: z.union([Permission, z.record(z.string(), Permission)]).optional(),
726
+ bash: z
727
+ .union([Permission, z.record(z.string(), Permission)])
728
+ .optional(),
503
729
  webfetch: Permission.optional(),
504
730
  doom_loop: Permission.optional(),
505
731
  external_directory: Permission.optional(),
@@ -518,7 +744,7 @@ export namespace Config {
518
744
  command: z.string().array(),
519
745
  environment: z.record(z.string(), z.string()).optional(),
520
746
  })
521
- .array(),
747
+ .array()
522
748
  )
523
749
  .optional(),
524
750
  session_completed: z
@@ -530,176 +756,190 @@ export namespace Config {
530
756
  .optional(),
531
757
  })
532
758
  .optional(),
533
- chatMaxRetries: z.number().optional().describe("Number of retries for chat completions on failure"),
759
+ chatMaxRetries: z
760
+ .number()
761
+ .optional()
762
+ .describe('Number of retries for chat completions on failure'),
534
763
  disable_paste_summary: z.boolean().optional(),
535
- batch_tool: z.boolean().optional().describe("Enable the batch tool"),
764
+ batch_tool: z.boolean().optional().describe('Enable the batch tool'),
536
765
  })
537
766
  .optional(),
538
767
  })
539
768
  .strict()
540
769
  .meta({
541
- ref: "Config",
542
- })
770
+ ref: 'Config',
771
+ });
543
772
 
544
- export type Info = z.output<typeof Info>
773
+ export type Info = z.output<typeof Info>;
545
774
 
546
775
  export const global = lazy(async () => {
547
776
  let result: Info = pipe(
548
777
  {},
549
- mergeDeep(await loadFile(path.join(Global.Path.config, "config.json"))),
550
- mergeDeep(await loadFile(path.join(Global.Path.config, "opencode.json"))),
551
- mergeDeep(await loadFile(path.join(Global.Path.config, "opencode.jsonc"))),
552
- )
778
+ mergeDeep(await loadFile(path.join(Global.Path.config, 'config.json'))),
779
+ mergeDeep(await loadFile(path.join(Global.Path.config, 'opencode.json'))),
780
+ mergeDeep(await loadFile(path.join(Global.Path.config, 'opencode.jsonc')))
781
+ );
553
782
 
554
- await import(path.join(Global.Path.config, "config"), {
783
+ await import(path.join(Global.Path.config, 'config'), {
555
784
  with: {
556
- type: "toml",
785
+ type: 'toml',
557
786
  },
558
787
  })
559
788
  .then(async (mod) => {
560
- const { provider, model, ...rest } = mod.default
561
- if (provider && model) result.model = `${provider}/${model}`
562
- result["$schema"] = "https://opencode.ai/config.json"
563
- result = mergeDeep(result, rest)
564
- await Bun.write(path.join(Global.Path.config, "config.json"), JSON.stringify(result, null, 2))
565
- await fs.unlink(path.join(Global.Path.config, "config"))
789
+ const { provider, model, ...rest } = mod.default;
790
+ if (provider && model) result.model = `${provider}/${model}`;
791
+ result['$schema'] = 'https://opencode.ai/config.json';
792
+ result = mergeDeep(result, rest);
793
+ await Bun.write(
794
+ path.join(Global.Path.config, 'config.json'),
795
+ JSON.stringify(result, null, 2)
796
+ );
797
+ await fs.unlink(path.join(Global.Path.config, 'config'));
566
798
  })
567
- .catch(() => {})
799
+ .catch(() => {});
568
800
 
569
- return result
570
- })
801
+ return result;
802
+ });
571
803
 
572
804
  async function loadFile(filepath: string): Promise<Info> {
573
- log.info("loading", { path: filepath })
805
+ log.info('loading', { path: filepath });
574
806
  let text = await Bun.file(filepath)
575
807
  .text()
576
808
  .catch((err) => {
577
- if (err.code === "ENOENT") return
578
- throw new JsonError({ path: filepath }, { cause: err })
579
- })
580
- if (!text) return {}
581
- return load(text, filepath)
809
+ if (err.code === 'ENOENT') return;
810
+ throw new JsonError({ path: filepath }, { cause: err });
811
+ });
812
+ if (!text) return {};
813
+ return load(text, filepath);
582
814
  }
583
815
 
584
816
  async function load(text: string, configFilepath: string) {
585
817
  text = text.replace(/\{env:([^}]+)\}/g, (_, varName) => {
586
- return process.env[varName] || ""
587
- })
818
+ return process.env[varName] || '';
819
+ });
588
820
 
589
- const fileMatches = text.match(/\{file:[^}]+\}/g)
821
+ const fileMatches = text.match(/\{file:[^}]+\}/g);
590
822
  if (fileMatches) {
591
- const configDir = path.dirname(configFilepath)
592
- const lines = text.split("\n")
823
+ const configDir = path.dirname(configFilepath);
824
+ const lines = text.split('\n');
593
825
 
594
826
  for (const match of fileMatches) {
595
- const lineIndex = lines.findIndex((line) => line.includes(match))
596
- if (lineIndex !== -1 && lines[lineIndex].trim().startsWith("//")) {
597
- continue // Skip if line is commented
827
+ const lineIndex = lines.findIndex((line) => line.includes(match));
828
+ if (lineIndex !== -1 && lines[lineIndex].trim().startsWith('//')) {
829
+ continue; // Skip if line is commented
598
830
  }
599
- let filePath = match.replace(/^\{file:/, "").replace(/\}$/, "")
600
- if (filePath.startsWith("~/")) {
601
- filePath = path.join(os.homedir(), filePath.slice(2))
831
+ let filePath = match.replace(/^\{file:/, '').replace(/\}$/, '');
832
+ if (filePath.startsWith('~/')) {
833
+ filePath = path.join(os.homedir(), filePath.slice(2));
602
834
  }
603
- const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(configDir, filePath)
835
+ const resolvedPath = path.isAbsolute(filePath)
836
+ ? filePath
837
+ : path.resolve(configDir, filePath);
604
838
  const fileContent = (
605
839
  await Bun.file(resolvedPath)
606
840
  .text()
607
841
  .catch((error) => {
608
- const errMsg = `bad file reference: "${match}"`
609
- if (error.code === "ENOENT") {
842
+ const errMsg = `bad file reference: "${match}"`;
843
+ if (error.code === 'ENOENT') {
610
844
  throw new InvalidError(
611
845
  {
612
846
  path: configFilepath,
613
847
  message: errMsg + ` ${resolvedPath} does not exist`,
614
848
  },
615
- { cause: error },
616
- )
849
+ { cause: error }
850
+ );
617
851
  }
618
- throw new InvalidError({ path: configFilepath, message: errMsg }, { cause: error })
852
+ throw new InvalidError(
853
+ { path: configFilepath, message: errMsg },
854
+ { cause: error }
855
+ );
619
856
  })
620
- ).trim()
857
+ ).trim();
621
858
  // escape newlines/quotes, strip outer quotes
622
- text = text.replace(match, JSON.stringify(fileContent).slice(1, -1))
859
+ text = text.replace(match, JSON.stringify(fileContent).slice(1, -1));
623
860
  }
624
861
  }
625
862
 
626
- const errors: JsoncParseError[] = []
627
- const data = parseJsonc(text, errors, { allowTrailingComma: true })
863
+ const errors: JsoncParseError[] = [];
864
+ const data = parseJsonc(text, errors, { allowTrailingComma: true });
628
865
  if (errors.length) {
629
- const lines = text.split("\n")
866
+ const lines = text.split('\n');
630
867
  const errorDetails = errors
631
868
  .map((e) => {
632
- const beforeOffset = text.substring(0, e.offset).split("\n")
633
- const line = beforeOffset.length
634
- const column = beforeOffset[beforeOffset.length - 1].length + 1
635
- const problemLine = lines[line - 1]
869
+ const beforeOffset = text.substring(0, e.offset).split('\n');
870
+ const line = beforeOffset.length;
871
+ const column = beforeOffset[beforeOffset.length - 1].length + 1;
872
+ const problemLine = lines[line - 1];
636
873
 
637
- const error = `${printParseErrorCode(e.error)} at line ${line}, column ${column}`
638
- if (!problemLine) return error
874
+ const error = `${printParseErrorCode(e.error)} at line ${line}, column ${column}`;
875
+ if (!problemLine) return error;
639
876
 
640
- return `${error}\n Line ${line}: ${problemLine}\n${"".padStart(column + 9)}^`
877
+ return `${error}\n Line ${line}: ${problemLine}\n${''.padStart(column + 9)}^`;
641
878
  })
642
- .join("\n")
879
+ .join('\n');
643
880
 
644
881
  throw new JsonError({
645
882
  path: configFilepath,
646
883
  message: `\n--- JSONC Input ---\n${text}\n--- Errors ---\n${errorDetails}\n--- End ---`,
647
- })
884
+ });
648
885
  }
649
886
 
650
- const parsed = Info.safeParse(data)
887
+ const parsed = Info.safeParse(data);
651
888
  if (parsed.success) {
652
889
  if (!parsed.data.$schema) {
653
- parsed.data.$schema = "https://opencode.ai/config.json"
654
- await Bun.write(configFilepath, JSON.stringify(parsed.data, null, 2))
890
+ parsed.data.$schema = 'https://opencode.ai/config.json';
891
+ await Bun.write(configFilepath, JSON.stringify(parsed.data, null, 2));
655
892
  }
656
- const data = parsed.data
657
- return data
893
+ const data = parsed.data;
894
+ return data;
658
895
  }
659
896
 
660
897
  throw new InvalidError({
661
898
  path: configFilepath,
662
899
  issues: parsed.error.issues,
663
- })
900
+ });
664
901
  }
665
902
  export const JsonError = NamedError.create(
666
- "ConfigJsonError",
903
+ 'ConfigJsonError',
667
904
  z.object({
668
905
  path: z.string(),
669
906
  message: z.string().optional(),
670
- }),
671
- )
907
+ })
908
+ );
672
909
 
673
910
  export const ConfigDirectoryTypoError = NamedError.create(
674
- "ConfigDirectoryTypoError",
911
+ 'ConfigDirectoryTypoError',
675
912
  z.object({
676
913
  path: z.string(),
677
914
  dir: z.string(),
678
915
  suggestion: z.string(),
679
- }),
680
- )
916
+ })
917
+ );
681
918
 
682
919
  export const InvalidError = NamedError.create(
683
- "ConfigInvalidError",
920
+ 'ConfigInvalidError',
684
921
  z.object({
685
922
  path: z.string(),
686
923
  issues: z.custom<z.core.$ZodIssue[]>().optional(),
687
924
  message: z.string().optional(),
688
- }),
689
- )
925
+ })
926
+ );
690
927
 
691
928
  export async function get() {
692
- return state().then((x) => x.config)
929
+ return state().then((x) => x.config);
693
930
  }
694
931
 
695
932
  export async function update(config: Info) {
696
- const filepath = path.join(Instance.directory, "config.json")
697
- const existing = await loadFile(filepath)
698
- await Bun.write(filepath, JSON.stringify(mergeDeep(existing, config), null, 2))
699
- await Instance.dispose()
933
+ const filepath = path.join(Instance.directory, 'config.json');
934
+ const existing = await loadFile(filepath);
935
+ await Bun.write(
936
+ filepath,
937
+ JSON.stringify(mergeDeep(existing, config), null, 2)
938
+ );
939
+ await Instance.dispose();
700
940
  }
701
941
 
702
942
  export async function directories() {
703
- return state().then((x) => x.directories)
943
+ return state().then((x) => x.directories);
704
944
  }
705
945
  }