@faable/faable 4.5.0 → 4.5.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/dist/api/FaableApi.js
CHANGED
|
@@ -88,9 +88,14 @@ class FaableApi {
|
|
|
88
88
|
// Uniform handling for an expired/invalid session across every
|
|
89
89
|
// command, regardless of which endpoint returned the 401.
|
|
90
90
|
if (res.status === 401) {
|
|
91
|
-
|
|
91
|
+
// With an API key there is no session to renew: the key itself
|
|
92
|
+
// is wrong or was revoked (the hosted MCP server runs on keys).
|
|
93
|
+
const by_key = !!process.env.FAABLE_API_KEY && !process.env.FAABLE_TOKEN;
|
|
94
|
+
const expired = new Error(by_key
|
|
95
|
+
? 'This Faable API key is invalid or was revoked. Create a new one in the dashboard (project settings → API Keys).'
|
|
96
|
+
: 'Your Faable session has expired or is invalid. Run `faable login` to sign in again.', { cause: error });
|
|
92
97
|
expired.status = 401;
|
|
93
|
-
expired.code = 'session_expired';
|
|
98
|
+
expired.code = by_key ? 'apikey_invalid' : 'session_expired';
|
|
94
99
|
throw expired;
|
|
95
100
|
}
|
|
96
101
|
const serverMessage = res.data?.message || res.statusText || 'Unknown Error';
|
|
@@ -227,8 +232,12 @@ class FaableApi {
|
|
|
227
232
|
async updateApp(app_id, params) {
|
|
228
233
|
return data(this.client.post(`/app/${app_id}`, params));
|
|
229
234
|
}
|
|
230
|
-
|
|
231
|
-
|
|
235
|
+
// Team pinned via header: the api answers 400 team_required without it
|
|
236
|
+
// (it needs the project to check the plan and the repository guard).
|
|
237
|
+
async linkRepository(app_id, params, team) {
|
|
238
|
+
return data(this.client.post(`/app/${app_id}/link-repository`, params, {
|
|
239
|
+
headers: projectHeader(team)
|
|
240
|
+
}));
|
|
232
241
|
}
|
|
233
242
|
// Organizations/accounts where the Faable GitHub App is installed.
|
|
234
243
|
async listGithubInstallations() {
|
|
@@ -259,11 +268,11 @@ class FaableApi {
|
|
|
259
268
|
}
|
|
260
269
|
// Build and deploy the current head of the deploy branch server-side —
|
|
261
270
|
// the same path a push webhook takes, same-commit dedupe included.
|
|
262
|
-
async deployNow(app_id) {
|
|
271
|
+
async deployNow(app_id, team) {
|
|
263
272
|
return data(
|
|
264
273
|
// deployment_id: the build this call started (api ≥ the release that
|
|
265
274
|
// added it; absent from older ones).
|
|
266
|
-
this.client.post(`/app/${app_id}/deploy
|
|
275
|
+
this.client.post(`/app/${app_id}/deploy`, undefined, { headers: projectHeader(team) }));
|
|
267
276
|
}
|
|
268
277
|
// Rebuild a failed deployment from its recorded source (CAS manifest or
|
|
269
278
|
// git ref). The API enforces the guards: failed phase only, never older
|
|
@@ -81,7 +81,7 @@ const apps_create = {
|
|
|
81
81
|
linked = await api.linkRepository(app.id, {
|
|
82
82
|
repository,
|
|
83
83
|
...(args.branch ? { github_branch: args.branch } : {})
|
|
84
|
-
});
|
|
84
|
+
}, app.team);
|
|
85
85
|
}
|
|
86
86
|
catch (err) {
|
|
87
87
|
await api.deleteApp(app.id, app.team).catch(() => {
|
|
@@ -102,7 +102,7 @@ const apps_create = {
|
|
|
102
102
|
let first_deploy = null;
|
|
103
103
|
if (repository && args.deploy && linked.deploy_trigger === 'webhook') {
|
|
104
104
|
try {
|
|
105
|
-
const started = await api.deployNow(app.id);
|
|
105
|
+
const started = await api.deployNow(app.id, app.team);
|
|
106
106
|
first_deploy = {
|
|
107
107
|
commit: started.commit,
|
|
108
108
|
branch: started.branch,
|
|
@@ -20,7 +20,7 @@ const trigger = {
|
|
|
20
20
|
const app = await ctx.api.getApp(app_id);
|
|
21
21
|
// Takes the exact same path a git push would (same-commit dedupe
|
|
22
22
|
// included) — the API answers with an actionable refusal otherwise.
|
|
23
|
-
const result = await ctx.api.deployNow(app_id);
|
|
23
|
+
const result = await ctx.api.deployNow(app_id, app.team);
|
|
24
24
|
if (args.json)
|
|
25
25
|
return print_json({ app_id, ...result });
|
|
26
26
|
log.info(`🚀 Building ${result.commit.slice(0, 7)} (${result.branch}) of ${app.name} server-side${result.deployment_id ? ` as ${result.deployment_id}` : ''}.`);
|
|
@@ -77,7 +77,7 @@ const link = {
|
|
|
77
77
|
// access to the repository before persisting the link.
|
|
78
78
|
let linked;
|
|
79
79
|
try {
|
|
80
|
-
linked = await api.linkRepository(selectedApp.id, { repository: gitUrl });
|
|
80
|
+
linked = await api.linkRepository(selectedApp.id, { repository: gitUrl }, selectedApp.team);
|
|
81
81
|
}
|
|
82
82
|
catch (err) {
|
|
83
83
|
const code = err?.code;
|
package/dist/mcp/http.js
CHANGED
|
@@ -15,14 +15,14 @@ import { TOOLS } from './tools.js';
|
|
|
15
15
|
// Streamable HTTP, stateless: every POST /mcp builds a server and a transport
|
|
16
16
|
// for that one request, so any instance can answer anything. The caller
|
|
17
17
|
// authenticates with a Faable API key (dashboard → project settings → API
|
|
18
|
-
// keys).
|
|
19
|
-
//
|
|
20
|
-
//
|
|
18
|
+
// keys). A key belongs to the project it was created in, and the api holds it
|
|
19
|
+
// there (api 2.199.2, backlog §302): it never acts as its owner nor reaches
|
|
20
|
+
// another project. Each tool call runs the CLI as a child
|
|
21
21
|
// with THAT key in its environment and an empty HOME of its own — two
|
|
22
22
|
// callers' credentials never meet in one process, and the key is never
|
|
23
23
|
// logged.
|
|
24
24
|
//
|
|
25
|
-
// POST /mcp
|
|
25
|
+
// POST /mcp the key's project
|
|
26
26
|
// POST /mcp/<project> pinned to one project (id, name or slug)
|
|
27
27
|
// ?mode=write also the reversible writes
|
|
28
28
|
// ?readonly=1 reads only — not even deploy_app
|
|
@@ -151,7 +151,7 @@ const llms_txt = (base = PUBLIC_URL) => [
|
|
|
151
151
|
'',
|
|
152
152
|
'> Connects Claude, Cursor and any MCP client to Faable Deploy: read apps, deployments, build and runtime logs, traffic and domains, and deploy — without leaving the editor.',
|
|
153
153
|
'',
|
|
154
|
-
`Endpoint: ${base}/mcp (Streamable HTTP). Authenticate with a Faable API key: \`Authorization: Bearer <key>\` (create one in the dashboard, project settings → API keys). The key
|
|
154
|
+
`Endpoint: ${base}/mcp (Streamable HTTP). Authenticate with a Faable API key: \`Authorization: Bearer <key>\` (create one in the dashboard, project settings → API keys). The key belongs to one project and only acts there.`,
|
|
155
155
|
'',
|
|
156
156
|
`- \`${base}/mcp/<project>\` pins every call to one project.`,
|
|
157
157
|
'- `?mode=write` adds the reversible writes (create apps, set secrets, add domains, retry or cancel builds, change deploy settings). Nothing destructive is exposed.',
|
package/dist/mcp/server.js
CHANGED
|
@@ -32,6 +32,7 @@ const NEXT_STEP = {
|
|
|
32
32
|
session_expired: 'The Faable session expired. Ask the user to run `faable login` in a terminal, then retry.',
|
|
33
33
|
account_suspended: 'This Faable account is suspended; the user should contact support@faable.com.',
|
|
34
34
|
apikey_session: 'The user is logged in with an API key; this needs `faable login` without --apikey.',
|
|
35
|
+
apikey_invalid: 'The API key this server was given is invalid or was revoked. The user needs a new one from the dashboard (project settings → API Keys).',
|
|
35
36
|
not_found: 'Check the id or name — list_apps / list_deployments show what exists.',
|
|
36
37
|
forbidden: 'This session has no access there — check the project (list_projects).',
|
|
37
38
|
app_required: 'Pass `app` (id, name or slug).',
|