@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
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import type { Argv } from 'yargs';
|
|
2
|
+
import { cmd } from './cmd';
|
|
3
|
+
import { UI } from '../ui';
|
|
4
|
+
import { Auth } from '../../auth';
|
|
5
|
+
import { AuthPlugins } from '../../auth/plugins';
|
|
6
|
+
import { ModelsDev } from '../../provider/models';
|
|
7
|
+
import * as prompts from '@clack/prompts';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import { Global } from '../../global';
|
|
11
|
+
import { map, pipe, sortBy, values } from 'remeda';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Auth Command
|
|
15
|
+
*
|
|
16
|
+
* Manages authentication for various providers.
|
|
17
|
+
* Based on OpenCode's auth implementation supporting:
|
|
18
|
+
* - OAuth providers (Anthropic Claude Pro/Max, GitHub Copilot)
|
|
19
|
+
* - API key providers (OpenAI, Anthropic API, etc.)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export const AuthListCommand = cmd({
|
|
23
|
+
command: 'list',
|
|
24
|
+
aliases: ['ls'],
|
|
25
|
+
describe: 'list configured credentials',
|
|
26
|
+
async handler() {
|
|
27
|
+
UI.empty();
|
|
28
|
+
const authPath = path.join(Global.Path.data, 'auth.json');
|
|
29
|
+
const homedir = os.homedir();
|
|
30
|
+
const displayPath = authPath.startsWith(homedir)
|
|
31
|
+
? authPath.replace(homedir, '~')
|
|
32
|
+
: authPath;
|
|
33
|
+
prompts.intro(`Credentials ${UI.Style.TEXT_DIM}${displayPath}`);
|
|
34
|
+
const results = await Auth.all().then((x) => Object.entries(x));
|
|
35
|
+
const database = await ModelsDev.get();
|
|
36
|
+
|
|
37
|
+
for (const [providerID, result] of results) {
|
|
38
|
+
const name = database[providerID]?.name || providerID;
|
|
39
|
+
prompts.log.info(`${name} ${UI.Style.TEXT_DIM}${result.type}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
prompts.outro(`${results.length} credentials`);
|
|
43
|
+
|
|
44
|
+
// Environment variables section
|
|
45
|
+
const activeEnvVars: Array<{ provider: string; envVar: string }> = [];
|
|
46
|
+
|
|
47
|
+
for (const [providerID, provider] of Object.entries(database)) {
|
|
48
|
+
for (const envVar of provider.env) {
|
|
49
|
+
if (process.env[envVar]) {
|
|
50
|
+
activeEnvVars.push({
|
|
51
|
+
provider: provider.name || providerID,
|
|
52
|
+
envVar,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (activeEnvVars.length > 0) {
|
|
59
|
+
UI.empty();
|
|
60
|
+
prompts.intro('Environment');
|
|
61
|
+
|
|
62
|
+
for (const { provider, envVar } of activeEnvVars) {
|
|
63
|
+
prompts.log.info(`${provider} ${UI.Style.TEXT_DIM}${envVar}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
prompts.outro(
|
|
67
|
+
`${activeEnvVars.length} environment variable` +
|
|
68
|
+
(activeEnvVars.length === 1 ? '' : 's')
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export const AuthLoginCommand = cmd({
|
|
75
|
+
command: 'login [url]',
|
|
76
|
+
describe: 'log in to a provider',
|
|
77
|
+
builder: (yargs: Argv) =>
|
|
78
|
+
yargs.positional('url', {
|
|
79
|
+
describe: 'wellknown auth provider URL',
|
|
80
|
+
type: 'string',
|
|
81
|
+
}),
|
|
82
|
+
async handler(args) {
|
|
83
|
+
UI.empty();
|
|
84
|
+
prompts.intro('Add credential');
|
|
85
|
+
|
|
86
|
+
// Handle wellknown URL login
|
|
87
|
+
if (args.url) {
|
|
88
|
+
try {
|
|
89
|
+
const wellknown = await fetch(`${args.url}/.well-known/opencode`).then(
|
|
90
|
+
(x) => x.json() as any
|
|
91
|
+
);
|
|
92
|
+
prompts.log.info(`Running \`${wellknown.auth.command.join(' ')}\``);
|
|
93
|
+
const proc = Bun.spawn({
|
|
94
|
+
cmd: wellknown.auth.command,
|
|
95
|
+
stdout: 'pipe',
|
|
96
|
+
});
|
|
97
|
+
const exit = await proc.exited;
|
|
98
|
+
if (exit !== 0) {
|
|
99
|
+
prompts.log.error('Failed');
|
|
100
|
+
prompts.outro('Done');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const token = await new Response(proc.stdout).text();
|
|
104
|
+
await Auth.set(args.url as string, {
|
|
105
|
+
type: 'wellknown',
|
|
106
|
+
key: wellknown.auth.env,
|
|
107
|
+
token: token.trim(),
|
|
108
|
+
});
|
|
109
|
+
prompts.log.success('Logged into ' + args.url);
|
|
110
|
+
prompts.outro('Done');
|
|
111
|
+
return;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
prompts.log.error(
|
|
114
|
+
`Failed to fetch wellknown from ${args.url}: ${error}`
|
|
115
|
+
);
|
|
116
|
+
prompts.outro('Failed');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Refresh models database
|
|
122
|
+
await ModelsDev.refresh().catch(() => {});
|
|
123
|
+
const providers = await ModelsDev.get();
|
|
124
|
+
|
|
125
|
+
// Provider priority (lower = higher priority)
|
|
126
|
+
const priority: Record<string, number> = {
|
|
127
|
+
anthropic: 0,
|
|
128
|
+
'github-copilot': 1,
|
|
129
|
+
openai: 2,
|
|
130
|
+
google: 3,
|
|
131
|
+
openrouter: 4,
|
|
132
|
+
vercel: 5,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
let provider = await prompts.autocomplete({
|
|
136
|
+
message: 'Select provider',
|
|
137
|
+
maxItems: 8,
|
|
138
|
+
options: [
|
|
139
|
+
...pipe(
|
|
140
|
+
providers,
|
|
141
|
+
values(),
|
|
142
|
+
sortBy(
|
|
143
|
+
(x) => priority[x.id] ?? 99,
|
|
144
|
+
(x) => x.name ?? x.id
|
|
145
|
+
),
|
|
146
|
+
map((x) => ({
|
|
147
|
+
label: x.name,
|
|
148
|
+
value: x.id,
|
|
149
|
+
hint: priority[x.id] === 0 ? 'recommended' : undefined,
|
|
150
|
+
}))
|
|
151
|
+
),
|
|
152
|
+
{
|
|
153
|
+
value: 'other',
|
|
154
|
+
label: 'Other',
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (prompts.isCancel(provider)) throw new UI.CancelledError();
|
|
160
|
+
|
|
161
|
+
// Check if there's a plugin for this provider
|
|
162
|
+
const plugin = AuthPlugins.getPlugin(provider);
|
|
163
|
+
|
|
164
|
+
if (plugin && plugin.methods.length > 0) {
|
|
165
|
+
// Select login method if multiple available
|
|
166
|
+
let methodIndex = 0;
|
|
167
|
+
if (plugin.methods.length > 1) {
|
|
168
|
+
const method = await prompts.select({
|
|
169
|
+
message: 'Login method',
|
|
170
|
+
options: plugin.methods.map((m, index) => ({
|
|
171
|
+
label: m.label,
|
|
172
|
+
value: index.toString(),
|
|
173
|
+
})),
|
|
174
|
+
});
|
|
175
|
+
if (prompts.isCancel(method)) throw new UI.CancelledError();
|
|
176
|
+
methodIndex = parseInt(method);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const method = plugin.methods[methodIndex];
|
|
180
|
+
|
|
181
|
+
// Handle prompts for auth method
|
|
182
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
183
|
+
const inputs: Record<string, string> = {};
|
|
184
|
+
|
|
185
|
+
if (method.prompts) {
|
|
186
|
+
for (const prompt of method.prompts) {
|
|
187
|
+
if (prompt.condition && !prompt.condition(inputs)) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (prompt.type === 'select') {
|
|
191
|
+
const value = await prompts.select({
|
|
192
|
+
message: prompt.message,
|
|
193
|
+
options: prompt.options!,
|
|
194
|
+
});
|
|
195
|
+
if (prompts.isCancel(value)) throw new UI.CancelledError();
|
|
196
|
+
inputs[prompt.key] = value;
|
|
197
|
+
} else {
|
|
198
|
+
const value = await prompts.text({
|
|
199
|
+
message: prompt.message,
|
|
200
|
+
placeholder: prompt.placeholder,
|
|
201
|
+
validate: prompt.validate
|
|
202
|
+
? (v) => prompt.validate!(v ?? '')
|
|
203
|
+
: undefined,
|
|
204
|
+
});
|
|
205
|
+
if (prompts.isCancel(value)) throw new UI.CancelledError();
|
|
206
|
+
inputs[prompt.key] = value;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (method.type === 'oauth') {
|
|
212
|
+
const authorize = await method.authorize!(inputs);
|
|
213
|
+
|
|
214
|
+
if (authorize.url) {
|
|
215
|
+
prompts.log.info('Go to: ' + authorize.url);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (authorize.method === 'auto') {
|
|
219
|
+
if (authorize.instructions) {
|
|
220
|
+
prompts.log.info(authorize.instructions);
|
|
221
|
+
}
|
|
222
|
+
const spinner = prompts.spinner();
|
|
223
|
+
spinner.start('Waiting for authorization...');
|
|
224
|
+
const result = await authorize.callback();
|
|
225
|
+
if (result.type === 'failed') {
|
|
226
|
+
spinner.stop('Failed to authorize', 1);
|
|
227
|
+
}
|
|
228
|
+
if (result.type === 'success') {
|
|
229
|
+
const saveProvider = result.provider ?? provider;
|
|
230
|
+
if ('refresh' in result) {
|
|
231
|
+
const {
|
|
232
|
+
type: _,
|
|
233
|
+
provider: __,
|
|
234
|
+
refresh,
|
|
235
|
+
access,
|
|
236
|
+
expires,
|
|
237
|
+
...extraFields
|
|
238
|
+
} = result;
|
|
239
|
+
await Auth.set(saveProvider, {
|
|
240
|
+
type: 'oauth',
|
|
241
|
+
refresh,
|
|
242
|
+
access,
|
|
243
|
+
expires,
|
|
244
|
+
...extraFields,
|
|
245
|
+
} as Auth.Info);
|
|
246
|
+
}
|
|
247
|
+
if ('key' in result) {
|
|
248
|
+
await Auth.set(saveProvider, {
|
|
249
|
+
type: 'api',
|
|
250
|
+
key: result.key,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
spinner.stop('Login successful');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (authorize.method === 'code') {
|
|
258
|
+
const code = await prompts.text({
|
|
259
|
+
message: 'Paste the authorization code here: ',
|
|
260
|
+
validate: (x) => (x && x.length > 0 ? undefined : 'Required'),
|
|
261
|
+
});
|
|
262
|
+
if (prompts.isCancel(code)) throw new UI.CancelledError();
|
|
263
|
+
const result = await authorize.callback(code);
|
|
264
|
+
if (result.type === 'failed') {
|
|
265
|
+
prompts.log.error('Failed to authorize');
|
|
266
|
+
}
|
|
267
|
+
if (result.type === 'success') {
|
|
268
|
+
const saveProvider = result.provider ?? provider;
|
|
269
|
+
if ('refresh' in result) {
|
|
270
|
+
const {
|
|
271
|
+
type: _,
|
|
272
|
+
provider: __,
|
|
273
|
+
refresh,
|
|
274
|
+
access,
|
|
275
|
+
expires,
|
|
276
|
+
...extraFields
|
|
277
|
+
} = result;
|
|
278
|
+
await Auth.set(saveProvider, {
|
|
279
|
+
type: 'oauth',
|
|
280
|
+
refresh,
|
|
281
|
+
access,
|
|
282
|
+
expires,
|
|
283
|
+
...extraFields,
|
|
284
|
+
} as Auth.Info);
|
|
285
|
+
}
|
|
286
|
+
if ('key' in result) {
|
|
287
|
+
await Auth.set(saveProvider, {
|
|
288
|
+
type: 'api',
|
|
289
|
+
key: result.key,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
prompts.log.success('Login successful');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
prompts.outro('Done');
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (method.type === 'api' && method.authorize) {
|
|
301
|
+
const result = await method.authorize(inputs);
|
|
302
|
+
if (result.type === 'failed') {
|
|
303
|
+
prompts.log.error('Failed to authorize');
|
|
304
|
+
}
|
|
305
|
+
if (result.type === 'success') {
|
|
306
|
+
const saveProvider = result.provider ?? provider;
|
|
307
|
+
await Auth.set(saveProvider, {
|
|
308
|
+
type: 'api',
|
|
309
|
+
key: result.key!,
|
|
310
|
+
});
|
|
311
|
+
prompts.log.success('Login successful');
|
|
312
|
+
}
|
|
313
|
+
prompts.outro('Done');
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Handle "other" provider
|
|
319
|
+
if (provider === 'other') {
|
|
320
|
+
const customProvider = await prompts.text({
|
|
321
|
+
message: 'Enter provider id',
|
|
322
|
+
validate: (x) =>
|
|
323
|
+
x && x.match(/^[0-9a-z-]+$/)
|
|
324
|
+
? undefined
|
|
325
|
+
: 'a-z, 0-9 and hyphens only',
|
|
326
|
+
});
|
|
327
|
+
if (prompts.isCancel(customProvider)) throw new UI.CancelledError();
|
|
328
|
+
provider = customProvider.replace(/^@ai-sdk\//, '');
|
|
329
|
+
prompts.log.warn(
|
|
330
|
+
`This only stores a credential for ${provider} - you may need to configure it in your config file.`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Amazon Bedrock uses environment variables
|
|
335
|
+
if (provider === 'amazon-bedrock') {
|
|
336
|
+
prompts.log.info(
|
|
337
|
+
'Amazon Bedrock can be configured with standard AWS environment variables like AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE or AWS_ACCESS_KEY_ID'
|
|
338
|
+
);
|
|
339
|
+
prompts.outro('Done');
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Provider-specific instructions for getting API keys
|
|
344
|
+
const apiKeyUrls: Record<string, string> = {
|
|
345
|
+
openai: 'https://platform.openai.com/api-keys',
|
|
346
|
+
anthropic: 'https://console.anthropic.com/settings/keys',
|
|
347
|
+
google: 'https://aistudio.google.com/app/apikey',
|
|
348
|
+
vercel: 'https://vercel.link/ai-gateway-token',
|
|
349
|
+
openrouter: 'https://openrouter.ai/keys',
|
|
350
|
+
groq: 'https://console.groq.com/keys',
|
|
351
|
+
mistral: 'https://console.mistral.ai/api-keys',
|
|
352
|
+
cohere: 'https://dashboard.cohere.com/api-keys',
|
|
353
|
+
perplexity: 'https://www.perplexity.ai/settings/api',
|
|
354
|
+
togetherai: 'https://api.together.xyz/settings/api-keys',
|
|
355
|
+
deepseek: 'https://platform.deepseek.com/api_keys',
|
|
356
|
+
xai: 'https://console.x.ai',
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
if (apiKeyUrls[provider]) {
|
|
360
|
+
prompts.log.info(`You can create an API key at ${apiKeyUrls[provider]}`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Fallback: prompt for API key
|
|
364
|
+
const key = await prompts.password({
|
|
365
|
+
message: 'Enter your API key',
|
|
366
|
+
validate: (x) => (x && x.length > 0 ? undefined : 'Required'),
|
|
367
|
+
});
|
|
368
|
+
if (prompts.isCancel(key)) throw new UI.CancelledError();
|
|
369
|
+
await Auth.set(provider, {
|
|
370
|
+
type: 'api',
|
|
371
|
+
key,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
prompts.outro('Done');
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
export const AuthLogoutCommand = cmd({
|
|
379
|
+
command: 'logout',
|
|
380
|
+
describe: 'log out from a configured provider',
|
|
381
|
+
async handler() {
|
|
382
|
+
UI.empty();
|
|
383
|
+
const credentials = await Auth.all().then((x) => Object.entries(x));
|
|
384
|
+
prompts.intro('Remove credential');
|
|
385
|
+
if (credentials.length === 0) {
|
|
386
|
+
prompts.log.error('No credentials found');
|
|
387
|
+
prompts.outro('Done');
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const database = await ModelsDev.get();
|
|
391
|
+
const providerID = await prompts.select({
|
|
392
|
+
message: 'Select provider',
|
|
393
|
+
options: credentials.map(([key, value]) => ({
|
|
394
|
+
label:
|
|
395
|
+
(database[key]?.name || key) +
|
|
396
|
+
UI.Style.TEXT_DIM +
|
|
397
|
+
' (' +
|
|
398
|
+
value.type +
|
|
399
|
+
')',
|
|
400
|
+
value: key,
|
|
401
|
+
})),
|
|
402
|
+
});
|
|
403
|
+
if (prompts.isCancel(providerID)) throw new UI.CancelledError();
|
|
404
|
+
await Auth.remove(providerID);
|
|
405
|
+
prompts.outro('Logout successful');
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
export const AuthStatusCommand = cmd({
|
|
410
|
+
command: 'status',
|
|
411
|
+
describe: 'check authentication status for all providers (experimental)',
|
|
412
|
+
async handler() {
|
|
413
|
+
UI.empty();
|
|
414
|
+
prompts.intro('Authentication Status');
|
|
415
|
+
|
|
416
|
+
const credentials = await Auth.all();
|
|
417
|
+
const database = await ModelsDev.get();
|
|
418
|
+
|
|
419
|
+
if (Object.keys(credentials).length === 0) {
|
|
420
|
+
prompts.log.warning('No credentials configured');
|
|
421
|
+
prompts.outro("Run 'agent auth login' to authenticate");
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
for (const [providerID, cred] of Object.entries(credentials)) {
|
|
426
|
+
const name = database[providerID]?.name || providerID;
|
|
427
|
+
|
|
428
|
+
if (cred.type === 'oauth') {
|
|
429
|
+
const isExpired = cred.expires < Date.now();
|
|
430
|
+
const expiresIn = cred.expires - Date.now();
|
|
431
|
+
const expiresStr = isExpired
|
|
432
|
+
? 'expired'
|
|
433
|
+
: expiresIn < 3600000
|
|
434
|
+
? `${Math.round(expiresIn / 60000)} minutes`
|
|
435
|
+
: `${Math.round(expiresIn / 3600000)} hours`;
|
|
436
|
+
|
|
437
|
+
if (isExpired) {
|
|
438
|
+
prompts.log.warning(
|
|
439
|
+
`${name}: OAuth token expired (will auto-refresh on next use)`
|
|
440
|
+
);
|
|
441
|
+
} else {
|
|
442
|
+
prompts.log.success(
|
|
443
|
+
`${name}: OAuth authenticated (expires in ${expiresStr})`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
} else if (cred.type === 'api') {
|
|
447
|
+
prompts.log.success(`${name}: API key configured`);
|
|
448
|
+
} else if (cred.type === 'wellknown') {
|
|
449
|
+
prompts.log.success(`${name}: WellKnown token configured`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
prompts.outro('Done');
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
export const AuthCommand = cmd({
|
|
458
|
+
command: 'auth',
|
|
459
|
+
describe: 'manage credentials',
|
|
460
|
+
builder: (yargs) =>
|
|
461
|
+
yargs
|
|
462
|
+
.command(AuthLoginCommand)
|
|
463
|
+
.command(AuthLogoutCommand)
|
|
464
|
+
.command(AuthListCommand)
|
|
465
|
+
.command(AuthStatusCommand)
|
|
466
|
+
.demandCommand(1, 'Please specify a subcommand'),
|
|
467
|
+
async handler() {},
|
|
468
|
+
});
|
package/src/cli/cmd/cmd.ts
CHANGED
package/src/cli/cmd/export.ts
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import type { Argv } from
|
|
2
|
-
import { Session } from
|
|
3
|
-
import { cmd } from
|
|
4
|
-
import { bootstrap } from
|
|
5
|
-
import { UI } from
|
|
6
|
-
import * as prompts from
|
|
7
|
-
import { EOL } from
|
|
1
|
+
import type { Argv } from 'yargs';
|
|
2
|
+
import { Session } from '../../session';
|
|
3
|
+
import { cmd } from './cmd';
|
|
4
|
+
import { bootstrap } from '../bootstrap';
|
|
5
|
+
import { UI } from '../ui';
|
|
6
|
+
import * as prompts from '@clack/prompts';
|
|
7
|
+
import { EOL } from 'os';
|
|
8
8
|
|
|
9
9
|
export const ExportCommand = cmd({
|
|
10
|
-
command:
|
|
11
|
-
describe:
|
|
10
|
+
command: 'export [sessionID]',
|
|
11
|
+
describe: 'export session data as JSON',
|
|
12
12
|
builder: (yargs: Argv) => {
|
|
13
|
-
return yargs.positional(
|
|
14
|
-
describe:
|
|
15
|
-
type:
|
|
16
|
-
})
|
|
13
|
+
return yargs.positional('sessionID', {
|
|
14
|
+
describe: 'session id to export',
|
|
15
|
+
type: 'string',
|
|
16
|
+
});
|
|
17
17
|
},
|
|
18
18
|
handler: async (args) => {
|
|
19
19
|
await bootstrap(process.cwd(), async () => {
|
|
20
|
-
let sessionID = args.sessionID
|
|
21
|
-
process.stderr.write(`Exporting session: ${sessionID ??
|
|
20
|
+
let sessionID = args.sessionID;
|
|
21
|
+
process.stderr.write(`Exporting session: ${sessionID ?? 'latest'}`);
|
|
22
22
|
|
|
23
23
|
if (!sessionID) {
|
|
24
|
-
UI.empty()
|
|
25
|
-
prompts.intro(
|
|
24
|
+
UI.empty();
|
|
25
|
+
prompts.intro('Export session', {
|
|
26
26
|
output: process.stderr,
|
|
27
|
-
})
|
|
27
|
+
});
|
|
28
28
|
|
|
29
|
-
const sessions = []
|
|
29
|
+
const sessions = [];
|
|
30
30
|
for await (const session of Session.list()) {
|
|
31
|
-
sessions.push(session)
|
|
31
|
+
sessions.push(session);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
if (sessions.length === 0) {
|
|
35
|
-
prompts.log.error(
|
|
35
|
+
prompts.log.error('No sessions found', {
|
|
36
36
|
output: process.stderr,
|
|
37
|
-
})
|
|
38
|
-
prompts.outro(
|
|
37
|
+
});
|
|
38
|
+
prompts.outro('Done', {
|
|
39
39
|
output: process.stderr,
|
|
40
|
-
})
|
|
41
|
-
return
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
sessions.sort((a, b) => b.time.updated - a.time.updated)
|
|
44
|
+
sessions.sort((a, b) => b.time.updated - a.time.updated);
|
|
45
45
|
|
|
46
46
|
const selectedSession = await prompts.autocomplete({
|
|
47
|
-
message:
|
|
47
|
+
message: 'Select session to export',
|
|
48
48
|
maxItems: 10,
|
|
49
49
|
options: sessions.map((session) => ({
|
|
50
50
|
label: session.title,
|
|
@@ -52,22 +52,22 @@ export const ExportCommand = cmd({
|
|
|
52
52
|
hint: `${new Date(session.time.updated).toLocaleString()} • ${session.id.slice(-8)}`,
|
|
53
53
|
})),
|
|
54
54
|
output: process.stderr,
|
|
55
|
-
})
|
|
55
|
+
});
|
|
56
56
|
|
|
57
57
|
if (prompts.isCancel(selectedSession)) {
|
|
58
|
-
throw new UI.CancelledError()
|
|
58
|
+
throw new UI.CancelledError();
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
sessionID = selectedSession as string
|
|
61
|
+
sessionID = selectedSession as string;
|
|
62
62
|
|
|
63
|
-
prompts.outro(
|
|
63
|
+
prompts.outro('Exporting session...', {
|
|
64
64
|
output: process.stderr,
|
|
65
|
-
})
|
|
65
|
+
});
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
try {
|
|
69
|
-
const sessionInfo = await Session.get(sessionID!)
|
|
70
|
-
const messages = await Session.messages({ sessionID: sessionID! })
|
|
69
|
+
const sessionInfo = await Session.get(sessionID!);
|
|
70
|
+
const messages = await Session.messages({ sessionID: sessionID! });
|
|
71
71
|
|
|
72
72
|
const exportData = {
|
|
73
73
|
info: sessionInfo,
|
|
@@ -75,14 +75,14 @@ export const ExportCommand = cmd({
|
|
|
75
75
|
info: msg.info,
|
|
76
76
|
parts: msg.parts,
|
|
77
77
|
})),
|
|
78
|
-
}
|
|
78
|
+
};
|
|
79
79
|
|
|
80
|
-
process.stdout.write(JSON.stringify(exportData, null, 2))
|
|
81
|
-
process.stdout.write(EOL)
|
|
80
|
+
process.stdout.write(JSON.stringify(exportData, null, 2));
|
|
81
|
+
process.stdout.write(EOL);
|
|
82
82
|
} catch (error) {
|
|
83
|
-
UI.error(`Session not found: ${sessionID!}`)
|
|
84
|
-
process.exit(1)
|
|
83
|
+
UI.error(`Session not found: ${sessionID!}`);
|
|
84
|
+
process.exit(1);
|
|
85
85
|
}
|
|
86
|
-
})
|
|
86
|
+
});
|
|
87
87
|
},
|
|
88
|
-
})
|
|
88
|
+
});
|