@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,98 +1,112 @@
1
- import type { Argv } from "yargs"
2
- import { cmd } from "./cmd"
3
- import { Session } from "../../session"
4
- import { bootstrap } from "../bootstrap"
5
- import { Storage } from "../../storage/storage"
6
- import { Project } from "../../project/project"
7
- import { Instance } from "../../project/instance"
1
+ import type { Argv } from 'yargs';
2
+ import { cmd } from './cmd';
3
+ import { Session } from '../../session';
4
+ import { bootstrap } from '../bootstrap';
5
+ import { Storage } from '../../storage/storage';
6
+ import { Project } from '../../project/project';
7
+ import { Instance } from '../../project/instance';
8
8
 
9
9
  interface SessionStats {
10
- totalSessions: number
11
- totalMessages: number
12
- totalCost: number
10
+ totalSessions: number;
11
+ totalMessages: number;
12
+ totalCost: number;
13
13
  totalTokens: {
14
- input: number
15
- output: number
16
- reasoning: number
14
+ input: number;
15
+ output: number;
16
+ reasoning: number;
17
17
  cache: {
18
- read: number
19
- write: number
20
- }
21
- }
22
- toolUsage: Record<string, number>
18
+ read: number;
19
+ write: number;
20
+ };
21
+ };
22
+ toolUsage: Record<string, number>;
23
23
  dateRange: {
24
- earliest: number
25
- latest: number
26
- }
27
- days: number
28
- costPerDay: number
24
+ earliest: number;
25
+ latest: number;
26
+ };
27
+ days: number;
28
+ costPerDay: number;
29
29
  }
30
30
 
31
31
  export const StatsCommand = cmd({
32
- command: "stats",
33
- describe: "show token usage and cost statistics",
32
+ command: 'stats',
33
+ describe: 'show token usage and cost statistics',
34
34
  builder: (yargs: Argv) => {
35
35
  return yargs
36
- .option("days", {
37
- describe: "show stats for the last N days (default: all time)",
38
- type: "number",
36
+ .option('days', {
37
+ describe: 'show stats for the last N days (default: all time)',
38
+ type: 'number',
39
39
  })
40
- .option("tools", {
41
- describe: "number of tools to show (default: all)",
42
- type: "number",
43
- })
44
- .option("project", {
45
- describe: "filter by project (default: all projects, empty string: current project)",
46
- type: "string",
40
+ .option('tools', {
41
+ describe: 'number of tools to show (default: all)',
42
+ type: 'number',
47
43
  })
44
+ .option('project', {
45
+ describe:
46
+ 'filter by project (default: all projects, empty string: current project)',
47
+ type: 'string',
48
+ });
48
49
  },
49
50
  handler: async (args) => {
50
51
  await bootstrap(process.cwd(), async () => {
51
- const stats = await aggregateSessionStats(args.days, args.project)
52
- displayStats(stats, args.tools)
53
- })
52
+ const stats = await aggregateSessionStats(args.days, args.project);
53
+ displayStats(stats, args.tools);
54
+ });
54
55
  },
55
- })
56
+ });
56
57
 
57
58
  async function getCurrentProject(): Promise<Project.Info> {
58
- return Instance.project
59
+ return Instance.project;
59
60
  }
60
61
 
61
62
  async function getAllSessions(): Promise<Session.Info[]> {
62
- const sessions: Session.Info[] = []
63
+ const sessions: Session.Info[] = [];
63
64
 
64
- const projectKeys = await Storage.list(["project"])
65
- const projects = await Promise.all(projectKeys.map((key) => Storage.read<Project.Info>(key)))
65
+ const projectKeys = await Storage.list(['project']);
66
+ const projects = await Promise.all(
67
+ projectKeys.map((key) => Storage.read<Project.Info>(key))
68
+ );
66
69
 
67
70
  for (const project of projects) {
68
- if (!project) continue
71
+ if (!project) continue;
69
72
 
70
- const sessionKeys = await Storage.list(["session", project.id])
71
- const projectSessions = await Promise.all(sessionKeys.map((key) => Storage.read<Session.Info>(key)))
73
+ const sessionKeys = await Storage.list(['session', project.id]);
74
+ const projectSessions = await Promise.all(
75
+ sessionKeys.map((key) => Storage.read<Session.Info>(key))
76
+ );
72
77
 
73
78
  for (const session of projectSessions) {
74
79
  if (session) {
75
- sessions.push(session)
80
+ sessions.push(session);
76
81
  }
77
82
  }
78
83
  }
79
84
 
80
- return sessions
85
+ return sessions;
81
86
  }
82
87
 
83
- async function aggregateSessionStats(days?: number, projectFilter?: string): Promise<SessionStats> {
84
- const sessions = await getAllSessions()
85
- const DAYS_IN_SECOND = 24 * 60 * 60 * 1000
86
- const cutoffTime = days ? Date.now() - days * DAYS_IN_SECOND : 0
88
+ async function aggregateSessionStats(
89
+ days?: number,
90
+ projectFilter?: string
91
+ ): Promise<SessionStats> {
92
+ const sessions = await getAllSessions();
93
+ const DAYS_IN_SECOND = 24 * 60 * 60 * 1000;
94
+ const cutoffTime = days ? Date.now() - days * DAYS_IN_SECOND : 0;
87
95
 
88
- let filteredSessions = days ? sessions.filter((session) => session.time.updated >= cutoffTime) : sessions
96
+ let filteredSessions = days
97
+ ? sessions.filter((session) => session.time.updated >= cutoffTime)
98
+ : sessions;
89
99
 
90
100
  if (projectFilter !== undefined) {
91
- if (projectFilter === "") {
92
- const currentProject = await getCurrentProject()
93
- filteredSessions = filteredSessions.filter((session) => session.projectID === currentProject.id)
101
+ if (projectFilter === '') {
102
+ const currentProject = await getCurrentProject();
103
+ filteredSessions = filteredSessions.filter(
104
+ (session) => session.projectID === currentProject.id
105
+ );
94
106
  } else {
95
- filteredSessions = filteredSessions.filter((session) => session.projectID === projectFilter)
107
+ filteredSessions = filteredSessions.filter(
108
+ (session) => session.projectID === projectFilter
109
+ );
96
110
  }
97
111
  }
98
112
 
@@ -116,46 +130,54 @@ async function aggregateSessionStats(days?: number, projectFilter?: string): Pro
116
130
  },
117
131
  days: 0,
118
132
  costPerDay: 0,
119
- }
133
+ };
120
134
 
121
135
  if (filteredSessions.length > 1000) {
122
- console.log(`Large dataset detected (${filteredSessions.length} sessions). This may take a while...`)
136
+ console.log(
137
+ `Large dataset detected (${filteredSessions.length} sessions). This may take a while...`
138
+ );
123
139
  }
124
140
 
125
141
  if (filteredSessions.length === 0) {
126
- return stats
142
+ return stats;
127
143
  }
128
144
 
129
- let earliestTime = Date.now()
130
- let latestTime = 0
145
+ let earliestTime = Date.now();
146
+ let latestTime = 0;
131
147
 
132
- const BATCH_SIZE = 20
148
+ const BATCH_SIZE = 20;
133
149
  for (let i = 0; i < filteredSessions.length; i += BATCH_SIZE) {
134
- const batch = filteredSessions.slice(i, i + BATCH_SIZE)
150
+ const batch = filteredSessions.slice(i, i + BATCH_SIZE);
135
151
 
136
152
  const batchPromises = batch.map(async (session) => {
137
- const messages = await Session.messages({ sessionID: session.id })
153
+ const messages = await Session.messages({ sessionID: session.id });
138
154
 
139
- let sessionCost = 0
140
- let sessionTokens = { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }
141
- let sessionToolUsage: Record<string, number> = {}
155
+ let sessionCost = 0;
156
+ let sessionTokens = {
157
+ input: 0,
158
+ output: 0,
159
+ reasoning: 0,
160
+ cache: { read: 0, write: 0 },
161
+ };
162
+ let sessionToolUsage: Record<string, number> = {};
142
163
 
143
164
  for (const message of messages) {
144
- if (message.info.role === "assistant") {
145
- sessionCost += message.info.cost || 0
165
+ if (message.info.role === 'assistant') {
166
+ sessionCost += message.info.cost || 0;
146
167
 
147
168
  if (message.info.tokens) {
148
- sessionTokens.input += message.info.tokens.input || 0
149
- sessionTokens.output += message.info.tokens.output || 0
150
- sessionTokens.reasoning += message.info.tokens.reasoning || 0
151
- sessionTokens.cache.read += message.info.tokens.cache?.read || 0
152
- sessionTokens.cache.write += message.info.tokens.cache?.write || 0
169
+ sessionTokens.input += message.info.tokens.input || 0;
170
+ sessionTokens.output += message.info.tokens.output || 0;
171
+ sessionTokens.reasoning += message.info.tokens.reasoning || 0;
172
+ sessionTokens.cache.read += message.info.tokens.cache?.read || 0;
173
+ sessionTokens.cache.write += message.info.tokens.cache?.write || 0;
153
174
  }
154
175
  }
155
176
 
156
177
  for (const part of message.parts) {
157
- if (part.type === "tool" && part.tool) {
158
- sessionToolUsage[part.tool] = (sessionToolUsage[part.tool] || 0) + 1
178
+ if (part.type === 'tool' && part.tool) {
179
+ sessionToolUsage[part.tool] =
180
+ (sessionToolUsage[part.tool] || 0) + 1;
159
181
  }
160
182
  }
161
183
  }
@@ -167,110 +189,127 @@ async function aggregateSessionStats(days?: number, projectFilter?: string): Pro
167
189
  sessionToolUsage,
168
190
  earliestTime: session.time.created,
169
191
  latestTime: session.time.updated,
170
- }
171
- })
192
+ };
193
+ });
172
194
 
173
- const batchResults = await Promise.all(batchPromises)
195
+ const batchResults = await Promise.all(batchPromises);
174
196
 
175
197
  for (const result of batchResults) {
176
- earliestTime = Math.min(earliestTime, result.earliestTime)
177
- latestTime = Math.max(latestTime, result.latestTime)
198
+ earliestTime = Math.min(earliestTime, result.earliestTime);
199
+ latestTime = Math.max(latestTime, result.latestTime);
178
200
 
179
- stats.totalMessages += result.messageCount
180
- stats.totalCost += result.sessionCost
181
- stats.totalTokens.input += result.sessionTokens.input
182
- stats.totalTokens.output += result.sessionTokens.output
183
- stats.totalTokens.reasoning += result.sessionTokens.reasoning
184
- stats.totalTokens.cache.read += result.sessionTokens.cache.read
185
- stats.totalTokens.cache.write += result.sessionTokens.cache.write
201
+ stats.totalMessages += result.messageCount;
202
+ stats.totalCost += result.sessionCost;
203
+ stats.totalTokens.input += result.sessionTokens.input;
204
+ stats.totalTokens.output += result.sessionTokens.output;
205
+ stats.totalTokens.reasoning += result.sessionTokens.reasoning;
206
+ stats.totalTokens.cache.read += result.sessionTokens.cache.read;
207
+ stats.totalTokens.cache.write += result.sessionTokens.cache.write;
186
208
 
187
209
  for (const [tool, count] of Object.entries(result.sessionToolUsage)) {
188
- stats.toolUsage[tool] = (stats.toolUsage[tool] || 0) + count
210
+ stats.toolUsage[tool] = (stats.toolUsage[tool] || 0) + count;
189
211
  }
190
212
  }
191
213
  }
192
214
 
193
- const actualDays = Math.max(1, Math.ceil((latestTime - earliestTime) / DAYS_IN_SECOND))
215
+ const actualDays = Math.max(
216
+ 1,
217
+ Math.ceil((latestTime - earliestTime) / DAYS_IN_SECOND)
218
+ );
194
219
  stats.dateRange = {
195
220
  earliest: earliestTime,
196
221
  latest: latestTime,
197
- }
198
- stats.days = actualDays
199
- stats.costPerDay = stats.totalCost / actualDays
222
+ };
223
+ stats.days = actualDays;
224
+ stats.costPerDay = stats.totalCost / actualDays;
200
225
 
201
- return stats
226
+ return stats;
202
227
  }
203
228
 
204
229
  export function displayStats(stats: SessionStats, toolLimit?: number) {
205
- const width = 56
230
+ const width = 56;
206
231
 
207
232
  function renderRow(label: string, value: string): string {
208
- const availableWidth = width - 1
209
- const paddingNeeded = availableWidth - label.length - value.length
210
- const padding = Math.max(0, paddingNeeded)
211
- return `│${label}${" ".repeat(padding)}${value} │`
233
+ const availableWidth = width - 1;
234
+ const paddingNeeded = availableWidth - label.length - value.length;
235
+ const padding = Math.max(0, paddingNeeded);
236
+ return `│${label}${' '.repeat(padding)}${value} │`;
212
237
  }
213
238
 
214
239
  // Overview section
215
- console.log("┌────────────────────────────────────────────────────────┐")
216
- console.log("│ OVERVIEW │")
217
- console.log("├────────────────────────────────────────────────────────┤")
218
- console.log(renderRow("Sessions", stats.totalSessions.toLocaleString()))
219
- console.log(renderRow("Messages", stats.totalMessages.toLocaleString()))
220
- console.log(renderRow("Days", stats.days.toString()))
221
- console.log("└────────────────────────────────────────────────────────┘")
222
- console.log()
240
+ console.log('┌────────────────────────────────────────────────────────┐');
241
+ console.log('│ OVERVIEW │');
242
+ console.log('├────────────────────────────────────────────────────────┤');
243
+ console.log(renderRow('Sessions', stats.totalSessions.toLocaleString()));
244
+ console.log(renderRow('Messages', stats.totalMessages.toLocaleString()));
245
+ console.log(renderRow('Days', stats.days.toString()));
246
+ console.log('└────────────────────────────────────────────────────────┘');
247
+ console.log();
223
248
 
224
249
  // Cost & Tokens section
225
- console.log("┌────────────────────────────────────────────────────────┐")
226
- console.log("│ COST & TOKENS │")
227
- console.log("├────────────────────────────────────────────────────────┤")
228
- const cost = isNaN(stats.totalCost) ? 0 : stats.totalCost
229
- const costPerDay = isNaN(stats.costPerDay) ? 0 : stats.costPerDay
230
- console.log(renderRow("Total Cost", `$${cost.toFixed(2)}`))
231
- console.log(renderRow("Cost/Day", `$${costPerDay.toFixed(2)}`))
232
- console.log(renderRow("Input", formatNumber(stats.totalTokens.input)))
233
- console.log(renderRow("Output", formatNumber(stats.totalTokens.output)))
234
- console.log(renderRow("Cache Read", formatNumber(stats.totalTokens.cache.read)))
235
- console.log(renderRow("Cache Write", formatNumber(stats.totalTokens.cache.write)))
236
- console.log("└────────────────────────────────────────────────────────┘")
237
- console.log()
250
+ console.log('┌────────────────────────────────────────────────────────┐');
251
+ console.log('│ COST & TOKENS │');
252
+ console.log('├────────────────────────────────────────────────────────┤');
253
+ const cost = isNaN(stats.totalCost) ? 0 : stats.totalCost;
254
+ const costPerDay = isNaN(stats.costPerDay) ? 0 : stats.costPerDay;
255
+ console.log(renderRow('Total Cost', `$${cost.toFixed(2)}`));
256
+ console.log(renderRow('Cost/Day', `$${costPerDay.toFixed(2)}`));
257
+ console.log(renderRow('Input', formatNumber(stats.totalTokens.input)));
258
+ console.log(renderRow('Output', formatNumber(stats.totalTokens.output)));
259
+ console.log(
260
+ renderRow('Cache Read', formatNumber(stats.totalTokens.cache.read))
261
+ );
262
+ console.log(
263
+ renderRow('Cache Write', formatNumber(stats.totalTokens.cache.write))
264
+ );
265
+ console.log('└────────────────────────────────────────────────────────┘');
266
+ console.log();
238
267
 
239
268
  // Tool Usage section
240
269
  if (Object.keys(stats.toolUsage).length > 0) {
241
- const sortedTools = Object.entries(stats.toolUsage).sort(([, a], [, b]) => b - a)
242
- const toolsToDisplay = toolLimit ? sortedTools.slice(0, toolLimit) : sortedTools
243
-
244
- console.log("┌────────────────────────────────────────────────────────┐")
245
- console.log("│ TOOL USAGE │")
246
- console.log("├────────────────────────────────────────────────────────┤")
247
-
248
- const maxCount = Math.max(...toolsToDisplay.map(([, count]) => count))
249
- const totalToolUsage = Object.values(stats.toolUsage).reduce((a, b) => a + b, 0)
270
+ const sortedTools = Object.entries(stats.toolUsage).sort(
271
+ ([, a], [, b]) => b - a
272
+ );
273
+ const toolsToDisplay = toolLimit
274
+ ? sortedTools.slice(0, toolLimit)
275
+ : sortedTools;
276
+
277
+ console.log('┌────────────────────────────────────────────────────────┐');
278
+ console.log('│ TOOL USAGE │');
279
+ console.log('├────────────────────────────────────────────────────────┤');
280
+
281
+ const maxCount = Math.max(...toolsToDisplay.map(([, count]) => count));
282
+ const totalToolUsage = Object.values(stats.toolUsage).reduce(
283
+ (a, b) => a + b,
284
+ 0
285
+ );
250
286
 
251
287
  for (const [tool, count] of toolsToDisplay) {
252
- const barLength = Math.max(1, Math.floor((count / maxCount) * 20))
253
- const bar = "".repeat(barLength)
254
- const percentage = ((count / totalToolUsage) * 100).toFixed(1)
255
-
256
- const maxToolLength = 18
257
- const truncatedTool = tool.length > maxToolLength ? tool.substring(0, maxToolLength - 2) + ".." : tool
258
- const toolName = truncatedTool.padEnd(maxToolLength)
259
-
260
- const content = ` ${toolName} ${bar.padEnd(20)} ${count.toString().padStart(3)} (${percentage.padStart(4)}%)`
261
- const padding = Math.max(0, width - content.length - 1)
262
- console.log(`│${content}${" ".repeat(padding)} │`)
288
+ const barLength = Math.max(1, Math.floor((count / maxCount) * 20));
289
+ const bar = ''.repeat(barLength);
290
+ const percentage = ((count / totalToolUsage) * 100).toFixed(1);
291
+
292
+ const maxToolLength = 18;
293
+ const truncatedTool =
294
+ tool.length > maxToolLength
295
+ ? tool.substring(0, maxToolLength - 2) + '..'
296
+ : tool;
297
+ const toolName = truncatedTool.padEnd(maxToolLength);
298
+
299
+ const content = ` ${toolName} ${bar.padEnd(20)} ${count.toString().padStart(3)} (${percentage.padStart(4)}%)`;
300
+ const padding = Math.max(0, width - content.length - 1);
301
+ console.log(`│${content}${' '.repeat(padding)} │`);
263
302
  }
264
- console.log("└────────────────────────────────────────────────────────┘")
303
+ console.log('└────────────────────────────────────────────────────────┘');
265
304
  }
266
- console.log()
305
+ console.log();
267
306
  }
268
307
 
269
308
  function formatNumber(num: number): string {
270
309
  if (num >= 1000000) {
271
- return (num / 1000000).toFixed(1) + "M"
310
+ return (num / 1000000).toFixed(1) + 'M';
272
311
  } else if (num >= 1000) {
273
- return (num / 1000).toFixed(1) + "K"
312
+ return (num / 1000).toFixed(1) + 'K';
274
313
  }
275
- return num.toString()
314
+ return num.toString();
276
315
  }
package/src/cli/error.ts CHANGED
@@ -1,27 +1,31 @@
1
- import { ConfigMarkdown } from "../config/markdown"
2
- import { Config } from "../config/config"
3
- import { MCP } from "../mcp"
4
- import { UI } from "./ui"
1
+ import { ConfigMarkdown } from '../config/markdown';
2
+ import { Config } from '../config/config';
3
+ import { MCP } from '../mcp';
4
+ import { UI } from './ui';
5
5
 
6
6
  export function FormatError(input: unknown) {
7
7
  if (MCP.Failed.isInstance(input))
8
- return `MCP server "${input.data.name}" failed. Note, opencode does not support MCP authentication yet.`
8
+ return `MCP server "${input.data.name}" failed. Note, opencode does not support MCP authentication yet.`;
9
9
  if (Config.JsonError.isInstance(input)) {
10
10
  return (
11
- `Config file at ${input.data.path} is not valid JSON(C)` + (input.data.message ? `: ${input.data.message}` : "")
12
- )
11
+ `Config file at ${input.data.path} is not valid JSON(C)` +
12
+ (input.data.message ? `: ${input.data.message}` : '')
13
+ );
13
14
  }
14
15
  if (Config.ConfigDirectoryTypoError.isInstance(input)) {
15
- return `Directory "${input.data.dir}" in ${input.data.path} is not valid. Use "${input.data.suggestion}" instead. This is a common typo.`
16
+ return `Directory "${input.data.dir}" in ${input.data.path} is not valid. Use "${input.data.suggestion}" instead. This is a common typo.`;
16
17
  }
17
18
  if (ConfigMarkdown.FrontmatterError.isInstance(input)) {
18
- return `Failed to parse frontmatter in ${input.data.path}:\n${input.data.message}`
19
+ return `Failed to parse frontmatter in ${input.data.path}:\n${input.data.message}`;
19
20
  }
20
21
  if (Config.InvalidError.isInstance(input))
21
22
  return [
22
- `Config file at ${input.data.path} is invalid` + (input.data.message ? `: ${input.data.message}` : ""),
23
- ...(input.data.issues?.map((issue) => "↳ " + issue.message + " " + issue.path.join(".")) ?? []),
24
- ].join("\n")
23
+ `Config file at ${input.data.path} is invalid` +
24
+ (input.data.message ? `: ${input.data.message}` : ''),
25
+ ...(input.data.issues?.map(
26
+ (issue) => '↳ ' + issue.message + ' ' + issue.path.join('.')
27
+ ) ?? []),
28
+ ].join('\n');
25
29
 
26
- if (UI.CancelledError.isInstance(input)) return ""
30
+ if (UI.CancelledError.isInstance(input)) return '';
27
31
  }
package/src/cli/ui.ts CHANGED
@@ -1,63 +1,78 @@
1
- import { NamedError } from "../util/error"
2
- import z from "zod"
1
+ import { NamedError } from '../util/error';
2
+ import z from 'zod';
3
3
 
4
4
  export namespace UI {
5
5
  // ANSI color codes for terminal output
6
6
  export const Style = {
7
- TEXT_NORMAL: "\x1b[0m",
8
- TEXT_BOLD: "\x1b[1m",
9
- TEXT_DIM: "\x1b[2m",
10
- TEXT_DANGER_BOLD: "\x1b[1;31m",
11
- TEXT_SUCCESS_BOLD: "\x1b[1;32m",
12
- TEXT_WARNING_BOLD: "\x1b[1;33m",
13
- TEXT_INFO_BOLD: "\x1b[1;34m",
14
- TEXT_HIGHLIGHT_BOLD: "\x1b[1;35m",
15
- TEXT_DIM_BOLD: "\x1b[1;90m",
16
- } as const
7
+ TEXT_NORMAL: '\x1b[0m',
8
+ TEXT_BOLD: '\x1b[1m',
9
+ TEXT_DIM: '\x1b[2m',
10
+ TEXT_DANGER_BOLD: '\x1b[1;31m',
11
+ TEXT_SUCCESS_BOLD: '\x1b[1;32m',
12
+ TEXT_WARNING_BOLD: '\x1b[1;33m',
13
+ TEXT_INFO_BOLD: '\x1b[1;34m',
14
+ TEXT_HIGHLIGHT_BOLD: '\x1b[1;35m',
15
+ TEXT_DIM_BOLD: '\x1b[1;90m',
16
+ } as const;
17
17
 
18
18
  // Error for cancelled operations (e.g., Ctrl+C in prompts)
19
19
  export const CancelledError = NamedError.create(
20
- "CancelledError",
20
+ 'CancelledError',
21
21
  z.object({})
22
- )
22
+ );
23
23
 
24
24
  // Print an empty line
25
25
  export function empty() {
26
- process.stderr.write("\n")
26
+ process.stderr.write('\n');
27
27
  }
28
28
 
29
29
  // Print a line with optional formatting
30
30
  export function println(...args: string[]) {
31
- process.stderr.write(args.join("") + Style.TEXT_NORMAL + "\n")
31
+ process.stderr.write(args.join('') + Style.TEXT_NORMAL + '\n');
32
32
  }
33
33
 
34
34
  // Print an error message
35
35
  export function error(message: string) {
36
- process.stderr.write(Style.TEXT_DANGER_BOLD + "Error: " + Style.TEXT_NORMAL + message + "\n")
36
+ process.stderr.write(
37
+ Style.TEXT_DANGER_BOLD + 'Error: ' + Style.TEXT_NORMAL + message + '\n'
38
+ );
37
39
  }
38
40
 
39
41
  // Print a success message
40
42
  export function success(message: string) {
41
- process.stderr.write(Style.TEXT_SUCCESS_BOLD + "Success: " + Style.TEXT_NORMAL + message + "\n")
43
+ process.stderr.write(
44
+ Style.TEXT_SUCCESS_BOLD + 'Success: ' + Style.TEXT_NORMAL + message + '\n'
45
+ );
42
46
  }
43
47
 
44
48
  // Print an info message
45
49
  export function info(message: string) {
46
- process.stderr.write(Style.TEXT_INFO_BOLD + "Info: " + Style.TEXT_NORMAL + message + "\n")
50
+ process.stderr.write(
51
+ Style.TEXT_INFO_BOLD + 'Info: ' + Style.TEXT_NORMAL + message + '\n'
52
+ );
47
53
  }
48
54
 
49
55
  // Basic markdown rendering for terminal
50
56
  export function markdown(text: string): string {
51
57
  // Simple markdown to ANSI conversion
52
- let result = text
58
+ let result = text;
53
59
 
54
60
  // Bold text: **text** or __text__
55
- result = result.replace(/\*\*(.+?)\*\*/g, Style.TEXT_BOLD + "$1" + Style.TEXT_NORMAL)
56
- result = result.replace(/__(.+?)__/g, Style.TEXT_BOLD + "$1" + Style.TEXT_NORMAL)
61
+ result = result.replace(
62
+ /\*\*(.+?)\*\*/g,
63
+ Style.TEXT_BOLD + '$1' + Style.TEXT_NORMAL
64
+ );
65
+ result = result.replace(
66
+ /__(.+?)__/g,
67
+ Style.TEXT_BOLD + '$1' + Style.TEXT_NORMAL
68
+ );
57
69
 
58
70
  // Code blocks: `code`
59
- result = result.replace(/`([^`]+)`/g, Style.TEXT_DIM + "$1" + Style.TEXT_NORMAL)
71
+ result = result.replace(
72
+ /`([^`]+)`/g,
73
+ Style.TEXT_DIM + '$1' + Style.TEXT_NORMAL
74
+ );
60
75
 
61
- return result
76
+ return result;
62
77
  }
63
78
  }