@link-assistant/agent 0.0.8 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/EXAMPLES.md +80 -1
- package/MODELS.md +72 -24
- package/README.md +95 -2
- package/TOOLS.md +20 -0
- package/package.json +36 -2
- package/src/agent/agent.ts +68 -54
- package/src/auth/claude-oauth.ts +426 -0
- package/src/auth/index.ts +28 -26
- package/src/auth/plugins.ts +876 -0
- package/src/bun/index.ts +53 -43
- package/src/bus/global.ts +5 -5
- package/src/bus/index.ts +59 -53
- package/src/cli/bootstrap.js +12 -12
- package/src/cli/bootstrap.ts +6 -6
- package/src/cli/cmd/agent.ts +97 -92
- package/src/cli/cmd/auth.ts +468 -0
- package/src/cli/cmd/cmd.ts +2 -2
- package/src/cli/cmd/export.ts +41 -41
- package/src/cli/cmd/mcp.ts +210 -53
- package/src/cli/cmd/models.ts +30 -29
- package/src/cli/cmd/run.ts +269 -213
- package/src/cli/cmd/stats.ts +185 -146
- package/src/cli/error.ts +17 -13
- package/src/cli/ui.ts +78 -0
- package/src/command/index.ts +26 -26
- package/src/config/config.ts +528 -288
- package/src/config/markdown.ts +15 -15
- package/src/file/ripgrep.ts +201 -169
- package/src/file/time.ts +21 -18
- package/src/file/watcher.ts +51 -42
- package/src/file.ts +1 -1
- package/src/flag/flag.ts +26 -11
- package/src/format/formatter.ts +206 -162
- package/src/format/index.ts +61 -61
- package/src/global/index.ts +21 -21
- package/src/id/id.ts +47 -33
- package/src/index.js +554 -332
- package/src/json-standard/index.ts +173 -0
- package/src/mcp/index.ts +135 -128
- package/src/patch/index.ts +336 -267
- package/src/project/bootstrap.ts +15 -15
- package/src/project/instance.ts +43 -36
- package/src/project/project.ts +47 -47
- package/src/project/state.ts +37 -33
- package/src/provider/models-macro.ts +5 -5
- package/src/provider/models.ts +32 -32
- package/src/provider/opencode.js +19 -19
- package/src/provider/provider.ts +518 -277
- package/src/provider/transform.ts +143 -102
- package/src/server/project.ts +21 -21
- package/src/server/server.ts +111 -105
- package/src/session/agent.js +66 -60
- package/src/session/compaction.ts +136 -111
- package/src/session/index.ts +189 -156
- package/src/session/message-v2.ts +312 -268
- package/src/session/message.ts +73 -57
- package/src/session/processor.ts +180 -166
- package/src/session/prompt.ts +678 -533
- package/src/session/retry.ts +26 -23
- package/src/session/revert.ts +76 -62
- package/src/session/status.ts +26 -26
- package/src/session/summary.ts +97 -76
- package/src/session/system.ts +77 -63
- package/src/session/todo.ts +22 -16
- package/src/snapshot/index.ts +92 -76
- package/src/storage/storage.ts +157 -120
- package/src/tool/bash.ts +116 -106
- package/src/tool/batch.ts +73 -59
- package/src/tool/codesearch.ts +60 -53
- package/src/tool/edit.ts +319 -263
- package/src/tool/glob.ts +32 -28
- package/src/tool/grep.ts +72 -53
- package/src/tool/invalid.ts +7 -7
- package/src/tool/ls.ts +77 -64
- package/src/tool/multiedit.ts +30 -21
- package/src/tool/patch.ts +121 -94
- package/src/tool/read.ts +140 -122
- package/src/tool/registry.ts +38 -38
- package/src/tool/task.ts +93 -60
- package/src/tool/todo.ts +16 -16
- package/src/tool/tool.ts +45 -36
- package/src/tool/webfetch.ts +97 -74
- package/src/tool/websearch.ts +78 -64
- package/src/tool/write.ts +21 -15
- package/src/util/binary.ts +27 -19
- package/src/util/context.ts +8 -8
- package/src/util/defer.ts +7 -5
- package/src/util/error.ts +24 -19
- package/src/util/eventloop.ts +16 -10
- package/src/util/filesystem.ts +37 -33
- package/src/util/fn.ts +11 -8
- package/src/util/iife.ts +1 -1
- package/src/util/keybind.ts +44 -44
- package/src/util/lazy.ts +7 -7
- package/src/util/locale.ts +20 -16
- package/src/util/lock.ts +43 -38
- package/src/util/log.ts +95 -85
- package/src/util/queue.ts +8 -8
- package/src/util/rpc.ts +35 -23
- package/src/util/scrap.ts +4 -4
- package/src/util/signal.ts +5 -5
- package/src/util/timeout.ts +6 -6
- package/src/util/token.ts +2 -2
- package/src/util/wildcard.ts +38 -27
package/src/cli/cmd/mcp.ts
CHANGED
|
@@ -1,80 +1,237 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
1
|
+
import type { Argv } from 'yargs';
|
|
2
|
+
import { cmd } from './cmd';
|
|
3
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
4
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
5
|
+
import * as prompts from '@clack/prompts';
|
|
6
|
+
import { UI } from '../ui';
|
|
7
|
+
import { Global } from '../../global';
|
|
8
|
+
import { Config } from '../../config/config';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import fs from 'fs/promises';
|
|
6
11
|
|
|
7
12
|
export const McpCommand = cmd({
|
|
8
|
-
command:
|
|
9
|
-
builder: (yargs) =>
|
|
13
|
+
command: 'mcp',
|
|
14
|
+
builder: (yargs) =>
|
|
15
|
+
yargs.command(McpAddCommand).command(McpListCommand).demandCommand(),
|
|
10
16
|
async handler() {},
|
|
11
|
-
})
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
async function loadGlobalConfig(): Promise<Config.Info> {
|
|
20
|
+
const configPath = path.join(Global.Path.config, 'opencode.json');
|
|
21
|
+
try {
|
|
22
|
+
const content = await Bun.file(configPath).text();
|
|
23
|
+
return JSON.parse(content);
|
|
24
|
+
} catch {
|
|
25
|
+
return {
|
|
26
|
+
$schema: 'https://opencode.ai/config.json',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function saveGlobalConfig(config: Config.Info): Promise<void> {
|
|
32
|
+
const configPath = path.join(Global.Path.config, 'opencode.json');
|
|
33
|
+
await fs.mkdir(Global.Path.config, { recursive: true });
|
|
34
|
+
await Bun.write(configPath, JSON.stringify(config, null, 2));
|
|
35
|
+
}
|
|
12
36
|
|
|
13
37
|
export const McpAddCommand = cmd({
|
|
14
|
-
command:
|
|
15
|
-
describe:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
38
|
+
command: 'add [name] [command..]',
|
|
39
|
+
describe: 'add an MCP server',
|
|
40
|
+
builder: (yargs: Argv) => {
|
|
41
|
+
return yargs
|
|
42
|
+
.positional('name', {
|
|
43
|
+
describe: 'name of the MCP server',
|
|
44
|
+
type: 'string',
|
|
45
|
+
})
|
|
46
|
+
.positional('command', {
|
|
47
|
+
describe:
|
|
48
|
+
'command and arguments to run the MCP server (e.g., npx @playwright/mcp@latest)',
|
|
49
|
+
type: 'string',
|
|
50
|
+
array: true,
|
|
51
|
+
})
|
|
52
|
+
.option('url', {
|
|
53
|
+
describe: 'URL for remote MCP server',
|
|
54
|
+
type: 'string',
|
|
55
|
+
})
|
|
56
|
+
.option('enabled', {
|
|
57
|
+
describe: 'enable the MCP server',
|
|
58
|
+
type: 'boolean',
|
|
59
|
+
default: true,
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
async handler(args) {
|
|
63
|
+
// If name and command are provided as CLI arguments, use non-interactive mode
|
|
64
|
+
if (args.name && ((args.command && args.command.length > 0) || args.url)) {
|
|
65
|
+
// Non-interactive mode: CLI arguments provided
|
|
66
|
+
const config = await loadGlobalConfig();
|
|
67
|
+
config.mcp = config.mcp || {};
|
|
68
|
+
|
|
69
|
+
if (args.url) {
|
|
70
|
+
// Remote MCP server
|
|
71
|
+
config.mcp[args.name] = {
|
|
72
|
+
type: 'remote',
|
|
73
|
+
url: args.url,
|
|
74
|
+
enabled: args.enabled,
|
|
75
|
+
};
|
|
76
|
+
UI.success(
|
|
77
|
+
`Remote MCP server "${args.name}" added with URL: ${args.url}`
|
|
78
|
+
);
|
|
79
|
+
} else if (args.command && args.command.length > 0) {
|
|
80
|
+
// Local MCP server
|
|
81
|
+
config.mcp[args.name] = {
|
|
82
|
+
type: 'local',
|
|
83
|
+
command: args.command,
|
|
84
|
+
enabled: args.enabled,
|
|
85
|
+
};
|
|
86
|
+
UI.success(
|
|
87
|
+
`Local MCP server "${args.name}" added with command: ${args.command.join(' ')}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await saveGlobalConfig(config);
|
|
92
|
+
const configPath = path.join(Global.Path.config, 'opencode.json');
|
|
93
|
+
UI.info(`Configuration saved to: ${configPath}`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Interactive mode: prompt for input
|
|
98
|
+
UI.empty();
|
|
99
|
+
prompts.intro('Add MCP server');
|
|
19
100
|
|
|
20
101
|
const name = await prompts.text({
|
|
21
|
-
message:
|
|
22
|
-
validate: (x) => (x && x.length > 0 ? undefined :
|
|
23
|
-
})
|
|
24
|
-
if (prompts.isCancel(name)) throw new UI.CancelledError()
|
|
102
|
+
message: 'Enter MCP server name',
|
|
103
|
+
validate: (x) => (x && x.length > 0 ? undefined : 'Required'),
|
|
104
|
+
});
|
|
105
|
+
if (prompts.isCancel(name)) throw new UI.CancelledError();
|
|
25
106
|
|
|
26
107
|
const type = await prompts.select({
|
|
27
|
-
message:
|
|
108
|
+
message: 'Select MCP server type',
|
|
28
109
|
options: [
|
|
29
110
|
{
|
|
30
|
-
label:
|
|
31
|
-
value:
|
|
32
|
-
hint:
|
|
111
|
+
label: 'Local',
|
|
112
|
+
value: 'local',
|
|
113
|
+
hint: 'Run a local command',
|
|
33
114
|
},
|
|
34
115
|
{
|
|
35
|
-
label:
|
|
36
|
-
value:
|
|
37
|
-
hint:
|
|
116
|
+
label: 'Remote',
|
|
117
|
+
value: 'remote',
|
|
118
|
+
hint: 'Connect to a remote URL',
|
|
38
119
|
},
|
|
39
120
|
],
|
|
40
|
-
})
|
|
41
|
-
if (prompts.isCancel(type)) throw new UI.CancelledError()
|
|
121
|
+
});
|
|
122
|
+
if (prompts.isCancel(type)) throw new UI.CancelledError();
|
|
42
123
|
|
|
43
|
-
|
|
124
|
+
const config = await loadGlobalConfig();
|
|
125
|
+
config.mcp = config.mcp || {};
|
|
126
|
+
|
|
127
|
+
if (type === 'local') {
|
|
44
128
|
const command = await prompts.text({
|
|
45
|
-
message:
|
|
46
|
-
placeholder:
|
|
47
|
-
validate: (x) => (x && x.length > 0 ? undefined :
|
|
48
|
-
})
|
|
49
|
-
if (prompts.isCancel(command)) throw new UI.CancelledError()
|
|
129
|
+
message: 'Enter command to run',
|
|
130
|
+
placeholder: 'e.g., npx @playwright/mcp@latest',
|
|
131
|
+
validate: (x) => (x && x.length > 0 ? undefined : 'Required'),
|
|
132
|
+
});
|
|
133
|
+
if (prompts.isCancel(command)) throw new UI.CancelledError();
|
|
134
|
+
|
|
135
|
+
// Parse command into array
|
|
136
|
+
const commandParts = command.split(/\s+/);
|
|
137
|
+
config.mcp[name] = {
|
|
138
|
+
type: 'local',
|
|
139
|
+
command: commandParts,
|
|
140
|
+
enabled: true,
|
|
141
|
+
};
|
|
50
142
|
|
|
51
|
-
|
|
52
|
-
prompts.
|
|
53
|
-
|
|
143
|
+
await saveGlobalConfig(config);
|
|
144
|
+
prompts.log.info(
|
|
145
|
+
`Local MCP server "${name}" configured with command: ${command}`
|
|
146
|
+
);
|
|
54
147
|
}
|
|
55
148
|
|
|
56
|
-
if (type ===
|
|
149
|
+
if (type === 'remote') {
|
|
57
150
|
const url = await prompts.text({
|
|
58
|
-
message:
|
|
59
|
-
placeholder:
|
|
151
|
+
message: 'Enter MCP server URL',
|
|
152
|
+
placeholder: 'e.g., https://example.com/mcp',
|
|
60
153
|
validate: (x) => {
|
|
61
|
-
if (!x) return
|
|
62
|
-
if (x.length === 0) return
|
|
63
|
-
const isValid = URL.canParse(x)
|
|
64
|
-
return isValid ? undefined :
|
|
154
|
+
if (!x) return 'Required';
|
|
155
|
+
if (x.length === 0) return 'Required';
|
|
156
|
+
const isValid = URL.canParse(x);
|
|
157
|
+
return isValid ? undefined : 'Invalid URL';
|
|
65
158
|
},
|
|
66
|
-
})
|
|
67
|
-
if (prompts.isCancel(url)) throw new UI.CancelledError()
|
|
159
|
+
});
|
|
160
|
+
if (prompts.isCancel(url)) throw new UI.CancelledError();
|
|
68
161
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
162
|
+
// Test connection
|
|
163
|
+
try {
|
|
164
|
+
const client = new Client({
|
|
165
|
+
name: 'opencode',
|
|
166
|
+
version: '1.0.0',
|
|
167
|
+
});
|
|
168
|
+
const transport = new StreamableHTTPClientTransport(new URL(url));
|
|
169
|
+
await client.connect(transport);
|
|
170
|
+
await client.close();
|
|
171
|
+
} catch (error) {
|
|
172
|
+
prompts.log.warn(
|
|
173
|
+
`Could not verify connection to ${url}, but saving configuration anyway`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
config.mcp[name] = {
|
|
178
|
+
type: 'remote',
|
|
179
|
+
url: url,
|
|
180
|
+
enabled: true,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
await saveGlobalConfig(config);
|
|
184
|
+
prompts.log.info(
|
|
185
|
+
`Remote MCP server "${name}" configured with URL: ${url}`
|
|
186
|
+
);
|
|
76
187
|
}
|
|
77
188
|
|
|
78
|
-
|
|
189
|
+
const configPath = path.join(Global.Path.config, 'opencode.json');
|
|
190
|
+
prompts.log.info(`Configuration saved to: ${configPath}`);
|
|
191
|
+
prompts.outro('MCP server added successfully');
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
export const McpListCommand = cmd({
|
|
196
|
+
command: 'list',
|
|
197
|
+
describe: 'list configured MCP servers',
|
|
198
|
+
async handler() {
|
|
199
|
+
const config = await loadGlobalConfig();
|
|
200
|
+
const mcpServers = config.mcp || {};
|
|
201
|
+
|
|
202
|
+
if (Object.keys(mcpServers).length === 0) {
|
|
203
|
+
UI.info('No MCP servers configured');
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
UI.println(
|
|
208
|
+
UI.Style.TEXT_BOLD + 'Configured MCP servers:' + UI.Style.TEXT_NORMAL
|
|
209
|
+
);
|
|
210
|
+
UI.empty();
|
|
211
|
+
|
|
212
|
+
for (const [name, server] of Object.entries(mcpServers)) {
|
|
213
|
+
const enabledStatus =
|
|
214
|
+
server.enabled !== false
|
|
215
|
+
? UI.Style.TEXT_SUCCESS_BOLD + '[enabled]'
|
|
216
|
+
: UI.Style.TEXT_DIM + '[disabled]';
|
|
217
|
+
UI.println(
|
|
218
|
+
UI.Style.TEXT_INFO_BOLD +
|
|
219
|
+
` ${name}` +
|
|
220
|
+
UI.Style.TEXT_NORMAL +
|
|
221
|
+
` ${enabledStatus}` +
|
|
222
|
+
UI.Style.TEXT_NORMAL
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
if (server.type === 'local') {
|
|
226
|
+
UI.println(UI.Style.TEXT_DIM + ` Type: local`);
|
|
227
|
+
UI.println(
|
|
228
|
+
UI.Style.TEXT_DIM + ` Command: ${server.command.join(' ')}`
|
|
229
|
+
);
|
|
230
|
+
} else if (server.type === 'remote') {
|
|
231
|
+
UI.println(UI.Style.TEXT_DIM + ` Type: remote`);
|
|
232
|
+
UI.println(UI.Style.TEXT_DIM + ` URL: ${server.url}`);
|
|
233
|
+
}
|
|
234
|
+
UI.empty();
|
|
235
|
+
}
|
|
79
236
|
},
|
|
80
|
-
})
|
|
237
|
+
});
|
package/src/cli/cmd/models.ts
CHANGED
|
@@ -1,58 +1,59 @@
|
|
|
1
|
-
import type { Argv } from
|
|
2
|
-
import { Instance } from
|
|
3
|
-
import { Provider } from
|
|
4
|
-
import { cmd } from
|
|
5
|
-
import { UI } from
|
|
6
|
-
import { EOL } from
|
|
1
|
+
import type { Argv } from 'yargs';
|
|
2
|
+
import { Instance } from '../../project/instance';
|
|
3
|
+
import { Provider } from '../../provider/provider';
|
|
4
|
+
import { cmd } from './cmd';
|
|
5
|
+
import { UI } from '../ui';
|
|
6
|
+
import { EOL } from 'os';
|
|
7
7
|
|
|
8
8
|
export const ModelsCommand = cmd({
|
|
9
|
-
command:
|
|
10
|
-
describe:
|
|
9
|
+
command: 'models [provider]',
|
|
10
|
+
describe: 'list all available models',
|
|
11
11
|
builder: (yargs: Argv) => {
|
|
12
12
|
return yargs
|
|
13
|
-
.positional(
|
|
14
|
-
describe:
|
|
15
|
-
type:
|
|
13
|
+
.positional('provider', {
|
|
14
|
+
describe: 'provider ID to filter models by',
|
|
15
|
+
type: 'string',
|
|
16
16
|
array: false,
|
|
17
17
|
})
|
|
18
|
-
.option(
|
|
19
|
-
describe:
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
.option('verbose', {
|
|
19
|
+
describe:
|
|
20
|
+
'use more verbose model output (includes metadata like costs)',
|
|
21
|
+
type: 'boolean',
|
|
22
|
+
});
|
|
22
23
|
},
|
|
23
24
|
handler: async (args) => {
|
|
24
25
|
await Instance.provide({
|
|
25
26
|
directory: process.cwd(),
|
|
26
27
|
async fn() {
|
|
27
|
-
const providers = await Provider.list()
|
|
28
|
+
const providers = await Provider.list();
|
|
28
29
|
|
|
29
30
|
function printModels(providerID: string, verbose?: boolean) {
|
|
30
|
-
const provider = providers[providerID]
|
|
31
|
+
const provider = providers[providerID];
|
|
31
32
|
for (const [modelID, model] of Object.entries(provider.info.models)) {
|
|
32
|
-
process.stdout.write(`${providerID}/${modelID}`)
|
|
33
|
-
process.stdout.write(EOL)
|
|
33
|
+
process.stdout.write(`${providerID}/${modelID}`);
|
|
34
|
+
process.stdout.write(EOL);
|
|
34
35
|
if (verbose) {
|
|
35
|
-
process.stdout.write(JSON.stringify(model, null, 2))
|
|
36
|
-
process.stdout.write(EOL)
|
|
36
|
+
process.stdout.write(JSON.stringify(model, null, 2));
|
|
37
|
+
process.stdout.write(EOL);
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
if (args.provider) {
|
|
42
|
-
const provider = providers[args.provider]
|
|
43
|
+
const provider = providers[args.provider];
|
|
43
44
|
if (!provider) {
|
|
44
|
-
UI.error(`Provider not found: ${args.provider}`)
|
|
45
|
-
return
|
|
45
|
+
UI.error(`Provider not found: ${args.provider}`);
|
|
46
|
+
return;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
printModels(args.provider, args.verbose)
|
|
49
|
-
return
|
|
49
|
+
printModels(args.provider, args.verbose);
|
|
50
|
+
return;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
for (const providerID of Object.keys(providers)) {
|
|
53
|
-
printModels(providerID, args.verbose)
|
|
54
|
+
printModels(providerID, args.verbose);
|
|
54
55
|
}
|
|
55
56
|
},
|
|
56
|
-
})
|
|
57
|
+
});
|
|
57
58
|
},
|
|
58
|
-
})
|
|
59
|
+
});
|