@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 +38 -0
- package/src/cli.ts +291 -0
- package/src/commands/auth.test.ts +60 -0
- package/src/commands/auth.ts +451 -0
- package/src/commands/config.ts +31 -0
- package/src/commands/index.ts +6 -0
- package/src/commands/profiles.ts +151 -0
- package/src/commands/repl.ts +95 -0
- package/src/commands/test.ts +94 -0
- package/src/commands/tools.ts +57 -0
- package/src/lib/context.ts +199 -0
- package/src/lib/integration.test.ts +84 -0
- package/src/lib/integration.ts +152 -0
- package/src/lib/oauth.ts +162 -0
- package/src/lib/prompts.ts +159 -0
- package/src/lib/types.ts +10 -0
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
|
+
});
|