@link-assistant/agent 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/agent",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "A minimal, public domain AI CLI agent compatible with OpenCode's JSON interface. Bun-only runtime.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -68,6 +68,8 @@
68
68
  "@opentui/solid": "^0.1.46",
69
69
  "@parcel/watcher": "^2.5.1",
70
70
  "@solid-primitives/event-bus": "^1.1.2",
71
+ "@standard-community/standard-json": "^0.3.5",
72
+ "@standard-community/standard-openapi": "^0.2.9",
71
73
  "@zip.js/zip.js": "^2.8.10",
72
74
  "ai": "6.0.0-beta.99",
73
75
  "chokidar": "^4.0.3",
@@ -82,9 +84,9 @@
82
84
  "hono-openapi": "^1.1.1",
83
85
  "ignore": "^7.0.5",
84
86
  "jsonc-parser": "^3.3.1",
87
+ "lino-objects-codec": "^0.1.1",
88
+ "log-lazy": "^1.0.4",
85
89
  "minimatch": "^10.1.1",
86
- "open": "^11.0.0",
87
- "partial-json": "^0.1.7",
88
90
  "remeda": "^2.32.0",
89
91
  "solid-js": "^1.9.10",
90
92
  "strip-ansi": "^7.1.2",
@@ -147,9 +147,10 @@ export namespace ClaudeOAuth {
147
147
  */
148
148
  export async function saveState(state: OAuthState): Promise<void> {
149
149
  await Bun.write(statePath, JSON.stringify(state, null, 2));
150
- log.info('saved oauth state', {
150
+ log.info(() => ({
151
+ message: 'saved oauth state',
151
152
  expiresAt: new Date(state.expiresAt).toISOString(),
152
- });
153
+ }));
153
154
  }
154
155
 
155
156
  /**
@@ -165,14 +166,14 @@ export namespace ClaudeOAuth {
165
166
  const parsed = OAuthState.parse(content);
166
167
 
167
168
  if (parsed.expiresAt < Date.now()) {
168
- log.warn('oauth state expired');
169
+ log.warn(() => ({ message: 'oauth state expired' }));
169
170
  await clearState();
170
171
  return undefined;
171
172
  }
172
173
 
173
174
  return parsed;
174
175
  } catch (error) {
175
- log.error('failed to load oauth state', { error });
176
+ log.error(() => ({ message: 'failed to load oauth state', error }));
176
177
  return undefined;
177
178
  }
178
179
  }
@@ -190,7 +191,7 @@ export namespace ClaudeOAuth {
190
191
  await fs.unlink(statePath).catch(() => {});
191
192
  }
192
193
  } catch (error) {
193
- log.error('failed to clear oauth state', { error });
194
+ log.error(() => ({ message: 'failed to clear oauth state', error }));
194
195
  }
195
196
  }
196
197
 
@@ -213,7 +214,9 @@ export namespace ClaudeOAuth {
213
214
  code_verifier: codeVerifier,
214
215
  });
215
216
 
216
- log.info('exchanging authorization code for tokens');
217
+ log.info(() => ({
218
+ message: 'exchanging authorization code for tokens',
219
+ }));
217
220
 
218
221
  const response = await fetch(Config.tokenUrl, {
219
222
  method: 'POST',
@@ -225,7 +228,11 @@ export namespace ClaudeOAuth {
225
228
 
226
229
  if (!response.ok) {
227
230
  const error = await response.text();
228
- log.error('token exchange failed', { status: response.status, error });
231
+ log.error(() => ({
232
+ message: 'token exchange failed',
233
+ status: response.status,
234
+ error,
235
+ }));
229
236
  throw new Error(`Token exchange failed: ${response.status} ${error}`);
230
237
  }
231
238
 
@@ -267,9 +274,10 @@ export namespace ClaudeOAuth {
267
274
  };
268
275
 
269
276
  await Bun.write(credentialsPath, JSON.stringify(credentials, null, 2));
270
- log.info('saved credentials', {
277
+ log.info(() => ({
278
+ message: 'saved credentials',
271
279
  expiresAt: new Date(credentials.claudeAiOauth!.expiresAt).toISOString(),
272
- });
280
+ }));
273
281
  }
274
282
 
275
283
  /**
@@ -283,7 +291,10 @@ export namespace ClaudeOAuth {
283
291
  try {
284
292
  const file = Bun.file(credentialsPath);
285
293
  if (!(await file.exists())) {
286
- log.info('credentials file not found', { path: credentialsPath });
294
+ log.info(() => ({
295
+ message: 'credentials file not found',
296
+ path: credentialsPath,
297
+ }));
287
298
  return undefined;
288
299
  }
289
300
 
@@ -291,27 +302,31 @@ export namespace ClaudeOAuth {
291
302
  const parsed = Credentials.parse(content);
292
303
 
293
304
  if (!parsed.claudeAiOauth) {
294
- log.info('no claudeAiOauth credentials found');
305
+ log.info(() => ({
306
+ message: 'no claudeAiOauth credentials found',
307
+ }));
295
308
  return undefined;
296
309
  }
297
310
 
298
311
  // Check if token is expired
299
312
  if (parsed.claudeAiOauth.expiresAt < Date.now()) {
300
- log.warn('token expired', {
313
+ log.warn(() => ({
314
+ message: 'token expired',
301
315
  expiresAt: new Date(parsed.claudeAiOauth.expiresAt).toISOString(),
302
- });
316
+ }));
303
317
  // TODO: Implement token refresh using refreshToken
304
318
  // For now, user needs to re-authenticate
305
319
  }
306
320
 
307
- log.info('loaded oauth credentials', {
321
+ log.info(() => ({
322
+ message: 'loaded oauth credentials',
308
323
  subscriptionType: parsed.claudeAiOauth.subscriptionType,
309
324
  scopes: parsed.claudeAiOauth.scopes,
310
- });
325
+ }));
311
326
 
312
327
  return parsed.claudeAiOauth;
313
328
  } catch (error) {
314
- log.error('failed to read credentials', { error });
329
+ log.error(() => ({ message: 'failed to read credentials', error }));
315
330
  return undefined;
316
331
  }
317
332
  }
@@ -344,7 +359,9 @@ export namespace ClaudeOAuth {
344
359
  export async function completeAuth(code: string): Promise<boolean> {
345
360
  const state = await loadState();
346
361
  if (!state) {
347
- log.error('no oauth state found - please start login flow first');
362
+ log.error(() => ({
363
+ message: 'no oauth state found - please start login flow first',
364
+ }));
348
365
  return false;
349
366
  }
350
367
 
@@ -352,10 +369,15 @@ export namespace ClaudeOAuth {
352
369
  const tokens = await exchangeCode(code, state.codeVerifier);
353
370
  await saveCredentials(tokens);
354
371
  await clearState();
355
- log.info('authentication completed successfully');
372
+ log.info(() => ({
373
+ message: 'authentication completed successfully',
374
+ }));
356
375
  return true;
357
376
  } catch (error) {
358
- log.error('failed to complete authentication', { error });
377
+ log.error(() => ({
378
+ message: 'failed to complete authentication',
379
+ error,
380
+ }));
359
381
  await clearState();
360
382
  return false;
361
383
  }
@@ -367,7 +389,7 @@ export namespace ClaudeOAuth {
367
389
  export async function refreshToken(): Promise<boolean> {
368
390
  const creds = await getCredentials();
369
391
  if (!creds?.refreshToken) {
370
- log.error('no refresh token available');
392
+ log.error(() => ({ message: 'no refresh token available' }));
371
393
  return false;
372
394
  }
373
395
 
@@ -377,7 +399,7 @@ export namespace ClaudeOAuth {
377
399
  refresh_token: creds.refreshToken,
378
400
  });
379
401
 
380
- log.info('refreshing access token');
402
+ log.info(() => ({ message: 'refreshing access token' }));
381
403
 
382
404
  try {
383
405
  const response = await fetch(Config.tokenUrl, {
@@ -390,16 +412,20 @@ export namespace ClaudeOAuth {
390
412
 
391
413
  if (!response.ok) {
392
414
  const error = await response.text();
393
- log.error('token refresh failed', { status: response.status, error });
415
+ log.error(() => ({
416
+ message: 'token refresh failed',
417
+ status: response.status,
418
+ error,
419
+ }));
394
420
  return false;
395
421
  }
396
422
 
397
423
  const tokens = TokenResponse.parse(await response.json());
398
424
  await saveCredentials(tokens);
399
- log.info('token refreshed successfully');
425
+ log.info(() => ({ message: 'token refreshed successfully' }));
400
426
  return true;
401
427
  } catch (error) {
402
- log.error('failed to refresh token', { error });
428
+ log.error(() => ({ message: 'failed to refresh token', error }));
403
429
  return false;
404
430
  }
405
431
  }
@@ -162,9 +162,10 @@ const AnthropicPlugin: AuthPlugin = {
162
162
  );
163
163
 
164
164
  if (!result.ok) {
165
- log.error('anthropic oauth token exchange failed', {
165
+ log.error(() => ({
166
+ message: 'anthropic oauth token exchange failed',
166
167
  status: result.status,
167
- });
168
+ }));
168
169
  return { type: 'failed' };
169
170
  }
170
171
 
@@ -229,9 +230,10 @@ const AnthropicPlugin: AuthPlugin = {
229
230
  );
230
231
 
231
232
  if (!tokenResult.ok) {
232
- log.error('anthropic oauth token exchange failed', {
233
+ log.error(() => ({
234
+ message: 'anthropic oauth token exchange failed',
233
235
  status: tokenResult.status,
234
- });
236
+ }));
235
237
  return { type: 'failed' };
236
238
  }
237
239
 
@@ -286,7 +288,9 @@ const AnthropicPlugin: AuthPlugin = {
286
288
 
287
289
  // Refresh token if expired
288
290
  if (!currentAuth.access || currentAuth.expires < Date.now()) {
289
- log.info('refreshing anthropic oauth token');
291
+ log.info(() => ({
292
+ message: 'refreshing anthropic oauth token',
293
+ }));
290
294
  const response = await fetch(
291
295
  'https://console.anthropic.com/v1/oauth/token',
292
296
  {
@@ -566,7 +570,7 @@ const GitHubCopilotPlugin: AuthPlugin = {
566
570
  : 'github.com';
567
571
  const urls = getCopilotUrls(domain);
568
572
 
569
- log.info('refreshing github copilot token');
573
+ log.info(() => ({ message: 'refreshing github copilot token' }));
570
574
  const response = await fetch(urls.COPILOT_API_KEY_URL, {
571
575
  headers: {
572
576
  Accept: 'application/json',
@@ -720,7 +724,9 @@ const OpenAIPlugin: AuthPlugin = {
720
724
  }
721
725
 
722
726
  if (!code) {
723
- log.error('openai oauth no code provided');
727
+ log.error(() => ({
728
+ message: 'openai oauth no code provided',
729
+ }));
724
730
  return { type: 'failed' };
725
731
  }
726
732
 
@@ -740,9 +746,10 @@ const OpenAIPlugin: AuthPlugin = {
740
746
  });
741
747
 
742
748
  if (!tokenResult.ok) {
743
- log.error('openai oauth token exchange failed', {
749
+ log.error(() => ({
750
+ message: 'openai oauth token exchange failed',
744
751
  status: tokenResult.status,
745
- });
752
+ }));
746
753
  return { type: 'failed' };
747
754
  }
748
755
 
@@ -752,7 +759,9 @@ const OpenAIPlugin: AuthPlugin = {
752
759
  !json.refresh_token ||
753
760
  typeof json.expires_in !== 'number'
754
761
  ) {
755
- log.error('openai oauth token response missing fields');
762
+ log.error(() => ({
763
+ message: 'openai oauth token response missing fields',
764
+ }));
756
765
  return { type: 'failed' };
757
766
  }
758
767
 
@@ -787,7 +796,7 @@ const OpenAIPlugin: AuthPlugin = {
787
796
 
788
797
  // Refresh token if expired
789
798
  if (!currentAuth.access || currentAuth.expires < Date.now()) {
790
- log.info('refreshing openai oauth token');
799
+ log.info(() => ({ message: 'refreshing openai oauth token' }));
791
800
  const response = await fetch(OPENAI_TOKEN_URL, {
792
801
  method: 'POST',
793
802
  headers: {
@@ -992,9 +1001,10 @@ const GooglePlugin: AuthPlugin = {
992
1001
  });
993
1002
 
994
1003
  if (!tokenResult.ok) {
995
- log.error('google oauth token exchange failed', {
1004
+ log.error(() => ({
1005
+ message: 'google oauth token exchange failed',
996
1006
  status: tokenResult.status,
997
- });
1007
+ }));
998
1008
  return { type: 'failed' };
999
1009
  }
1000
1010
 
@@ -1004,7 +1014,9 @@ const GooglePlugin: AuthPlugin = {
1004
1014
  !json.refresh_token ||
1005
1015
  typeof json.expires_in !== 'number'
1006
1016
  ) {
1007
- log.error('google oauth token response missing fields');
1017
+ log.error(() => ({
1018
+ message: 'google oauth token response missing fields',
1019
+ }));
1008
1020
  return { type: 'failed' };
1009
1021
  }
1010
1022
 
@@ -1015,7 +1027,7 @@ const GooglePlugin: AuthPlugin = {
1015
1027
  expires: Date.now() + json.expires_in * 1000,
1016
1028
  };
1017
1029
  } catch (error) {
1018
- log.error('google oauth failed', { error });
1030
+ log.error(() => ({ message: 'google oauth failed', error }));
1019
1031
  return { type: 'failed' };
1020
1032
  }
1021
1033
  },
@@ -1058,7 +1070,7 @@ const GooglePlugin: AuthPlugin = {
1058
1070
  !currentAuth.access ||
1059
1071
  currentAuth.expires < Date.now() + FIVE_MIN_MS
1060
1072
  ) {
1061
- log.info('refreshing google oauth token');
1073
+ log.info(() => ({ message: 'refreshing google oauth token' }));
1062
1074
  const response = await fetch(GOOGLE_TOKEN_URL, {
1063
1075
  method: 'POST',
1064
1076
  headers: {
package/src/bun/index.ts CHANGED
@@ -17,10 +17,11 @@ export namespace BunProc {
17
17
  cmd: string[],
18
18
  options?: Bun.SpawnOptions.OptionsObject<any, any, any>
19
19
  ) {
20
- log.info('running', {
20
+ log.info(() => ({
21
+ message: 'running',
21
22
  cmd: [which(), ...cmd],
22
23
  ...options,
23
- });
24
+ }));
24
25
  const result = Bun.spawn([which(), ...cmd], {
25
26
  ...options,
26
27
  stdout: 'pipe',
@@ -42,11 +43,7 @@ export namespace BunProc {
42
43
  ? result.stderr
43
44
  : await readableStreamToText(result.stderr)
44
45
  : undefined;
45
- log.info('done', {
46
- code,
47
- stdout,
48
- stderr,
49
- });
46
+ log.info(() => ({ message: 'done', code, stdout, stderr }));
50
47
  if (code !== 0) {
51
48
  const parts = [`Command failed with exit code ${result.exitCode}`];
52
49
  if (stderr) parts.push(`stderr: ${stderr}`);
@@ -111,14 +108,13 @@ export namespace BunProc {
111
108
 
112
109
  // Check for dry-run mode
113
110
  if (Flag.OPENCODE_DRY_RUN) {
114
- log.info(
115
- '[DRY RUN] Would install package (skipping actual installation)',
116
- {
117
- pkg,
118
- version,
119
- targetPath: mod,
120
- }
121
- );
111
+ log.info(() => ({
112
+ message:
113
+ '[DRY RUN] Would install package (skipping actual installation)',
114
+ pkg,
115
+ version,
116
+ targetPath: mod,
117
+ }));
122
118
  // In dry-run mode, pretend the package is installed
123
119
  return mod;
124
120
  }
@@ -137,10 +133,11 @@ export namespace BunProc {
137
133
  // - If .npmrc files exist, Bun will use them automatically
138
134
  // - If no .npmrc files exist, Bun will default to https://registry.npmjs.org
139
135
  // - No need to pass --registry flag
140
- log.info("installing package using Bun's default registry resolution", {
136
+ log.info(() => ({
137
+ message: "installing package using Bun's default registry resolution",
141
138
  pkg,
142
139
  version,
143
- });
140
+ }));
144
141
 
145
142
  // Retry logic for cache-related errors
146
143
  let lastError: Error | undefined;
@@ -150,7 +147,12 @@ export namespace BunProc {
150
147
  cwd: Global.Path.cache,
151
148
  });
152
149
 
153
- log.info('package installed successfully', { pkg, version, attempt });
150
+ log.info(() => ({
151
+ message: 'package installed successfully',
152
+ pkg,
153
+ version,
154
+ attempt,
155
+ }));
154
156
  parsed.dependencies[pkg] = version;
155
157
  await Bun.write(pkgjson.name!, JSON.stringify(parsed, null, 2));
156
158
  return mod;
@@ -158,43 +160,47 @@ export namespace BunProc {
158
160
  const errorMsg = e instanceof Error ? e.message : String(e);
159
161
  const isCacheError = isCacheRelatedError(errorMsg);
160
162
 
161
- log.warn('package installation attempt failed', {
163
+ log.warn(() => ({
164
+ message: 'package installation attempt failed',
162
165
  pkg,
163
166
  version,
164
167
  attempt,
165
168
  maxRetries: MAX_RETRIES,
166
169
  error: errorMsg,
167
170
  isCacheError,
168
- });
171
+ }));
169
172
 
170
173
  if (isCacheError && attempt < MAX_RETRIES) {
171
- log.info('retrying installation after cache-related error', {
174
+ log.info(() => ({
175
+ message: 'retrying installation after cache-related error',
172
176
  pkg,
173
177
  version,
174
178
  attempt,
175
179
  nextAttempt: attempt + 1,
176
180
  delayMs: RETRY_DELAY_MS,
177
- });
181
+ }));
178
182
  await delay(RETRY_DELAY_MS);
179
183
  lastError = e instanceof Error ? e : new Error(errorMsg);
180
184
  continue;
181
185
  }
182
186
 
183
187
  // Non-cache error or final attempt - log and throw
184
- log.error('package installation failed', {
188
+ log.error(() => ({
189
+ message: 'package installation failed',
185
190
  pkg,
186
191
  version,
187
192
  error: errorMsg,
188
193
  stack: e instanceof Error ? e.stack : undefined,
189
194
  possibleCacheCorruption: isCacheError,
190
195
  attempts: attempt,
191
- });
196
+ }));
192
197
 
193
198
  // Provide helpful recovery instructions for cache-related errors
194
199
  if (isCacheError) {
195
- log.error(
196
- 'Bun package cache may be corrupted. Try clearing the cache with: bun pm cache rm'
197
- );
200
+ log.error(() => ({
201
+ message:
202
+ 'Bun package cache may be corrupted. Try clearing the cache with: bun pm cache rm',
203
+ }));
198
204
  }
199
205
 
200
206
  throw new InstallFailedError(
package/src/bus/index.ts CHANGED
@@ -63,9 +63,7 @@ export namespace Bus {
63
63
  type: def.type,
64
64
  properties,
65
65
  };
66
- log.info('publishing', {
67
- type: def.type,
68
- });
66
+ log.info(() => ({ message: 'publishing', type: def.type }));
69
67
  const pending = [];
70
68
  for (const key of [def.type, '*']) {
71
69
  const match = state().subscriptions.get(key);
@@ -107,14 +105,14 @@ export namespace Bus {
107
105
  }
108
106
 
109
107
  function raw(type: string, callback: (event: any) => void) {
110
- log.info('subscribing', { type });
108
+ log.info(() => ({ message: 'subscribing', type }));
111
109
  const subscriptions = state().subscriptions;
112
110
  let match = subscriptions.get(type) ?? [];
113
111
  match.push(callback);
114
112
  subscriptions.set(type, match);
115
113
 
116
114
  return () => {
117
- log.info('unsubscribing', { type });
115
+ log.info(() => ({ message: 'unsubscribing', type }));
118
116
  const match = subscriptions.get(type);
119
117
  if (!match) return;
120
118
  const index = match.indexOf(callback);
@@ -53,16 +53,21 @@ export namespace Config {
53
53
  if (!newDirExists) {
54
54
  try {
55
55
  // Perform migration by copying the entire directory
56
- log.info(
57
- `Migrating config from ${oldDir} to ${newDir} for smooth transition`
58
- );
56
+ log.info(() => ({
57
+ message: `Migrating config from ${oldDir} to ${newDir} for smooth transition`,
58
+ }));
59
59
 
60
60
  // Use fs-extra style recursive copy
61
61
  await copyDirectory(oldDir, newDir);
62
62
 
63
- log.info(`Successfully migrated config to ${newDir}`);
63
+ log.info(() => ({
64
+ message: `Successfully migrated config to ${newDir}`,
65
+ }));
64
66
  } catch (error) {
65
- log.error(`Failed to migrate config from ${oldDir}:`, error);
67
+ log.error(() => ({
68
+ message: `Failed to migrate config from ${oldDir}:`,
69
+ error,
70
+ }));
66
71
  // Don't throw - allow the app to continue with the old config
67
72
  }
68
73
  }
@@ -83,14 +88,19 @@ export namespace Config {
83
88
  .catch(() => false);
84
89
 
85
90
  if (oldGlobalExists && !newGlobalExists) {
86
- log.info(
87
- `Migrating global config from ${oldGlobalPath} to ${newGlobalPath}`
88
- );
91
+ log.info(() => ({
92
+ message: `Migrating global config from ${oldGlobalPath} to ${newGlobalPath}`,
93
+ }));
89
94
  await copyDirectory(oldGlobalPath, newGlobalPath);
90
- log.info(`Successfully migrated global config to ${newGlobalPath}`);
95
+ log.info(() => ({
96
+ message: `Successfully migrated global config to ${newGlobalPath}`,
97
+ }));
91
98
  }
92
99
  } catch (error) {
93
- log.error('Failed to migrate global config:', error);
100
+ log.error(() => ({
101
+ message: 'Failed to migrate global config:',
102
+ error,
103
+ }));
94
104
  // Don't throw - allow the app to continue
95
105
  }
96
106
  }
@@ -126,7 +136,10 @@ export namespace Config {
126
136
  // Override with custom config if provided
127
137
  if (Flag.OPENCODE_CONFIG) {
128
138
  result = mergeDeep(result, await loadFile(Flag.OPENCODE_CONFIG));
129
- log.debug('loaded custom config', { path: Flag.OPENCODE_CONFIG });
139
+ log.debug(() => ({
140
+ message: 'loaded custom config',
141
+ path: Flag.OPENCODE_CONFIG,
142
+ }));
130
143
  }
131
144
 
132
145
  for (const file of ['opencode.jsonc', 'opencode.json']) {
@@ -142,7 +155,9 @@ export namespace Config {
142
155
 
143
156
  if (Flag.OPENCODE_CONFIG_CONTENT) {
144
157
  result = mergeDeep(result, JSON.parse(Flag.OPENCODE_CONFIG_CONTENT));
145
- log.debug('loaded custom config from OPENCODE_CONFIG_CONTENT');
158
+ log.debug(() => ({
159
+ message: 'loaded custom config from OPENCODE_CONFIG_CONTENT',
160
+ }));
146
161
  }
147
162
 
148
163
  for (const [key, value] of Object.entries(auth)) {
@@ -182,12 +197,11 @@ export namespace Config {
182
197
  const filteredDirs = foundDirs.filter((dir) => {
183
198
  // If .link-assistant-agent exists, exclude .opencode directories
184
199
  if (hasNewConfig && dir.endsWith('.opencode')) {
185
- log.debug(
186
- 'Skipping .opencode directory (using .link-assistant-agent):',
187
- {
188
- path: dir,
189
- }
190
- );
200
+ log.debug(() => ({
201
+ message:
202
+ 'Skipping .opencode directory (using .link-assistant-agent):',
203
+ path: dir,
204
+ }));
191
205
  return false;
192
206
  }
193
207
  return true;
@@ -197,9 +211,10 @@ export namespace Config {
197
211
 
198
212
  if (Flag.OPENCODE_CONFIG_DIR) {
199
213
  directories.push(Flag.OPENCODE_CONFIG_DIR);
200
- log.debug('loading config from LINK_ASSISTANT_AGENT_CONFIG_DIR', {
214
+ log.debug(() => ({
215
+ message: 'loading config from LINK_ASSISTANT_AGENT_CONFIG_DIR',
201
216
  path: Flag.OPENCODE_CONFIG_DIR,
202
- });
217
+ }));
203
218
  }
204
219
 
205
220
  const promises: Promise<void>[] = [];
@@ -212,7 +227,9 @@ export namespace Config {
212
227
  dir === Flag.OPENCODE_CONFIG_DIR
213
228
  ) {
214
229
  for (const file of ['opencode.jsonc', 'opencode.json']) {
215
- log.debug(`loading config from ${path.join(dir, file)}`);
230
+ log.debug(() => ({
231
+ message: `loading config from ${path.join(dir, file)}`,
232
+ }));
216
233
  result = mergeDeep(result, await loadFile(path.join(dir, file)));
217
234
  // to satisy the type checker
218
235
  result.agent ??= {};
@@ -932,7 +949,7 @@ export namespace Config {
932
949
  });
933
950
 
934
951
  async function loadFile(filepath: string): Promise<Info> {
935
- log.info('loading', { path: filepath });
952
+ log.info(() => ({ message: 'loading', path: filepath }));
936
953
  let text = await Bun.file(filepath)
937
954
  .text()
938
955
  .catch((err) => {
@@ -275,7 +275,7 @@ export namespace Ripgrep {
275
275
  }
276
276
 
277
277
  export async function tree(input: { cwd: string; limit?: number }) {
278
- log.info('tree', input);
278
+ log.info(() => ({ message: 'tree', ...input }));
279
279
  const files = await Array.fromAsync(Ripgrep.files({ cwd: input.cwd }));
280
280
  interface Node {
281
281
  path: string[];
package/src/file/time.ts CHANGED
@@ -15,7 +15,7 @@ export namespace FileTime {
15
15
  });
16
16
 
17
17
  export function read(sessionID: string, file: string) {
18
- log.info('read', { sessionID, file });
18
+ log.info(() => ({ message: 'read', sessionID, file }));
19
19
  const { read } = state();
20
20
  read[sessionID] = read[sessionID] || {};
21
21
  read[sessionID][file] = new Date();