@faable/faable 4.5.5 → 4.7.0
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/dist/api/FaableApi.js +6 -0
- package/dist/api/auth_admin.js +26 -1
- package/dist/commands/auth/accounts/index.js +11 -4
- package/dist/commands/auth/connections/index.js +10 -0
- package/dist/commands/auth/connections/list.js +48 -0
- package/dist/commands/auth/index.js +5 -1
- package/dist/commands/auth/logs/list.js +28 -9
- package/dist/commands/auth/query.js +17 -9
- package/dist/commands/auth/sessions/index.js +15 -0
- package/dist/commands/auth/sessions/list.js +73 -0
- package/dist/commands/auth/sessions/revoke.js +96 -0
- package/dist/commands/auth/users/get.js +6 -4
- package/dist/commands/auth/users/ids.js +4 -2
- package/dist/commands/auth/users/index.js +3 -1
- package/dist/commands/auth/users/list.js +89 -5
- package/dist/commands/auth/users/password_setup.js +54 -0
- package/dist/commands/auth/users/reinstate.js +8 -3
- package/dist/commands/auth/users/resolve.js +36 -0
- package/dist/commands/auth/users/suspend.js +8 -3
- package/dist/commands/deploy/apps/create.js +35 -2
- package/dist/commands/deploy/github/index.js +10 -0
- package/dist/commands/deploy/github/repos.js +70 -0
- package/dist/commands/deploy/index.js +2 -0
- package/dist/commands/deploy/inspect/inspect.js +18 -1
- package/dist/commands/deploy/inspect/trigger.js +28 -0
- package/dist/commands/deploy/launch.js +62 -2
- package/dist/commands/deploy/wait.js +54 -0
- package/dist/mcp/http.js +117 -23
- package/dist/mcp/oauth.js +165 -0
- package/dist/mcp/server.js +16 -5
- package/dist/mcp/tools.js +427 -6
- package/package.json +1 -1
package/dist/mcp/tools.js
CHANGED
|
@@ -52,6 +52,51 @@ const app_row = (a) => ({
|
|
|
52
52
|
branch: a.github_branch,
|
|
53
53
|
deploy_mode: a.deploy_trigger
|
|
54
54
|
});
|
|
55
|
+
// ── Faable Auth ─────────────────────────────────────────────────────────────
|
|
56
|
+
const tenant = z
|
|
57
|
+
.string()
|
|
58
|
+
.optional()
|
|
59
|
+
.describe("Auth tenant id (account_…). Defaults to the project's tenant (list_auth_tenants when it has several).");
|
|
60
|
+
const auth_scope = (a) => [...opt('--account', a.tenant), ...scope(a)];
|
|
61
|
+
const user_ref = z
|
|
62
|
+
.string()
|
|
63
|
+
.describe('User id (user_…) or exact email');
|
|
64
|
+
const time = z
|
|
65
|
+
.string()
|
|
66
|
+
.regex(/^(\d+[mhd]|\d{4}-\d{2}-\d{2}|\d{10,})$/);
|
|
67
|
+
const limit_flag = (a, d = 50) => ['--limit', String(a.limit ?? d)];
|
|
68
|
+
const limit = z
|
|
69
|
+
.number()
|
|
70
|
+
.int()
|
|
71
|
+
.min(1)
|
|
72
|
+
.max(200)
|
|
73
|
+
.optional()
|
|
74
|
+
.describe('How many (default 50)');
|
|
75
|
+
const user_row = (u) => ({
|
|
76
|
+
id: u.id,
|
|
77
|
+
email: u.email,
|
|
78
|
+
name: u.name,
|
|
79
|
+
email_verified: u.email_verified,
|
|
80
|
+
suspended: !!u.suspended,
|
|
81
|
+
suspended_reason: u.suspended ? u.suspended_reason : undefined,
|
|
82
|
+
last_login: u.last_login ?? null,
|
|
83
|
+
logins_count: u.logins_count,
|
|
84
|
+
created_at: u.createdAt
|
|
85
|
+
});
|
|
86
|
+
const log_user = (l) => typeof l.user === 'string'
|
|
87
|
+
? { user_id: l.user }
|
|
88
|
+
: l.user
|
|
89
|
+
? { user_id: l.user.id, email: l.user.email, name: l.user.name }
|
|
90
|
+
: {};
|
|
91
|
+
const AUTH_READ = { ...READ };
|
|
92
|
+
// Which product a tool works on — /tools.json and the landing group by it.
|
|
93
|
+
const product_of = (tool) => tool.command[0] === 'auth' ? 'auth' : 'deploy';
|
|
94
|
+
const AUTH_WRITE = {
|
|
95
|
+
readOnlyHint: false,
|
|
96
|
+
destructiveHint: false,
|
|
97
|
+
idempotentHint: true,
|
|
98
|
+
openWorldHint: true
|
|
99
|
+
};
|
|
55
100
|
const TOOLS = [
|
|
56
101
|
{
|
|
57
102
|
name: 'whoami',
|
|
@@ -151,7 +196,7 @@ const TOOLS = [
|
|
|
151
196
|
{
|
|
152
197
|
name: 'get_deployment',
|
|
153
198
|
title: 'Get a deployment',
|
|
154
|
-
description:
|
|
199
|
+
description: "Everything recorded about one deployment: phase, commit, detected stack, the runnable artifact, the FULL failure reason and, when it failed, `fault` — whether it is the user's to fix (`user`) or Faable's (`platform`). Without `deployment`, the newest one of the app.",
|
|
155
200
|
input: {
|
|
156
201
|
app,
|
|
157
202
|
project,
|
|
@@ -167,6 +212,8 @@ const TOOLS = [
|
|
|
167
212
|
detected: d.detected,
|
|
168
213
|
redeploy_of: d.redeploy_of,
|
|
169
214
|
build_ms: d.build_ms,
|
|
215
|
+
// user = the code/config/repository; platform = Faable's side.
|
|
216
|
+
fault: d.fault ?? null,
|
|
170
217
|
artifact: d.artifact?.artifact
|
|
171
218
|
? {
|
|
172
219
|
profile: d.artifact.artifact.profile,
|
|
@@ -339,13 +386,291 @@ const TOOLS = [
|
|
|
339
386
|
annotations: READ,
|
|
340
387
|
example: { app: 'web' }
|
|
341
388
|
},
|
|
389
|
+
// ── Faable Auth: the users of the project's Auth tenant ───────────────────
|
|
390
|
+
{
|
|
391
|
+
name: 'list_auth_tenants',
|
|
392
|
+
title: 'List Auth tenants',
|
|
393
|
+
description: "The Faable Auth tenants (user directories) of a project. Most projects have one and every Auth tool uses it; with several, pass the right one as `tenant`.",
|
|
394
|
+
input: { project },
|
|
395
|
+
command: ['auth', 'accounts', 'list'],
|
|
396
|
+
flags: scope,
|
|
397
|
+
shape: page((a) => ({ id: a.id, name: a.name, domain: a.domain })),
|
|
398
|
+
annotations: AUTH_READ,
|
|
399
|
+
example: { project: 'p' }
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
name: 'list_auth_logins',
|
|
403
|
+
title: 'Recent logins',
|
|
404
|
+
description: "Who logged in to the app's Faable Auth, newest first: one row per login with the user's email, when, from which IP and with which method. Answers 'who logged in recently?' or 'did X log in today?'. For 'how many', use count_auth_users.",
|
|
405
|
+
input: {
|
|
406
|
+
since: time
|
|
407
|
+
.optional()
|
|
408
|
+
.describe('How far back: 30m, 24h, 7d, a date (default 24h)'),
|
|
409
|
+
status: z
|
|
410
|
+
.enum(['success', 'failed'])
|
|
411
|
+
.optional()
|
|
412
|
+
.describe('Only successful or only failed attempts (default: both)'),
|
|
413
|
+
limit,
|
|
414
|
+
tenant,
|
|
415
|
+
project
|
|
416
|
+
},
|
|
417
|
+
command: ['auth', 'logs', 'list'],
|
|
418
|
+
flags: a => [
|
|
419
|
+
'--type',
|
|
420
|
+
'user.login',
|
|
421
|
+
'--since',
|
|
422
|
+
a.since ?? '24h',
|
|
423
|
+
...opt('--status', a.status),
|
|
424
|
+
'--expand-user',
|
|
425
|
+
...limit_flag(a),
|
|
426
|
+
...auth_scope(a)
|
|
427
|
+
],
|
|
428
|
+
untrusted: true,
|
|
429
|
+
shape: (d) => ({
|
|
430
|
+
logins: (d?.data ?? []).map((l) => ({
|
|
431
|
+
at: l.createdAt,
|
|
432
|
+
...log_user(l),
|
|
433
|
+
status: l.status,
|
|
434
|
+
ip: l.data?.ip,
|
|
435
|
+
connection_type: l.data?.connection_type,
|
|
436
|
+
client_id: l.data?.client_id
|
|
437
|
+
})),
|
|
438
|
+
has_more: !!d?.has_more
|
|
439
|
+
}),
|
|
440
|
+
annotations: AUTH_READ,
|
|
441
|
+
example: { since: '7d', status: 'success', limit: 20, tenant: 'account_1' }
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: 'count_auth_users',
|
|
445
|
+
title: 'Count users',
|
|
446
|
+
description: "How many users of the Auth tenant match: logged in since a time ('how many users logged in today / this week'), signed up since a time ('how many sign-ups this month'), suspended, or all of them. Counts users, not logins.",
|
|
447
|
+
input: {
|
|
448
|
+
last_login_since: time
|
|
449
|
+
.optional()
|
|
450
|
+
.describe('Logged in at or after: 24h, 7d, a date'),
|
|
451
|
+
created_since: time.optional().describe('Signed up at or after: 24h, 7d, a date'),
|
|
452
|
+
created_until: time.optional().describe('Signed up at or before'),
|
|
453
|
+
suspended: z.boolean().optional(),
|
|
454
|
+
tenant,
|
|
455
|
+
project
|
|
456
|
+
},
|
|
457
|
+
command: ['auth', 'users', 'list'],
|
|
458
|
+
flags: a => [
|
|
459
|
+
'--count',
|
|
460
|
+
...opt('--last-login-since', a.last_login_since),
|
|
461
|
+
...opt('--created-since', a.created_since),
|
|
462
|
+
...opt('--created-until', a.created_until),
|
|
463
|
+
...(a.suspended === undefined ? [] : [a.suspended ? '--suspended' : '--no-suspended']),
|
|
464
|
+
...auth_scope(a)
|
|
465
|
+
],
|
|
466
|
+
annotations: AUTH_READ,
|
|
467
|
+
example: { last_login_since: '24h', created_since: '7d', suspended: false }
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
name: 'list_auth_users',
|
|
471
|
+
title: 'List users',
|
|
472
|
+
description: "Users of the Auth tenant. Find one by email or text, list the suspended ones, or sort: `-last_login` (most recent login first), `last_login` (longest without logging in), `-logins_count` (most active), `-createdAt` (newest sign-ups, the default). Sorting by last_login leaves out users who never logged in.",
|
|
473
|
+
input: {
|
|
474
|
+
search: z.string().optional().describe('Text in name, email or phone'),
|
|
475
|
+
email: z.string().optional().describe('Exact email'),
|
|
476
|
+
suspended: z.boolean().optional(),
|
|
477
|
+
sort: z
|
|
478
|
+
.enum(['-last_login', 'last_login', '-logins_count', '-createdAt', 'createdAt'])
|
|
479
|
+
.optional(),
|
|
480
|
+
last_login_since: time.optional().describe('Logged in at or after: 24h, 7d, a date'),
|
|
481
|
+
created_since: time.optional().describe('Signed up at or after'),
|
|
482
|
+
limit,
|
|
483
|
+
cursor,
|
|
484
|
+
tenant,
|
|
485
|
+
project
|
|
486
|
+
},
|
|
487
|
+
command: ['auth', 'users', 'list'],
|
|
488
|
+
flags: a => [
|
|
489
|
+
...opt('-q', a.search),
|
|
490
|
+
...opt('--email', a.email),
|
|
491
|
+
...(a.suspended === undefined ? [] : [a.suspended ? '--suspended' : '--no-suspended']),
|
|
492
|
+
// `--sort=-x`: as two words yargs would read `-last_login` as flags.
|
|
493
|
+
...(a.sort ? [`--sort=${a.sort}`] : []),
|
|
494
|
+
...opt('--last-login-since', a.last_login_since),
|
|
495
|
+
...opt('--created-since', a.created_since),
|
|
496
|
+
...limit_flag(a),
|
|
497
|
+
...opt('--starting-after', a.cursor),
|
|
498
|
+
...auth_scope(a)
|
|
499
|
+
],
|
|
500
|
+
// Names and emails are typed by the end users themselves.
|
|
501
|
+
untrusted: true,
|
|
502
|
+
shape: page(user_row),
|
|
503
|
+
annotations: AUTH_READ,
|
|
504
|
+
example: {
|
|
505
|
+
search: 'ana',
|
|
506
|
+
suspended: false,
|
|
507
|
+
created_since: '30d',
|
|
508
|
+
email: 'ana@example.com',
|
|
509
|
+
sort: '-last_login',
|
|
510
|
+
last_login_since: '7d',
|
|
511
|
+
limit: 10,
|
|
512
|
+
cursor: 'c'
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
name: 'get_auth_user',
|
|
517
|
+
title: 'Get a user',
|
|
518
|
+
description: "One user of the Auth tenant by id or email: profile, verification, suspension (and why), last login and login count, and the social identities they sign in with. Use it to confirm who a user is before suspending them, or to see why they can't log in (together with list_auth_logs).",
|
|
519
|
+
input: { user: user_ref, tenant, project },
|
|
520
|
+
command: ['auth', 'users', 'get'],
|
|
521
|
+
flags: a => [a.user, ...auth_scope(a)],
|
|
522
|
+
untrusted: true,
|
|
523
|
+
shape: (u) => ({
|
|
524
|
+
...user_row(u),
|
|
525
|
+
phone: u.phone,
|
|
526
|
+
last_ip: u.last_ip,
|
|
527
|
+
identities: (u.identities ?? []).map((i) => ({
|
|
528
|
+
provider: i.provider ?? i.connection_type,
|
|
529
|
+
user_id: i.user_id ?? i.provider_user_id
|
|
530
|
+
}))
|
|
531
|
+
}),
|
|
532
|
+
annotations: AUTH_READ,
|
|
533
|
+
example: { user: 'ana@example.com' }
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
name: 'list_auth_logs',
|
|
537
|
+
title: 'Read the Auth audit log',
|
|
538
|
+
description: "The Auth tenant's audit log, newest first: logins, failed logins (`status: failed`), sign-ups, password resets, admin changes (`type` prefix `admin.user`). Filter by user to answer 'why can't X log in?' — the failed attempts say why.",
|
|
539
|
+
input: {
|
|
540
|
+
email: z.string().optional().describe("Only this user's events (exact email)"),
|
|
541
|
+
user: z.string().optional().describe('Only this user id (user_…)'),
|
|
542
|
+
type: z.string().optional().describe('Exact event type, e.g. user.login, user.signup'),
|
|
543
|
+
origin: z.string().optional().describe('Type prefix, e.g. oauth, admin'),
|
|
544
|
+
status: z.enum(['success', 'failed', 'skipped', 'info']).optional(),
|
|
545
|
+
since: time.optional().describe('30m, 24h, 7d or a date (default 7d)'),
|
|
546
|
+
until: time.optional(),
|
|
547
|
+
limit,
|
|
548
|
+
tenant,
|
|
549
|
+
project
|
|
550
|
+
},
|
|
551
|
+
command: ['auth', 'logs', 'list'],
|
|
552
|
+
flags: a => [
|
|
553
|
+
...opt('--email', a.email),
|
|
554
|
+
...opt('--user', a.user),
|
|
555
|
+
...opt('--type', a.type),
|
|
556
|
+
...opt('--origin', a.origin),
|
|
557
|
+
...opt('--status', a.status),
|
|
558
|
+
'--since',
|
|
559
|
+
a.since ?? '7d',
|
|
560
|
+
...opt('--until', a.until),
|
|
561
|
+
'--expand-user',
|
|
562
|
+
...limit_flag(a),
|
|
563
|
+
...auth_scope(a)
|
|
564
|
+
],
|
|
565
|
+
untrusted: true,
|
|
566
|
+
shape: (d) => ({
|
|
567
|
+
events: (d?.data ?? []).map((l) => ({
|
|
568
|
+
at: l.createdAt,
|
|
569
|
+
type: l.type,
|
|
570
|
+
status: l.status,
|
|
571
|
+
...log_user(l),
|
|
572
|
+
message: l.message,
|
|
573
|
+
ip: l.data?.ip
|
|
574
|
+
})),
|
|
575
|
+
has_more: !!d?.has_more
|
|
576
|
+
}),
|
|
577
|
+
annotations: AUTH_READ,
|
|
578
|
+
example: {
|
|
579
|
+
email: 'ana@example.com',
|
|
580
|
+
type: 'user.login',
|
|
581
|
+
origin: 'oauth',
|
|
582
|
+
status: 'failed',
|
|
583
|
+
since: '24h',
|
|
584
|
+
until: '2026-10-05',
|
|
585
|
+
limit: 20
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
name: 'list_auth_sessions',
|
|
590
|
+
title: "List a user's sessions",
|
|
591
|
+
description: 'The devices a user is signed in on (one row per login session): IP, device, last activity and whether it is still active.',
|
|
592
|
+
input: {
|
|
593
|
+
user: user_ref,
|
|
594
|
+
active: z.boolean().optional().describe('Only active sessions (default true)'),
|
|
595
|
+
tenant,
|
|
596
|
+
project
|
|
597
|
+
},
|
|
598
|
+
command: ['auth', 'sessions', 'list'],
|
|
599
|
+
flags: a => [
|
|
600
|
+
'--user',
|
|
601
|
+
a.user,
|
|
602
|
+
...(a.active === false ? [] : ['--active']),
|
|
603
|
+
'--limit',
|
|
604
|
+
'50',
|
|
605
|
+
...auth_scope(a)
|
|
606
|
+
],
|
|
607
|
+
untrusted: true,
|
|
608
|
+
shape: page((s) => ({
|
|
609
|
+
id: s.id,
|
|
610
|
+
status: s.status,
|
|
611
|
+
ip: s.ip,
|
|
612
|
+
device: s.device_name ?? s.user_agent,
|
|
613
|
+
last_seen_at: s.last_seen_at,
|
|
614
|
+
created_at: s.createdAt
|
|
615
|
+
})),
|
|
616
|
+
annotations: AUTH_READ,
|
|
617
|
+
example: { user: 'ana@example.com', active: true }
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
name: 'list_auth_connections',
|
|
621
|
+
title: 'List login methods',
|
|
622
|
+
description: 'How users can log in to the Auth tenant: social providers (Google, GitHub…), passwordless (email or SMS codes) and username/password, and which are enabled.',
|
|
623
|
+
input: { tenant, project },
|
|
624
|
+
command: ['auth', 'connections', 'list'],
|
|
625
|
+
flags: a => ['--limit', '100', ...auth_scope(a)],
|
|
626
|
+
annotations: AUTH_READ,
|
|
627
|
+
example: { tenant: 'account_1' }
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
name: 'list_auth_clients',
|
|
631
|
+
title: 'List Auth applications',
|
|
632
|
+
description: 'The applications (OAuth clients) of the Auth tenant: web apps, SPAs, native apps and machine-to-machine clients.',
|
|
633
|
+
input: { search: z.string().optional(), tenant, project },
|
|
634
|
+
command: ['auth', 'clients', 'list'],
|
|
635
|
+
flags: a => [...opt('-q', a.search), '--limit', '100', ...auth_scope(a)],
|
|
636
|
+
shape: page((c) => ({
|
|
637
|
+
id: c.id,
|
|
638
|
+
client_id: c.client_id,
|
|
639
|
+
name: c.name,
|
|
640
|
+
application_type: c.application_type,
|
|
641
|
+
grant_types: c.grant_types
|
|
642
|
+
})),
|
|
643
|
+
annotations: AUTH_READ,
|
|
644
|
+
example: { search: 'web', tenant: 'account_1' }
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
name: 'list_github_repos',
|
|
648
|
+
title: 'List deployable GitHub repositories',
|
|
649
|
+
description: 'The GitHub repositories Faable can deploy: those the Faable GitHub App is installed on. Use it before create_app to find the repository the user means. When the app is not installed (or the repository is missing), give the user `install_url` — installing it is a step in their browser.',
|
|
650
|
+
input: {
|
|
651
|
+
query: z.string().optional().describe('Filter by repository name'),
|
|
652
|
+
limit: z.number().int().min(1).max(100).optional()
|
|
653
|
+
},
|
|
654
|
+
command: ['deploy', 'github', 'repos'],
|
|
655
|
+
flags: a => [...opt('-q', a.query), ...opt('--limit', a.limit)],
|
|
656
|
+
annotations: READ,
|
|
657
|
+
example: { query: 'web', limit: 10 }
|
|
658
|
+
},
|
|
342
659
|
{
|
|
343
660
|
name: 'deploy_app',
|
|
344
661
|
title: 'Deploy an app',
|
|
345
|
-
description: "Build and deploy the latest commit of the app's deploy branch on Faable's servers — the same as a git push. Nothing is uploaded from this machine.
|
|
346
|
-
input: {
|
|
662
|
+
description: "Build and deploy the latest commit of the app's deploy branch on Faable's servers — the same as a git push. Nothing is uploaded from this machine. With `wait`, returns once it is live (with the URL) or failed (with the reason and whether it is the user's code or Faable's); without it, returns the deployment id at once. Only for apps with push-to-deploy.",
|
|
663
|
+
input: {
|
|
664
|
+
app,
|
|
665
|
+
project,
|
|
666
|
+
wait: z
|
|
667
|
+
.boolean()
|
|
668
|
+
.optional()
|
|
669
|
+
.describe('Wait until live or failed, up to 5 minutes (default false)')
|
|
670
|
+
},
|
|
347
671
|
command: ['deploy', 'trigger'],
|
|
348
|
-
flags: target,
|
|
672
|
+
flags: a => [...target(a), ...(a.wait ? ['--wait', '--timeout', '300'] : [])],
|
|
673
|
+
timeout_ms: 330_000,
|
|
349
674
|
annotations: {
|
|
350
675
|
readOnlyHint: false,
|
|
351
676
|
destructiveHint: false,
|
|
@@ -372,6 +697,10 @@ const TOOLS = [
|
|
|
372
697
|
.boolean()
|
|
373
698
|
.optional()
|
|
374
699
|
.describe('Start the first deploy (default true)'),
|
|
700
|
+
wait: z
|
|
701
|
+
.boolean()
|
|
702
|
+
.optional()
|
|
703
|
+
.describe('Wait for the first deploy to be live (returns the URL) or to fail, up to 5 minutes'),
|
|
375
704
|
project
|
|
376
705
|
},
|
|
377
706
|
command: ['deploy', 'apps', 'create'],
|
|
@@ -381,15 +710,17 @@ const TOOLS = [
|
|
|
381
710
|
...opt('--name', a.name),
|
|
382
711
|
...opt('--branch', a.branch),
|
|
383
712
|
...(a.deploy === false ? ['--no-deploy'] : []),
|
|
713
|
+
...(a.wait && a.deploy !== false ? ['--wait', '--timeout', '300'] : []),
|
|
384
714
|
...scope(a)
|
|
385
715
|
],
|
|
716
|
+
timeout_ms: 330_000,
|
|
386
717
|
write: true,
|
|
387
718
|
annotations: {
|
|
388
719
|
readOnlyHint: false,
|
|
389
720
|
destructiveHint: false,
|
|
390
721
|
openWorldHint: true
|
|
391
722
|
},
|
|
392
|
-
example: { repo: 'acme/web', name: 'web', branch: 'main',
|
|
723
|
+
example: { repo: 'acme/web', name: 'web', branch: 'main', wait: true }
|
|
393
724
|
},
|
|
394
725
|
{
|
|
395
726
|
name: 'redeploy',
|
|
@@ -494,7 +825,97 @@ const TOOLS = [
|
|
|
494
825
|
root_dir: 'apps/web',
|
|
495
826
|
mode: 'push'
|
|
496
827
|
}
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
name: 'deploy_directory',
|
|
831
|
+
title: 'Deploy a local directory',
|
|
832
|
+
description: "Upload a directory of this machine and deploy it (`faable deploy`): Faable builds it and serves it. Pass `app` to deploy to an existing app, or `create` to make a new one with that name. Waits until it is live and returns the URL — or why it failed and whether that is the user's code (`user`) or Faable (`platform`). Only on the local server (`npx @faable/faable mcp`). Prefer create_app/deploy_app when the code is on GitHub: a push then deploys on its own.",
|
|
833
|
+
input: {
|
|
834
|
+
workdir: z
|
|
835
|
+
.string()
|
|
836
|
+
.regex(/^\//, 'an absolute path')
|
|
837
|
+
.describe('Absolute path of the directory to deploy'),
|
|
838
|
+
app: z.string().optional().describe('Existing app id, name or slug'),
|
|
839
|
+
create: z
|
|
840
|
+
.string()
|
|
841
|
+
.optional()
|
|
842
|
+
.describe('Name of a NEW app to create and deploy to (instead of `app`)'),
|
|
843
|
+
project
|
|
844
|
+
},
|
|
845
|
+
command: ['deploy', 'launch'],
|
|
846
|
+
flags: a => [
|
|
847
|
+
'--workdir',
|
|
848
|
+
a.workdir,
|
|
849
|
+
...opt('--app', a.app),
|
|
850
|
+
...opt('--create', a.create),
|
|
851
|
+
'--yes',
|
|
852
|
+
...scope(a)
|
|
853
|
+
],
|
|
854
|
+
write: true,
|
|
855
|
+
local_only: true,
|
|
856
|
+
timeout_ms: 900_000,
|
|
857
|
+
annotations: {
|
|
858
|
+
readOnlyHint: false,
|
|
859
|
+
destructiveHint: false,
|
|
860
|
+
openWorldHint: true
|
|
861
|
+
},
|
|
862
|
+
example: { workdir: '/home/me/web', app: 'web' }
|
|
863
|
+
},
|
|
864
|
+
// ── Faable Auth writes: only with `faable mcp --writes` ───────────────────
|
|
865
|
+
{
|
|
866
|
+
name: 'suspend_auth_user',
|
|
867
|
+
title: 'Suspend a user',
|
|
868
|
+
description: "Suspend one user of the Auth tenant: they can no longer log in, and their sessions and refresh tokens stop working (access tokens already issued live until they expire). Reversible with reinstate_auth_user. Confirm with get_auth_user that it is the right person first, and say why in `reason` — it is recorded on the user.",
|
|
869
|
+
input: {
|
|
870
|
+
user: user_ref,
|
|
871
|
+
reason: z.string().max(512).describe('Why — recorded on the user'),
|
|
872
|
+
tenant,
|
|
873
|
+
project
|
|
874
|
+
},
|
|
875
|
+
command: ['auth', 'users', 'suspend'],
|
|
876
|
+
flags: a => [a.user, '--reason', a.reason, '--yes', ...auth_scope(a)],
|
|
877
|
+
write: true,
|
|
878
|
+
annotations: AUTH_WRITE,
|
|
879
|
+
example: { user: 'ana@example.com', reason: 'chargeback' }
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
name: 'reinstate_auth_user',
|
|
883
|
+
title: 'Reinstate a user',
|
|
884
|
+
description: 'Lift the suspension of a user: they can log in again.',
|
|
885
|
+
input: { user: user_ref, tenant, project },
|
|
886
|
+
command: ['auth', 'users', 'reinstate'],
|
|
887
|
+
flags: a => [a.user, '--yes', ...auth_scope(a)],
|
|
888
|
+
write: true,
|
|
889
|
+
annotations: AUTH_WRITE,
|
|
890
|
+
example: { user: 'ana@example.com' }
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
name: 'revoke_auth_sessions',
|
|
894
|
+
title: 'Sign a user out everywhere',
|
|
895
|
+
description: "End every active session of a user (the 'sign out of all devices' button): they have to log in again on each device. Access tokens already issued live until they expire.",
|
|
896
|
+
input: { user: user_ref, tenant, project },
|
|
897
|
+
command: ['auth', 'sessions', 'revoke'],
|
|
898
|
+
flags: a => ['--user', a.user, '--yes', ...auth_scope(a)],
|
|
899
|
+
write: true,
|
|
900
|
+
annotations: AUTH_WRITE,
|
|
901
|
+
example: { user: 'ana@example.com' }
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
name: 'send_password_setup',
|
|
905
|
+
title: 'Send a password setup email',
|
|
906
|
+
description: 'Send a user the email (or code) to set or reset their password — to invite a user created by hand, or to unblock one who forgot it.',
|
|
907
|
+
input: {
|
|
908
|
+
user: user_ref,
|
|
909
|
+
channel: z.enum(['email', 'sms', 'whatsapp']).optional(),
|
|
910
|
+
tenant,
|
|
911
|
+
project
|
|
912
|
+
},
|
|
913
|
+
command: ['auth', 'users', 'password-setup'],
|
|
914
|
+
flags: a => [a.user, ...opt('--channel', a.channel), '--yes', ...auth_scope(a)],
|
|
915
|
+
write: true,
|
|
916
|
+
annotations: { ...AUTH_WRITE, idempotentHint: false },
|
|
917
|
+
example: { user: 'ana@example.com', channel: 'email' }
|
|
497
918
|
}
|
|
498
919
|
];
|
|
499
920
|
|
|
500
|
-
export { TOOLS, env_quote };
|
|
921
|
+
export { TOOLS, env_quote, product_of };
|