@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 +37 -0
- package/src/cli.ts +261 -0
- package/src/commands/auth.ts +405 -0
- package/src/commands/config.ts +31 -0
- package/src/commands/index.ts +6 -0
- package/src/commands/profiles.ts +143 -0
- package/src/commands/repl.ts +95 -0
- package/src/commands/test.ts +92 -0
- package/src/commands/tools.ts +57 -0
- package/src/lib/context.ts +192 -0
- package/src/lib/integration.test.ts +76 -0
- package/src/lib/integration.ts +134 -0
- package/src/lib/oauth.ts +136 -0
- package/src/lib/prompts.ts +159 -0
- package/src/lib/types.ts +10 -0
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
|
+
};
|