@slates/cli 1.0.0-rc.10

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,38 @@
1
+ {
2
+ "name": "@slates/cli",
3
+ "version": "1.0.0-rc.10",
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
+ "typecheck": "tsc --noEmit",
24
+ "biome:check": "biome check --write src"
25
+ },
26
+ "dependencies": {
27
+ "@inquirer/prompts": "^7.4.0",
28
+ "@slates/client": "1.0.0-rc.12",
29
+ "@slates/oauth-microsoft": "1.0.0-rc.5",
30
+ "@slates/profiles": "1.0.0-rc.10",
31
+ "sade": "^1.8.1"
32
+ },
33
+ "devDependencies": {
34
+ "@slates/tsconfig": "1.0.0-rc.1",
35
+ "typescript": "5.8.2",
36
+ "vitest": "^3.1.2"
37
+ }
38
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,291 @@
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
+ cli
63
+ .command('profiles add')
64
+ .option('--name', 'Profile name')
65
+ .option('--entry', 'Local slate entry file')
66
+ .option('--export-name', 'Named export for the local slate provider')
67
+ .option('--default', 'Use this profile as the default')
68
+ .action(opts =>
69
+ printResult(() =>
70
+ addProfile({
71
+ integration: integration!,
72
+ name: opts.name,
73
+ entry: opts.entry,
74
+ exportName: opts.exportName,
75
+ useAsDefault: Boolean(opts.default)
76
+ })
77
+ )
78
+ );
79
+
80
+ cli
81
+ .command('profiles list')
82
+ .action(() => printResult(() => listProfiles({ integration: integration! })));
83
+
84
+ cli
85
+ .command('profiles get [profile]')
86
+ .action((profile: string | undefined) =>
87
+ printResult(() => getProfile({ integration: integration!, profile }))
88
+ );
89
+
90
+ cli
91
+ .command('profiles use [profile]')
92
+ .action((profile: string | undefined) =>
93
+ printResult(() => useProfile({ integration: integration!, profile }))
94
+ );
95
+
96
+ cli
97
+ .command('profiles remove [profile]')
98
+ .action((profile: string | undefined) =>
99
+ printResult(() => removeProfile({ integration: integration!, profile }))
100
+ );
101
+
102
+ cli
103
+ .command('setup')
104
+ .option('--name', 'Profile name')
105
+ .option('--export-name', 'Named export for the local slate provider')
106
+ .action(opts =>
107
+ printResult(() =>
108
+ setupIntegration({
109
+ integration: integration!,
110
+ name: opts.name,
111
+ exportName: opts.exportName
112
+ })
113
+ )
114
+ );
115
+
116
+ cli
117
+ .command('tools list')
118
+ .option('--profile', 'Profile ID or name')
119
+ .action(opts =>
120
+ printResult(() =>
121
+ listTools({
122
+ integration: integration!,
123
+ profile: opts.profile
124
+ })
125
+ )
126
+ );
127
+
128
+ cli
129
+ .command('tools get [toolId]')
130
+ .option('--profile', 'Profile ID or name')
131
+ .action((toolId: string | undefined, opts) =>
132
+ printResult(() =>
133
+ getTool({
134
+ integration: integration!,
135
+ profile: opts.profile,
136
+ toolId
137
+ })
138
+ )
139
+ );
140
+
141
+ cli
142
+ .command('tools schema [toolId]')
143
+ .option('--profile', 'Profile ID or name')
144
+ .action((toolId: string | undefined, opts) =>
145
+ printResult(async () => {
146
+ let tool = await getTool({
147
+ integration: integration!,
148
+ profile: opts.profile,
149
+ toolId
150
+ });
151
+ return tool.inputSchema;
152
+ })
153
+ );
154
+
155
+ cli
156
+ .command('tools call [toolId]')
157
+ .option('--profile', 'Profile ID or name')
158
+ .option('--input', 'JSON input object')
159
+ .option('--auth-method-id', 'Preferred auth method ID')
160
+ .action((toolId: string | undefined, opts) =>
161
+ printResult(() =>
162
+ callTool({
163
+ integration: integration!,
164
+ profile: opts.profile,
165
+ toolId,
166
+ input: opts.input,
167
+ authMethodId: opts.authMethodId
168
+ })
169
+ )
170
+ );
171
+
172
+ cli
173
+ .command('auth list')
174
+ .option('--profile', 'Profile ID or name')
175
+ .action(opts =>
176
+ printResult(() => listAuth({ integration: integration!, profile: opts.profile }))
177
+ );
178
+
179
+ cli
180
+ .command('auth get [authMethodId]')
181
+ .option('--profile', 'Profile ID or name')
182
+ .action((authMethodId: string | undefined, opts) =>
183
+ printResult(() =>
184
+ getAuth({ integration: integration!, profile: opts.profile, authMethodId })
185
+ )
186
+ );
187
+
188
+ cli
189
+ .command('auth setup [authMethodId]')
190
+ .option('--profile', 'Profile ID or name')
191
+ .option('--input', 'JSON auth input object')
192
+ .option('--oauth-credential', 'OAuth credential ID or name')
193
+ .option('--client-id', 'OAuth client ID')
194
+ .option('--client-secret', 'OAuth client secret')
195
+ .option('--scopes', 'Comma-separated OAuth scopes')
196
+ .action((authMethodId: string | undefined, opts) =>
197
+ printResult(() =>
198
+ setupAuth({
199
+ integration: integration!,
200
+ profile: opts.profile,
201
+ authMethodId,
202
+ input: opts.input,
203
+ oauthCredential: opts.oauthCredential,
204
+ clientId: opts.clientId,
205
+ clientSecret: opts.clientSecret,
206
+ scopes: opts.scopes
207
+ })
208
+ )
209
+ );
210
+
211
+ cli
212
+ .command('auth credentials list [authMethodId]')
213
+ .action((authMethodId: string | undefined) =>
214
+ printResult(() => listOAuthCredentials({ integration: integration!, authMethodId }))
215
+ );
216
+
217
+ cli
218
+ .command('auth credentials add [authMethodId]')
219
+ .option('--name', 'Credential name')
220
+ .option('--client-id', 'OAuth client ID')
221
+ .option('--client-secret', 'OAuth client secret')
222
+ .action((authMethodId: string | undefined, opts) =>
223
+ printResult(() =>
224
+ addOAuthCredentials({
225
+ integration: integration!,
226
+ authMethodId,
227
+ name: opts.name,
228
+ clientId: opts.clientId,
229
+ clientSecret: opts.clientSecret
230
+ })
231
+ )
232
+ );
233
+
234
+ cli
235
+ .command('auth refresh [authMethodId]')
236
+ .option('--profile', 'Profile ID or name')
237
+ .action((authMethodId: string | undefined, opts) =>
238
+ printResult(() =>
239
+ refreshAuth({ integration: integration!, profile: opts.profile, authMethodId })
240
+ )
241
+ );
242
+
243
+ cli
244
+ .command('config get')
245
+ .option('--profile', 'Profile ID or name')
246
+ .action(opts =>
247
+ printResult(() => getConfig({ integration: integration!, profile: opts.profile }))
248
+ );
249
+
250
+ cli
251
+ .command('config set')
252
+ .option('--profile', 'Profile ID or name')
253
+ .option('--input', 'JSON config object')
254
+ .action(opts =>
255
+ printResult(() =>
256
+ setConfig({ integration: integration!, profile: opts.profile, input: opts.input })
257
+ )
258
+ );
259
+
260
+ cli
261
+ .command('config schema')
262
+ .option('--profile', 'Profile ID or name')
263
+ .action(opts =>
264
+ printResult(() => getConfigSchema({ integration: integration!, profile: opts.profile }))
265
+ );
266
+
267
+ cli
268
+ .command('test')
269
+ .option('--profile', 'Profile ID or name')
270
+ .action(opts =>
271
+ printResult(async () => {
272
+ let separatorIndex = process.argv.indexOf('--');
273
+ await runVitestWithProfile({
274
+ integration: integration!,
275
+ profile: opts.profile,
276
+ vitestArgs: separatorIndex === -1 ? [] : process.argv.slice(separatorIndex + 1)
277
+ });
278
+
279
+ return { success: true };
280
+ })
281
+ );
282
+
283
+ cli
284
+ .command('repl')
285
+ .option('--profile', 'Profile ID or name')
286
+ .action(opts =>
287
+ printResult(() => startRepl({ integration: integration!, profile: opts.profile }))
288
+ );
289
+
290
+ cli.parse([process.argv[0] ?? 'bun', process.argv[1] ?? 'slates', ...argv.slice(1)]);
291
+ }
@@ -0,0 +1,60 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { normalizeCallbackRedirectUriForIntegration } from './auth';
3
+
4
+ describe('normalizeCallbackRedirectUriForIntegration', () => {
5
+ it('normalizes Notion loopback redirects to localhost', () => {
6
+ expect(
7
+ normalizeCallbackRedirectUriForIntegration('notion', 'http://127.0.0.1:45873/callback')
8
+ ).toBe('http://localhost:45873/callback');
9
+ });
10
+
11
+ it('normalizes Intercom loopback redirects to localhost', () => {
12
+ expect(
13
+ normalizeCallbackRedirectUriForIntegration('intercom', 'http://127.0.0.1:45873/callback')
14
+ ).toBe('http://localhost:45873/callback');
15
+ });
16
+
17
+ it('normalizes Typeform loopback redirects to localhost', () => {
18
+ expect(
19
+ normalizeCallbackRedirectUriForIntegration('typeform', 'http://127.0.0.1:45873/callback')
20
+ ).toBe('http://localhost:45873/callback');
21
+ });
22
+
23
+ it('normalizes Xero loopback redirects to localhost', () => {
24
+ expect(
25
+ normalizeCallbackRedirectUriForIntegration('xero', 'http://127.0.0.1:45873/callback')
26
+ ).toBe('http://localhost:45873/callback');
27
+ });
28
+
29
+ it('normalizes Zendesk loopback redirects to localhost', () => {
30
+ expect(
31
+ normalizeCallbackRedirectUriForIntegration('zendesk', 'http://127.0.0.1:45873/callback')
32
+ ).toBe('http://localhost:45873/callback');
33
+ });
34
+
35
+ it('leaves unrelated integration redirects unchanged', () => {
36
+ expect(
37
+ normalizeCallbackRedirectUriForIntegration('attio', 'http://127.0.0.1:45873/callback')
38
+ ).toBe('http://127.0.0.1:45873/callback');
39
+ });
40
+
41
+ it('normalizes HubSpot developer platform OAuth redirects to localhost', () => {
42
+ expect(
43
+ normalizeCallbackRedirectUriForIntegration(
44
+ 'hubspot',
45
+ 'http://127.0.0.1:45873/callback',
46
+ 'developer_platform_oauth'
47
+ )
48
+ ).toBe('http://localhost:45873/callback');
49
+ });
50
+
51
+ it('leaves HubSpot legacy OAuth redirects unchanged', () => {
52
+ expect(
53
+ normalizeCallbackRedirectUriForIntegration(
54
+ 'hubspot',
55
+ 'http://127.0.0.1:45873/callback',
56
+ 'oauth'
57
+ )
58
+ ).toBe('http://127.0.0.1:45873/callback');
59
+ });
60
+ });