@slates/cli 1.0.0-rc.2

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 ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@slates/cli",
3
+ "version": "1.0.0-rc.2",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "files": [
8
+ "src/**",
9
+ "dist/**",
10
+ "README.md",
11
+ "package.json"
12
+ ],
13
+ "author": "Tobias Herber",
14
+ "license": "FSL 1.1",
15
+ "type": "module",
16
+ "source": "src/cli.ts",
17
+ "bin": "./src/cli.ts",
18
+ "exports": {
19
+ "default": "./src/cli.ts"
20
+ },
21
+ "scripts": {
22
+ "test": "vitest run --passWithNoTests",
23
+ "lint": "prettier src/**/*.ts --check",
24
+ "typecheck": "tsc --noEmit"
25
+ },
26
+ "dependencies": {
27
+ "@inquirer/prompts": "^7.4.0",
28
+ "@slates/client": "1.0.0-rc.2",
29
+ "@slates/profiles": "1.0.0-rc.2",
30
+ "sade": "^1.8.1"
31
+ },
32
+ "devDependencies": {
33
+ "@slates/tsconfig": "1.0.0-rc.1",
34
+ "typescript": "5.8.2",
35
+ "vitest": "^3.1.2"
36
+ }
37
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import sade from 'sade';
4
+ import {
5
+ addOAuthCredentials,
6
+ addProfile,
7
+ callTool,
8
+ getAuth,
9
+ getConfig,
10
+ getConfigSchema,
11
+ getProfile,
12
+ getTool,
13
+ listAuth,
14
+ listOAuthCredentials,
15
+ listProfiles,
16
+ listTools,
17
+ refreshAuth,
18
+ removeProfile,
19
+ runAllIntegrationTests,
20
+ runVitestWithProfile,
21
+ setConfig,
22
+ setupAuth,
23
+ setupIntegration,
24
+ startRepl,
25
+ useProfile
26
+ } from './commands';
27
+
28
+ let printResult = async (cb: () => Promise<unknown>) => {
29
+ try {
30
+ let result = await cb();
31
+ if (result !== undefined) {
32
+ console.log(JSON.stringify(result, null, 2));
33
+ }
34
+ } catch (error) {
35
+ console.error(error instanceof Error ? error.message : error);
36
+ process.exit(1);
37
+ }
38
+ };
39
+
40
+ let cli = sade('slates');
41
+ let argv = process.argv.slice(2);
42
+ let isGlobalTestCommand = argv[0] === 'test';
43
+ let integration = isGlobalTestCommand ? null : argv[0];
44
+
45
+ if (!isGlobalTestCommand && (!integration || integration.startsWith('-'))) {
46
+ console.error('Usage: slates <integration> <command>\n slates test');
47
+ process.exit(1);
48
+ }
49
+
50
+ if (isGlobalTestCommand) {
51
+ cli.command('test').action(() =>
52
+ printResult(async () => {
53
+ let separatorIndex = process.argv.indexOf('--');
54
+ return runAllIntegrationTests({
55
+ vitestArgs: separatorIndex === -1 ? [] : process.argv.slice(separatorIndex + 1)
56
+ });
57
+ })
58
+ );
59
+
60
+ cli.parse([process.argv[0] ?? 'bun', process.argv[1] ?? 'slates', ...argv]);
61
+ } else {
62
+
63
+ cli
64
+ .command('profiles add')
65
+ .option('--name', 'Profile name')
66
+ .option('--entry', 'Local slate entry file')
67
+ .option('--export-name', 'Named export for the local slate provider')
68
+ .option('--default', 'Use this profile as the default')
69
+ .action(opts =>
70
+ printResult(() =>
71
+ addProfile({
72
+ integration: integration!,
73
+ name: opts.name,
74
+ entry: opts.entry,
75
+ exportName: opts.exportName,
76
+ useAsDefault: Boolean(opts.default)
77
+ })
78
+ )
79
+ );
80
+
81
+ cli
82
+ .command('profiles list')
83
+ .action(() => printResult(() => listProfiles({ integration: integration! })));
84
+
85
+ cli
86
+ .command('profiles get [profile]')
87
+ .action((profile: string | undefined) =>
88
+ printResult(() => getProfile({ integration: integration!, profile }))
89
+ );
90
+
91
+ cli
92
+ .command('profiles use [profile]')
93
+ .action((profile: string | undefined) =>
94
+ printResult(() => useProfile({ integration: integration!, profile }))
95
+ );
96
+
97
+ cli
98
+ .command('profiles remove [profile]')
99
+ .action((profile: string | undefined) =>
100
+ printResult(() => removeProfile({ integration: integration!, profile }))
101
+ );
102
+
103
+ cli
104
+ .command('setup')
105
+ .option('--name', 'Profile name')
106
+ .option('--export-name', 'Named export for the local slate provider')
107
+ .action(opts =>
108
+ printResult(() =>
109
+ setupIntegration({
110
+ integration: integration!,
111
+ name: opts.name,
112
+ exportName: opts.exportName
113
+ })
114
+ )
115
+ );
116
+
117
+ cli
118
+ .command('tools list')
119
+ .option('--profile', 'Profile ID or name')
120
+ .action(opts => printResult(() => listTools({ integration: integration!, profile: opts.profile })));
121
+
122
+ cli
123
+ .command('tools get [toolId]')
124
+ .option('--profile', 'Profile ID or name')
125
+ .action((toolId: string | undefined, opts) =>
126
+ printResult(() => getTool({ integration: integration!, profile: opts.profile, toolId }))
127
+ );
128
+
129
+ cli
130
+ .command('tools schema [toolId]')
131
+ .option('--profile', 'Profile ID or name')
132
+ .action((toolId: string | undefined, opts) =>
133
+ printResult(async () => {
134
+ let tool = await getTool({ integration: integration!, profile: opts.profile, toolId });
135
+ return tool.inputSchema;
136
+ })
137
+ );
138
+
139
+ cli
140
+ .command('tools call [toolId]')
141
+ .option('--profile', 'Profile ID or name')
142
+ .option('--input', 'JSON input object')
143
+ .option('--auth-method-id', 'Preferred auth method ID')
144
+ .action((toolId: string | undefined, opts) =>
145
+ printResult(() =>
146
+ callTool({
147
+ integration: integration!,
148
+ profile: opts.profile,
149
+ toolId,
150
+ input: opts.input,
151
+ authMethodId: opts.authMethodId
152
+ })
153
+ )
154
+ );
155
+
156
+ cli
157
+ .command('auth list')
158
+ .option('--profile', 'Profile ID or name')
159
+ .action(opts => printResult(() => listAuth({ integration: integration!, profile: opts.profile })));
160
+
161
+ cli
162
+ .command('auth get [authMethodId]')
163
+ .option('--profile', 'Profile ID or name')
164
+ .action((authMethodId: string | undefined, opts) =>
165
+ printResult(() => getAuth({ integration: integration!, profile: opts.profile, authMethodId }))
166
+ );
167
+
168
+ cli
169
+ .command('auth setup [authMethodId]')
170
+ .option('--profile', 'Profile ID or name')
171
+ .option('--input', 'JSON auth input object')
172
+ .option('--oauth-credential', 'OAuth credential ID or name')
173
+ .option('--client-id', 'OAuth client ID')
174
+ .option('--client-secret', 'OAuth client secret')
175
+ .option('--scopes', 'Comma-separated OAuth scopes')
176
+ .action((authMethodId: string | undefined, opts) =>
177
+ printResult(() =>
178
+ setupAuth({
179
+ integration: integration!,
180
+ profile: opts.profile,
181
+ authMethodId,
182
+ input: opts.input,
183
+ oauthCredential: opts.oauthCredential,
184
+ clientId: opts.clientId,
185
+ clientSecret: opts.clientSecret,
186
+ scopes: opts.scopes
187
+ })
188
+ )
189
+ );
190
+
191
+ cli
192
+ .command('auth credentials list [authMethodId]')
193
+ .action((authMethodId: string | undefined) =>
194
+ printResult(() => listOAuthCredentials({ integration: integration!, authMethodId }))
195
+ );
196
+
197
+ cli
198
+ .command('auth credentials add [authMethodId]')
199
+ .option('--name', 'Credential name')
200
+ .option('--client-id', 'OAuth client ID')
201
+ .option('--client-secret', 'OAuth client secret')
202
+ .action((authMethodId: string | undefined, opts) =>
203
+ printResult(() =>
204
+ addOAuthCredentials({
205
+ integration: integration!,
206
+ authMethodId,
207
+ name: opts.name,
208
+ clientId: opts.clientId,
209
+ clientSecret: opts.clientSecret
210
+ })
211
+ )
212
+ );
213
+
214
+ cli
215
+ .command('auth refresh [authMethodId]')
216
+ .option('--profile', 'Profile ID or name')
217
+ .action((authMethodId: string | undefined, opts) =>
218
+ printResult(() => refreshAuth({ integration: integration!, profile: opts.profile, authMethodId }))
219
+ );
220
+
221
+ cli
222
+ .command('config get')
223
+ .option('--profile', 'Profile ID or name')
224
+ .action(opts => printResult(() => getConfig({ integration: integration!, profile: opts.profile })));
225
+
226
+ cli
227
+ .command('config set')
228
+ .option('--profile', 'Profile ID or name')
229
+ .option('--input', 'JSON config object')
230
+ .action(opts =>
231
+ printResult(() => setConfig({ integration: integration!, profile: opts.profile, input: opts.input }))
232
+ );
233
+
234
+ cli
235
+ .command('config schema')
236
+ .option('--profile', 'Profile ID or name')
237
+ .action(opts => printResult(() => getConfigSchema({ integration: integration!, profile: opts.profile })));
238
+
239
+ cli
240
+ .command('test')
241
+ .option('--profile', 'Profile ID or name')
242
+ .action(opts =>
243
+ printResult(async () => {
244
+ let separatorIndex = process.argv.indexOf('--');
245
+ await runVitestWithProfile({
246
+ integration: integration!,
247
+ profile: opts.profile,
248
+ vitestArgs: separatorIndex === -1 ? [] : process.argv.slice(separatorIndex + 1)
249
+ });
250
+
251
+ return { success: true };
252
+ })
253
+ );
254
+
255
+ cli
256
+ .command('repl')
257
+ .option('--profile', 'Profile ID or name')
258
+ .action(opts => printResult(() => startRepl({ integration: integration!, profile: opts.profile })));
259
+
260
+ cli.parse([process.argv[0] ?? 'bun', process.argv[1] ?? 'slates', ...argv.slice(1)]);
261
+ }
@@ -0,0 +1,405 @@
1
+ import { confirm, select } from '@inquirer/prompts';
2
+ import { SlatesOAuthCredentialRecord, SlatesStoredAuth } from '@slates/profiles';
3
+ import {
4
+ chooseAuthMethod,
5
+ createClientContext,
6
+ createIntegrationClientContext,
7
+ openIntegrationStore
8
+ } from '../lib/context';
9
+ import { chooseScopes, createOAuthCallbackListener, openBrowser } from '../lib/oauth';
10
+ import {
11
+ parseJsonObject,
12
+ parseList,
13
+ promptForObjectSchema,
14
+ promptForString
15
+ } from '../lib/prompts';
16
+ import { JsonInput, WithProfile } from '../lib/types';
17
+
18
+ type JsonObject = Record<string, any>;
19
+
20
+ type AuthSetupOptions = WithProfile &
21
+ JsonInput & {
22
+ authMethodId?: string;
23
+ clientId?: string;
24
+ clientSecret?: string;
25
+ oauthCredential?: string;
26
+ scopes?: string;
27
+ };
28
+
29
+ export let listAuth = async (opts: WithProfile) => {
30
+ let { store, profile } = await createClientContext(opts);
31
+ return store.listAuth(profile.id);
32
+ };
33
+
34
+ export let getAuth = async (opts: WithProfile & { authMethodId?: string }) => {
35
+ let { store, profile } = await createClientContext(opts);
36
+ return store.getAuth(profile.id, opts.authMethodId);
37
+ };
38
+
39
+ export let listOAuthCredentials = async (
40
+ opts: Pick<WithProfile, 'integration'> & { authMethodId?: string }
41
+ ) => {
42
+ let { store } = await openIntegrationStore(opts.integration);
43
+ return store.listOAuthCredentials(opts.authMethodId).map(credential => ({
44
+ id: credential.id,
45
+ name: credential.name,
46
+ authMethodId: credential.authMethodId,
47
+ clientId: credential.clientId
48
+ }));
49
+ };
50
+
51
+ export let addOAuthCredentials = async (
52
+ opts: Pick<WithProfile, 'integration'> & {
53
+ authMethodId?: string;
54
+ name?: string;
55
+ clientId?: string;
56
+ clientSecret?: string;
57
+ }
58
+ ): Promise<SlatesOAuthCredentialRecord> => {
59
+ let { store, client } = await createIntegrationClientContext({
60
+ integration: opts.integration
61
+ });
62
+ let authMethod = await chooseAuthMethod({
63
+ client,
64
+ authMethodId: opts.authMethodId,
65
+ forcePrompt: !opts.authMethodId
66
+ });
67
+
68
+ if (authMethod.type !== 'auth.oauth') {
69
+ throw new Error(`Authentication method ${authMethod.id} is not OAuth.`);
70
+ }
71
+
72
+ let clientId = opts.clientId ?? (await promptForString({ message: 'OAuth client ID' }));
73
+ let clientSecret =
74
+ opts.clientSecret ??
75
+ (await promptForString({ message: 'OAuth client secret', secret: true }));
76
+ let name =
77
+ opts.name ??
78
+ (await promptForString({
79
+ message: 'Credential name',
80
+ defaultValue: `${authMethod.name} credentials`
81
+ }));
82
+
83
+ let credential = store.upsertOAuthCredential({
84
+ name,
85
+ authMethodId: authMethod.id,
86
+ clientId,
87
+ clientSecret
88
+ });
89
+ await store.save();
90
+ return credential;
91
+ };
92
+
93
+ let createOAuthCredentialInteractive = async (opts: {
94
+ store: Awaited<ReturnType<typeof createClientContext>>['store'];
95
+ authMethod: { id: string; name: string; type: string };
96
+ clientId?: string;
97
+ clientSecret?: string;
98
+ }) => {
99
+ let clientId = opts.clientId ?? (await promptForString({ message: 'OAuth client ID' }));
100
+ let clientSecret =
101
+ opts.clientSecret ??
102
+ (await promptForString({ message: 'OAuth client secret', secret: true }));
103
+ let name = await promptForString({
104
+ message: 'Credential name',
105
+ defaultValue: `${opts.authMethod.name} credentials`
106
+ });
107
+
108
+ let credential = opts.store.upsertOAuthCredential({
109
+ name,
110
+ authMethodId: opts.authMethod.id,
111
+ clientId,
112
+ clientSecret
113
+ });
114
+ await opts.store.save();
115
+ return credential;
116
+ };
117
+
118
+ let chooseOAuthCredentialsForSetup = async (opts: {
119
+ store: Awaited<ReturnType<typeof createClientContext>>['store'];
120
+ authMethod: { id: string; name: string; type: string };
121
+ clientId?: string;
122
+ clientSecret?: string;
123
+ oauthCredential?: string;
124
+ }) => {
125
+ if (opts.authMethod.type !== 'auth.oauth') {
126
+ return null;
127
+ }
128
+
129
+ if (opts.clientId || opts.clientSecret) {
130
+ let credential = await createOAuthCredentialInteractive({
131
+ store: opts.store,
132
+ authMethod: opts.authMethod,
133
+ clientId: opts.clientId,
134
+ clientSecret: opts.clientSecret
135
+ });
136
+ return {
137
+ credential,
138
+ clientId: credential.clientId,
139
+ clientSecret: credential.clientSecret
140
+ };
141
+ }
142
+
143
+ if (opts.oauthCredential) {
144
+ let credential = opts.store.getOAuthCredential(opts.oauthCredential, opts.authMethod.id);
145
+ if (!credential) {
146
+ throw new Error(`Unknown OAuth credentials: ${opts.oauthCredential}`);
147
+ }
148
+
149
+ return {
150
+ credential,
151
+ clientId: credential.clientId,
152
+ clientSecret: credential.clientSecret
153
+ };
154
+ }
155
+
156
+ let credentials = opts.store.listOAuthCredentials(opts.authMethod.id);
157
+ if (credentials.length === 0) {
158
+ let credential = await createOAuthCredentialInteractive({
159
+ store: opts.store,
160
+ authMethod: opts.authMethod
161
+ });
162
+ return {
163
+ credential,
164
+ clientId: credential.clientId,
165
+ clientSecret: credential.clientSecret
166
+ };
167
+ }
168
+
169
+ if (credentials.length === 1) {
170
+ let useExisting = await confirm({
171
+ message: `Use saved OAuth credentials "${credentials[0]!.name}"?`,
172
+ default: true
173
+ });
174
+
175
+ if (useExisting) {
176
+ let credential = credentials[0]!;
177
+ return {
178
+ credential,
179
+ clientId: credential.clientId,
180
+ clientSecret: credential.clientSecret
181
+ };
182
+ }
183
+
184
+ let credential = await createOAuthCredentialInteractive({
185
+ store: opts.store,
186
+ authMethod: opts.authMethod
187
+ });
188
+ return {
189
+ credential,
190
+ clientId: credential.clientId,
191
+ clientSecret: credential.clientSecret
192
+ };
193
+ }
194
+
195
+ let selected = await select({
196
+ message: 'Choose OAuth credentials',
197
+ choices: [
198
+ ...credentials.map(credential => ({
199
+ name: `${credential.name} (${credential.clientId})`,
200
+ value: credential.id
201
+ })),
202
+ {
203
+ name: 'Create new OAuth credentials',
204
+ value: '__new__'
205
+ }
206
+ ]
207
+ });
208
+
209
+ if (selected === '__new__') {
210
+ let credential = await createOAuthCredentialInteractive({
211
+ store: opts.store,
212
+ authMethod: opts.authMethod
213
+ });
214
+ return {
215
+ credential,
216
+ clientId: credential.clientId,
217
+ clientSecret: credential.clientSecret
218
+ };
219
+ }
220
+
221
+ let credential = opts.store.getOAuthCredential(selected, opts.authMethod.id);
222
+ if (!credential) {
223
+ throw new Error(`Unknown OAuth credentials: ${selected}`);
224
+ }
225
+
226
+ return {
227
+ credential,
228
+ clientId: credential.clientId,
229
+ clientSecret: credential.clientSecret
230
+ };
231
+ };
232
+
233
+ let runAuthSetup = async (opts: AuthSetupOptions): Promise<SlatesStoredAuth> => {
234
+ let { store, profile, client } = await createClientContext(opts);
235
+ let authMethod = await chooseAuthMethod({
236
+ client,
237
+ authMethodId: opts.authMethodId,
238
+ forcePrompt: !opts.authMethodId
239
+ });
240
+
241
+ let defaultInput = authMethod.capabilities.getDefaultInput?.enabled
242
+ ? ((await client.getDefaultAuthInput(authMethod.id)).input ?? {})
243
+ : {};
244
+ let authInput =
245
+ parseJsonObject(opts.input, 'auth input') ??
246
+ (await promptForObjectSchema(authMethod.inputSchema, defaultInput));
247
+
248
+ if (authMethod.capabilities.handleChangedInput?.enabled) {
249
+ authInput =
250
+ (
251
+ await client.updateAuthInput({
252
+ authenticationMethodId: authMethod.id,
253
+ previousInput: null,
254
+ newInput: authInput
255
+ })
256
+ ).input ?? authInput;
257
+ }
258
+
259
+ let output: JsonObject;
260
+ let finalInput = authInput;
261
+ let callbackState: JsonObject | null = null;
262
+ let scopes = parseList(opts.scopes);
263
+
264
+ if (authMethod.type === 'auth.oauth') {
265
+ let callback = await createOAuthCallbackListener();
266
+ console.log(`OAuth redirect URL: ${callback.redirectUri}`);
267
+
268
+ let resolvedOAuthCredentials = await chooseOAuthCredentialsForSetup({
269
+ store,
270
+ authMethod,
271
+ clientId: opts.clientId,
272
+ clientSecret: opts.clientSecret,
273
+ oauthCredential: opts.oauthCredential
274
+ });
275
+ if (!resolvedOAuthCredentials) {
276
+ throw new Error(`Authentication method ${authMethod.id} is not OAuth.`);
277
+ }
278
+
279
+ let clientId = resolvedOAuthCredentials.clientId;
280
+ let clientSecret = resolvedOAuthCredentials.clientSecret;
281
+ scopes = await chooseScopes(authMethod, scopes);
282
+
283
+ let authorizationUrl = await client.getAuthorizationUrl({
284
+ authenticationMethodId: authMethod.id,
285
+ redirectUri: callback.redirectUri,
286
+ state: callback.state,
287
+ input: authInput,
288
+ clientId,
289
+ clientSecret,
290
+ scopes
291
+ });
292
+
293
+ callbackState = authorizationUrl.callbackState ?? null;
294
+ finalInput = authorizationUrl.input ?? authInput;
295
+
296
+ await openBrowser(authorizationUrl.authorizationUrl);
297
+ let callbackResult = await callback.wait();
298
+ if (callbackResult.state !== callback.state) {
299
+ throw new Error('OAuth state mismatch.');
300
+ }
301
+
302
+ let authOutput = await client.handleAuthorizationCallback({
303
+ authenticationMethodId: authMethod.id,
304
+ code: callbackResult.code,
305
+ state: callbackResult.state,
306
+ redirectUri: callback.redirectUri,
307
+ input: finalInput,
308
+ clientId,
309
+ clientSecret,
310
+ scopes,
311
+ callbackState: callbackState ?? undefined
312
+ });
313
+
314
+ output = authOutput.output;
315
+ finalInput = authOutput.input ?? finalInput;
316
+ scopes = authOutput.scopes ?? scopes;
317
+
318
+ let profileInfo = authMethod.capabilities.getProfile?.enabled
319
+ ? await client.getAuthProfile({
320
+ authenticationMethodId: authMethod.id,
321
+ output,
322
+ input: finalInput,
323
+ scopes
324
+ })
325
+ : null;
326
+
327
+ let stored = store.upsertAuth(profile.id, {
328
+ authMethodId: authMethod.id,
329
+ authMethodName: authMethod.name,
330
+ authType: authMethod.type,
331
+ input: finalInput,
332
+ output,
333
+ oauthCredentialId: resolvedOAuthCredentials.credential?.id,
334
+ scopes,
335
+ clientId,
336
+ clientSecret,
337
+ callbackState,
338
+ profile: profileInfo?.profile ?? null
339
+ });
340
+
341
+ await store.save();
342
+ return stored;
343
+ }
344
+
345
+ output = (
346
+ await client.getAuthOutput({
347
+ authenticationMethodId: authMethod.id,
348
+ input: authInput
349
+ })
350
+ ).output;
351
+
352
+ let profileInfo = authMethod.capabilities.getProfile?.enabled
353
+ ? await client.getAuthProfile({
354
+ authenticationMethodId: authMethod.id,
355
+ output,
356
+ input: finalInput,
357
+ scopes
358
+ })
359
+ : null;
360
+
361
+ let stored = store.upsertAuth(profile.id, {
362
+ authMethodId: authMethod.id,
363
+ authMethodName: authMethod.name,
364
+ authType: authMethod.type,
365
+ input: finalInput,
366
+ output,
367
+ scopes,
368
+ profile: profileInfo?.profile ?? null
369
+ });
370
+
371
+ await store.save();
372
+ return stored;
373
+ };
374
+
375
+ export let setupAuth = async (opts: AuthSetupOptions) => runAuthSetup(opts);
376
+
377
+ export let refreshAuth = async (opts: WithProfile & { authMethodId?: string }) => {
378
+ let { store, profile, client } = await createClientContext(opts);
379
+ let storedAuth = store.getAuth(profile.id, opts.authMethodId);
380
+ if (!storedAuth) {
381
+ throw new Error('No stored authentication was found for this profile.');
382
+ }
383
+
384
+ let authMethod = await client.getAuthMethod(storedAuth.authMethodId);
385
+ if (!authMethod.authenticationMethod.capabilities.handleTokenRefresh?.enabled) {
386
+ throw new Error('This authentication method does not support token refresh.');
387
+ }
388
+
389
+ let refreshed = await client.refreshToken({
390
+ authenticationMethodId: storedAuth.authMethodId,
391
+ output: storedAuth.output,
392
+ input: storedAuth.input,
393
+ clientId: storedAuth.clientId ?? '',
394
+ clientSecret: storedAuth.clientSecret ?? '',
395
+ scopes: storedAuth.scopes
396
+ });
397
+
398
+ let updated = store.upsertAuth(profile.id, {
399
+ ...storedAuth,
400
+ input: refreshed.input ?? storedAuth.input,
401
+ output: refreshed.output
402
+ });
403
+ await store.save();
404
+ return updated;
405
+ };