@lovinka/deployik-mcp 0.1.1 → 0.1.5
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/README.md +72 -15
- package/dist/client/http.js +36 -0
- package/dist/client/http.js.map +1 -1
- package/dist/config/audit.js +12 -1
- package/dist/config/audit.js.map +1 -1
- package/dist/config/binding.js +85 -18
- package/dist/config/binding.js.map +1 -1
- package/dist/config/cache.js +9 -1
- package/dist/config/cache.js.map +1 -1
- package/dist/index.js +119 -1
- package/dist/index.js.map +1 -1
- package/dist/install-mcp.js +133 -0
- package/dist/install-mcp.js.map +1 -0
- package/dist/install-skill.js +74 -0
- package/dist/install-skill.js.map +1 -0
- package/dist/install.js +80 -0
- package/dist/install.js.map +1 -0
- package/dist/lib/normalize.js +60 -0
- package/dist/lib/normalize.js.map +1 -0
- package/dist/resolve/project.js +26 -6
- package/dist/resolve/project.js.map +1 -1
- package/dist/server.js +3 -2
- package/dist/server.js.map +1 -1
- package/dist/tools/env.js +17 -12
- package/dist/tools/env.js.map +1 -1
- package/dist/tools/help.js +120 -0
- package/dist/tools/help.js.map +1 -1
- package/dist/tools/secrets.js +17 -12
- package/dist/tools/secrets.js.map +1 -1
- package/dist/tools/workflows.js +176 -24
- package/dist/tools/workflows.js.map +1 -1
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/package.json +2 -2
package/dist/tools/help.js
CHANGED
|
@@ -30,5 +30,125 @@ export function registerHelpTools(server, ctx) {
|
|
|
30
30
|
return { text: recipe.body, data: { topic: recipe.topic, title: recipe.title } };
|
|
31
31
|
},
|
|
32
32
|
});
|
|
33
|
+
registerTool(server, ctx, {
|
|
34
|
+
name: "find_help",
|
|
35
|
+
description: "Answer 'where do I set this?' / 'how do I X?' style questions by ranking the bundled Deployik recipes against your question. Returns the best matching recipe in full plus a short list of runners-up. Use this when you don't know which exact topic to ask for — just describe the goal in plain English.",
|
|
36
|
+
inputSchema: {
|
|
37
|
+
question: z.string().describe("Plain-English question or goal, e.g. 'where do I set Stripe API keys for the live site?'"),
|
|
38
|
+
max_results: z.number().int().positive().max(8).default(3),
|
|
39
|
+
},
|
|
40
|
+
annotations: { readOnlyHint: true },
|
|
41
|
+
handler: async (args) => {
|
|
42
|
+
const ranked = rankRecipes(args.question);
|
|
43
|
+
if (ranked.length === 0) {
|
|
44
|
+
return {
|
|
45
|
+
text: `No matching recipe found for '${args.question}'. Call list_recipes to see the full catalog.`,
|
|
46
|
+
data: { matches: [] },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const top = ranked[0];
|
|
50
|
+
const runnersUp = ranked.slice(1, args.max_results);
|
|
51
|
+
const summary = [
|
|
52
|
+
`Best match: ${top.recipe.title} (topic: ${top.recipe.topic}, score: ${top.score})`,
|
|
53
|
+
`Why: ${top.matchedTerms.length > 0 ? "matched terms — " + top.matchedTerms.join(", ") : "general overlap"}`,
|
|
54
|
+
``,
|
|
55
|
+
`── Recipe body ──`,
|
|
56
|
+
top.recipe.body,
|
|
57
|
+
];
|
|
58
|
+
if (runnersUp.length > 0) {
|
|
59
|
+
summary.push(``, `── Other relevant topics ──`);
|
|
60
|
+
for (const r of runnersUp) {
|
|
61
|
+
summary.push(` • ${r.recipe.topic.padEnd(22)} ${r.recipe.title} (score: ${r.score})`);
|
|
62
|
+
}
|
|
63
|
+
summary.push(``, `Call get_recipe({ topic: "<name>" }) to fetch any of those in full.`);
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
text: summary.join("\n"),
|
|
67
|
+
data: {
|
|
68
|
+
best: { topic: top.recipe.topic, title: top.recipe.title, score: top.score, matched: top.matchedTerms },
|
|
69
|
+
runners_up: runnersUp.map((r) => ({ topic: r.recipe.topic, title: r.recipe.title, score: r.score })),
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const STOPWORDS = new Set([
|
|
76
|
+
"the", "a", "an", "is", "are", "do", "does", "did", "i", "me", "my", "we", "our", "you", "your",
|
|
77
|
+
"to", "of", "in", "on", "at", "for", "with", "this", "that", "these", "those", "how", "where",
|
|
78
|
+
"what", "when", "why", "which", "can", "could", "should", "would", "please", "just", "like",
|
|
79
|
+
"set", "setting", "add", "change", "update", "make", "need", "want", "get", "go", "be", "new",
|
|
80
|
+
"deployik", "app", "site", "project", "value", "using", "via", "up", "down",
|
|
81
|
+
]);
|
|
82
|
+
const SYNONYMS = {
|
|
83
|
+
// env vars
|
|
84
|
+
env: "envvars", envs: "envvars", environment: "envvars", variable: "envvars", variables: "envvars",
|
|
85
|
+
config: "envvars", configuration: "envvars", "next-public": "envvars",
|
|
86
|
+
// secrets
|
|
87
|
+
secret: "envvars", secrets: "envvars", key: "envvars", apikey: "envvars",
|
|
88
|
+
stripe: "envvars", token: "envvars", password: "envvars",
|
|
89
|
+
// domain
|
|
90
|
+
domain: "domain", domains: "domain", url: "domain", ssl: "domain", dns: "domain",
|
|
91
|
+
custom: "domain", subdomain: "domain", https: "domain", cert: "domain", certificate: "domain",
|
|
92
|
+
// auto-deploy
|
|
93
|
+
autodeploy: "autodeploy", webhook: "autodeploy", auto: "autodeploy", push: "autodeploy",
|
|
94
|
+
github: "autodeploy", branch: "autodeploy", trigger: "autodeploy",
|
|
95
|
+
// password protection
|
|
96
|
+
protect: "protection", protection: "protection", protected: "protection", lock: "protection",
|
|
97
|
+
preview: "preview", private: "protection",
|
|
98
|
+
// email / contact form
|
|
99
|
+
email: "email", contact: "email", smtp: "email", recaptcha: "email", form: "email",
|
|
100
|
+
webglobe: "email", mail: "email", "form-email": "email",
|
|
101
|
+
// rollback
|
|
102
|
+
rollback: "rollback", revert: "rollback", undo: "rollback", restore: "rollback", previous: "rollback",
|
|
103
|
+
// create / connect project
|
|
104
|
+
connect: "create", create: "create", new: "create", setup: "create", import: "create",
|
|
105
|
+
repo: "create", repository: "create", deploy: "create", first: "create",
|
|
106
|
+
};
|
|
107
|
+
function rankRecipes(question) {
|
|
108
|
+
const tokens = tokenize(question);
|
|
109
|
+
if (tokens.length === 0)
|
|
110
|
+
return [];
|
|
111
|
+
// Expand tokens through the synonym map (a token contributes both itself and any
|
|
112
|
+
// canonical bucket it belongs to).
|
|
113
|
+
const expanded = new Set();
|
|
114
|
+
for (const t of tokens) {
|
|
115
|
+
expanded.add(t);
|
|
116
|
+
if (SYNONYMS[t])
|
|
117
|
+
expanded.add(SYNONYMS[t]);
|
|
118
|
+
}
|
|
119
|
+
const ranked = [];
|
|
120
|
+
for (const recipe of listRecipes()) {
|
|
121
|
+
const haystack = (recipe.topic + " " + recipe.title + " " + recipe.summary + " " + recipe.body).toLowerCase();
|
|
122
|
+
let score = 0;
|
|
123
|
+
const matched = [];
|
|
124
|
+
for (const term of expanded) {
|
|
125
|
+
if (!haystack.includes(term))
|
|
126
|
+
continue;
|
|
127
|
+
// Weight: matches in topic/title are worth more than body matches.
|
|
128
|
+
const inTopic = recipe.topic.toLowerCase().includes(term);
|
|
129
|
+
const inTitle = recipe.title.toLowerCase().includes(term);
|
|
130
|
+
const inSummary = recipe.summary.toLowerCase().includes(term);
|
|
131
|
+
const weight = inTopic ? 5 : inTitle ? 3 : inSummary ? 2 : 1;
|
|
132
|
+
score += weight;
|
|
133
|
+
matched.push(term);
|
|
134
|
+
}
|
|
135
|
+
if (score > 0)
|
|
136
|
+
ranked.push({ recipe, score, matchedTerms: matched });
|
|
137
|
+
}
|
|
138
|
+
ranked.sort((a, b) => b.score - a.score);
|
|
139
|
+
return ranked;
|
|
140
|
+
}
|
|
141
|
+
function tokenize(input) {
|
|
142
|
+
const out = [];
|
|
143
|
+
for (const raw of input.toLowerCase().split(/[^a-z0-9]+/)) {
|
|
144
|
+
if (!raw)
|
|
145
|
+
continue;
|
|
146
|
+
if (raw.length < 2)
|
|
147
|
+
continue;
|
|
148
|
+
if (STOPWORDS.has(raw))
|
|
149
|
+
continue;
|
|
150
|
+
out.push(raw);
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
33
153
|
}
|
|
34
154
|
//# sourceMappingURL=help.js.map
|
package/dist/tools/help.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/tools/help.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,YAAY,EAAoB,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/tools/help.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/secrets.js
CHANGED
|
@@ -3,7 +3,8 @@ import { registerTool } from "./_helpers.js";
|
|
|
3
3
|
import { resolveProject } from "../resolve/project.js";
|
|
4
4
|
import { checkSafety } from "../lib/safety.js";
|
|
5
5
|
import { renderDryRun } from "../lib/format.js";
|
|
6
|
-
|
|
6
|
+
import { requireScope } from "../lib/normalize.js";
|
|
7
|
+
const SCOPE = z.string().describe("'shared' (both envs), 'preview' (test/staging/dev), or 'production' (live/prod). Aliases accepted.");
|
|
7
8
|
export function registerSecretTools(server, ctx) {
|
|
8
9
|
registerTool(server, ctx, {
|
|
9
10
|
name: "list_secrets",
|
|
@@ -15,9 +16,10 @@ export function registerSecretTools(server, ctx) {
|
|
|
15
16
|
},
|
|
16
17
|
annotations: { readOnlyHint: true },
|
|
17
18
|
handler: async (args) => {
|
|
19
|
+
const environment = requireScope(args.environment);
|
|
18
20
|
const { project } = await resolveProject(ctx, args);
|
|
19
21
|
const vars = await ctx.client.request(`/projects/${project.id}/secrets`, {
|
|
20
|
-
query: { environment
|
|
22
|
+
query: { environment },
|
|
21
23
|
});
|
|
22
24
|
const text = vars.length === 0
|
|
23
25
|
? "(no secrets in this scope)"
|
|
@@ -36,12 +38,13 @@ export function registerSecretTools(server, ctx) {
|
|
|
36
38
|
value: z.string(),
|
|
37
39
|
},
|
|
38
40
|
handler: async (args) => {
|
|
41
|
+
const environment = requireScope(args.environment);
|
|
39
42
|
const { project } = await resolveProject(ctx, args);
|
|
40
43
|
await ctx.client.request(`/projects/${project.id}/secrets`, {
|
|
41
44
|
method: "POST",
|
|
42
|
-
body: { environment
|
|
45
|
+
body: { environment, key: args.key, value: args.value },
|
|
43
46
|
});
|
|
44
|
-
return { text: `Set secret ${args.key} in ${
|
|
47
|
+
return { text: `Set secret ${args.key} in ${environment} scope of ${project.name}.` };
|
|
45
48
|
},
|
|
46
49
|
});
|
|
47
50
|
registerTool(server, ctx, {
|
|
@@ -58,15 +61,16 @@ export function registerSecretTools(server, ctx) {
|
|
|
58
61
|
annotations: { destructiveHint: true },
|
|
59
62
|
audit: true,
|
|
60
63
|
handler: async (args) => {
|
|
64
|
+
const environment = requireScope(args.environment);
|
|
61
65
|
const { project } = await resolveProject(ctx, args);
|
|
62
|
-
const tier =
|
|
66
|
+
const tier = environment === "production" ? "destructive_production" : "destructive";
|
|
63
67
|
const safety = checkSafety({
|
|
64
68
|
toolName: "bulk_set_secrets",
|
|
65
69
|
tier,
|
|
66
70
|
expectedName: project.name,
|
|
67
71
|
impact: {
|
|
68
72
|
project: project.name,
|
|
69
|
-
scope:
|
|
73
|
+
scope: environment,
|
|
70
74
|
replacing_with: args.variables.length,
|
|
71
75
|
note: "Existing secrets not in this set will be DELETED.",
|
|
72
76
|
},
|
|
@@ -75,9 +79,9 @@ export function registerSecretTools(server, ctx) {
|
|
|
75
79
|
return { text: renderDryRun(safety.dryRun) };
|
|
76
80
|
const res = await ctx.client.request(`/projects/${project.id}/secrets`, {
|
|
77
81
|
method: "PUT",
|
|
78
|
-
body: { environment
|
|
82
|
+
body: { environment, variables: args.variables },
|
|
79
83
|
});
|
|
80
|
-
return { text: `Replaced ${
|
|
84
|
+
return { text: `Replaced ${environment} secrets on ${project.name} — now ${res.count} keys.` };
|
|
81
85
|
},
|
|
82
86
|
});
|
|
83
87
|
registerTool(server, ctx, {
|
|
@@ -94,21 +98,22 @@ export function registerSecretTools(server, ctx) {
|
|
|
94
98
|
annotations: { destructiveHint: true },
|
|
95
99
|
audit: true,
|
|
96
100
|
handler: async (args) => {
|
|
101
|
+
const environment = requireScope(args.environment);
|
|
97
102
|
const { project } = await resolveProject(ctx, args);
|
|
98
|
-
const tier =
|
|
103
|
+
const tier = environment === "production" ? "destructive_production" : "destructive";
|
|
99
104
|
const safety = checkSafety({
|
|
100
105
|
toolName: "delete_secret",
|
|
101
106
|
tier,
|
|
102
107
|
expectedName: project.name,
|
|
103
|
-
impact: { project: project.name, scope:
|
|
108
|
+
impact: { project: project.name, scope: environment, key: args.key },
|
|
104
109
|
}, { confirm: args.confirm, confirm_name: args.confirm_name });
|
|
105
110
|
if (!safety.proceed)
|
|
106
111
|
return { text: renderDryRun(safety.dryRun) };
|
|
107
112
|
await ctx.client.request(`/projects/${project.id}/secrets/${encodeURIComponent(args.key)}`, {
|
|
108
113
|
method: "DELETE",
|
|
109
|
-
query: { environment
|
|
114
|
+
query: { environment },
|
|
110
115
|
});
|
|
111
|
-
return { text: `Deleted secret ${args.key} from ${
|
|
116
|
+
return { text: `Deleted secret ${args.key} from ${environment} scope of ${project.name}.` };
|
|
112
117
|
},
|
|
113
118
|
});
|
|
114
119
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/tools/secrets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,YAAY,EAAoB,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/tools/secrets.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/workflows.js
CHANGED
|
@@ -6,8 +6,10 @@ import { pollUntil, sleep } from "../lib/poll.js";
|
|
|
6
6
|
import { renderDryRun, renderProjectSummary, renderDeploymentSummary, renderDomainsList, renderVolumesList, renderProtection } from "../lib/format.js";
|
|
7
7
|
import { formatLogs } from "../lib/logs.js";
|
|
8
8
|
import { writeBinding, ensureGitignore, readBinding } from "../config/binding.js";
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
import { requireEnvironment, requireScope } from "../lib/normalize.js";
|
|
10
|
+
// Accept any string so we can normalize 'prod'/'live'/'staging' charitably.
|
|
11
|
+
const ENV = z.string().describe("'preview' (test) or 'production' (live). Also accepts: prod, live, staging, dev, test.");
|
|
12
|
+
const VAR_SCOPE = z.string().describe("'shared' (both envs), 'preview' (test only), or 'production' (live only). Also accepts: both, all, prod, live, staging.");
|
|
11
13
|
const TERMINAL_STATUSES = new Set(["live", "failed", "replaced", "rolled_back"]);
|
|
12
14
|
export function registerWorkflowTools(server, ctx) {
|
|
13
15
|
registerTool(server, ctx, {
|
|
@@ -17,24 +19,26 @@ export function registerWorkflowTools(server, ctx) {
|
|
|
17
19
|
project: z.string().optional(),
|
|
18
20
|
project_id: z.string().optional(),
|
|
19
21
|
workspace: z.string().optional(),
|
|
20
|
-
default_environment:
|
|
22
|
+
default_environment: z.string().optional().describe("Optional default environment: 'preview' or 'production' (also accepts prod/live/staging)."),
|
|
21
23
|
},
|
|
22
24
|
handler: async (args) => {
|
|
23
25
|
const user = await ctx.client.request(`/auth/me`);
|
|
24
26
|
const { project, source } = await resolveProject(ctx, args);
|
|
27
|
+
const defaultEnvironment = args.default_environment ? requireEnvironment(args.default_environment) : undefined;
|
|
25
28
|
const binding = {
|
|
26
29
|
project: project.name,
|
|
27
30
|
workspace: project.organization_name ?? project.organization_id,
|
|
28
|
-
defaultEnvironment
|
|
31
|
+
defaultEnvironment,
|
|
29
32
|
};
|
|
30
|
-
writeBinding(ctx.stateDir, binding);
|
|
33
|
+
writeBinding(ctx.stateDir, ctx.cwd, binding);
|
|
31
34
|
const gitignoreUpdated = ensureGitignore(ctx.cwd);
|
|
32
35
|
const lines = [
|
|
33
36
|
`Bound this repo to Deployik project '${project.name}' (workspace: ${binding.workspace}).`,
|
|
34
37
|
`Authenticated as ${user.username} (${user.role}).`,
|
|
35
38
|
`Resolution source: ${source}.`,
|
|
39
|
+
`Wrote .deployik.json (commit this — it tells teammates which project deploys here).`,
|
|
36
40
|
gitignoreUpdated ? "Added `.deployik/` to .gitignore." : "`.deployik/` already in .gitignore (or no .gitignore at repo root).",
|
|
37
|
-
`
|
|
41
|
+
`Private state dir: ${ctx.stateDir} (cache, token, audit — never commit).`,
|
|
38
42
|
];
|
|
39
43
|
return { text: lines.join("\n"), data: { project, binding, gitignoreUpdated } };
|
|
40
44
|
},
|
|
@@ -96,7 +100,7 @@ export function registerWorkflowTools(server, ctx) {
|
|
|
96
100
|
});
|
|
97
101
|
registerTool(server, ctx, {
|
|
98
102
|
name: "deploy_project",
|
|
99
|
-
description: "
|
|
103
|
+
description: "Start a new deployment. Use environment 'preview' for a test build (auto-deployed to a *.preview.lovinka.com URL) or 'production' for the public live version. With `wait:true`, this tool waits until the build finishes (success or failure) before returning. Production deploys require `confirm:true` + `confirm_name` matching the project slug, so the AI can't accidentally push to live.",
|
|
100
104
|
inputSchema: {
|
|
101
105
|
project_id: z.string().optional(),
|
|
102
106
|
project: z.string().optional(),
|
|
@@ -111,8 +115,9 @@ export function registerWorkflowTools(server, ctx) {
|
|
|
111
115
|
},
|
|
112
116
|
audit: true,
|
|
113
117
|
handler: async (args) => {
|
|
118
|
+
const environment = requireEnvironment(args.environment);
|
|
114
119
|
const { project } = await resolveProject(ctx, args);
|
|
115
|
-
if (
|
|
120
|
+
if (environment === "production") {
|
|
116
121
|
const safety = checkSafety({
|
|
117
122
|
toolName: "deploy_project",
|
|
118
123
|
tier: "destructive_production",
|
|
@@ -122,7 +127,7 @@ export function registerWorkflowTools(server, ctx) {
|
|
|
122
127
|
if (!safety.proceed)
|
|
123
128
|
return { text: renderDryRun(safety.dryRun) };
|
|
124
129
|
}
|
|
125
|
-
const body = { environment
|
|
130
|
+
const body = { environment };
|
|
126
131
|
if (args.branch)
|
|
127
132
|
body.branch = args.branch;
|
|
128
133
|
if (args.create_tag)
|
|
@@ -131,7 +136,7 @@ export function registerWorkflowTools(server, ctx) {
|
|
|
131
136
|
body.tag_name = args.tag_name;
|
|
132
137
|
const deployment = await ctx.client.request(`/projects/${project.id}/deployments`, { method: "POST", body });
|
|
133
138
|
if (!args.wait) {
|
|
134
|
-
return { text: `Triggered ${
|
|
139
|
+
return { text: `Triggered ${environment} deploy.\n${renderDeploymentSummary(deployment)}`, data: deployment };
|
|
135
140
|
}
|
|
136
141
|
const final = await pollUntil(() => ctx.client.request(`/projects/${project.id}/deployments/${deployment.id}`), (d) => TERMINAL_STATUSES.has(d.status), { intervalMs: 2000, timeoutMs: args.timeout_ms, initialDelayMs: 1500 });
|
|
137
142
|
const status = final.value;
|
|
@@ -144,46 +149,48 @@ export function registerWorkflowTools(server, ctx) {
|
|
|
144
149
|
});
|
|
145
150
|
registerTool(server, ctx, {
|
|
146
151
|
name: "set_secret",
|
|
147
|
-
description: "
|
|
152
|
+
description: "Add or change a secret value — API key, database password, Stripe key, anything sensitive. Stored encrypted; only available to the running container at runtime. NEXT_PUBLIC_* keys are NOT allowed here (they need to be baked into the build — use set_env_var). If you don't pass `project`, the tool uses the binding from `.deployik/` in the current folder.",
|
|
148
153
|
inputSchema: {
|
|
149
154
|
project_id: z.string().optional(),
|
|
150
|
-
project: z.string().optional(),
|
|
155
|
+
project: z.string().optional().describe("Project slug like 'my-app'. Omit to use the .deployik binding."),
|
|
151
156
|
environment: VAR_SCOPE.default("production"),
|
|
152
|
-
key: z.string(),
|
|
153
|
-
value: z.string(),
|
|
157
|
+
key: z.string().describe("The variable name, e.g. STRIPE_SECRET_KEY."),
|
|
158
|
+
value: z.string().describe("The actual secret value."),
|
|
154
159
|
},
|
|
155
160
|
handler: async (args) => {
|
|
156
161
|
if (args.key.startsWith("NEXT_PUBLIC_")) {
|
|
157
162
|
return {
|
|
158
|
-
text: `
|
|
163
|
+
text: `NEXT_PUBLIC_* keys can't be secrets — they need to be embedded into the build so the browser can read them. Use set_env_var instead.`,
|
|
159
164
|
isError: true,
|
|
160
165
|
};
|
|
161
166
|
}
|
|
167
|
+
const environment = requireScope(args.environment);
|
|
162
168
|
const { project } = await resolveProject(ctx, args);
|
|
163
169
|
await ctx.client.request(`/projects/${project.id}/secrets`, {
|
|
164
170
|
method: "POST",
|
|
165
|
-
body: { environment
|
|
171
|
+
body: { environment, key: args.key, value: args.value },
|
|
166
172
|
});
|
|
167
|
-
return { text: `
|
|
173
|
+
return { text: `Saved secret '${args.key}' to ${environment} of ${project.name}.` };
|
|
168
174
|
},
|
|
169
175
|
});
|
|
170
176
|
registerTool(server, ctx, {
|
|
171
177
|
name: "set_env_var",
|
|
172
|
-
description: "
|
|
178
|
+
description: "Add or change an environment variable — a non-sensitive config value or a NEXT_PUBLIC_* value that the browser can see. If you don't pass `project`, the tool uses the binding from `.deployik/` in the current folder. Use 'shared' scope to set the same value for both preview and production.",
|
|
173
179
|
inputSchema: {
|
|
174
180
|
project_id: z.string().optional(),
|
|
175
181
|
project: z.string().optional(),
|
|
176
182
|
environment: VAR_SCOPE.default("production"),
|
|
177
|
-
key: z.string(),
|
|
183
|
+
key: z.string().describe("Variable name. Letters, digits, underscores only. Cannot start with a number."),
|
|
178
184
|
value: z.string(),
|
|
179
185
|
},
|
|
180
186
|
handler: async (args) => {
|
|
187
|
+
const environment = requireScope(args.environment);
|
|
181
188
|
const { project } = await resolveProject(ctx, args);
|
|
182
189
|
await ctx.client.request(`/projects/${project.id}/env`, {
|
|
183
190
|
method: "POST",
|
|
184
|
-
body: { environment
|
|
191
|
+
body: { environment, key: args.key, value: args.value },
|
|
185
192
|
});
|
|
186
|
-
return { text: `
|
|
193
|
+
return { text: `Saved '${args.key}' to ${environment} of ${project.name}.` };
|
|
187
194
|
},
|
|
188
195
|
});
|
|
189
196
|
registerTool(server, ctx, {
|
|
@@ -208,7 +215,7 @@ export function registerWorkflowTools(server, ctx) {
|
|
|
208
215
|
});
|
|
209
216
|
registerTool(server, ctx, {
|
|
210
217
|
name: "tail_latest_logs",
|
|
211
|
-
description: "
|
|
218
|
+
description: "Show me the most recent build logs for a project. Finds the latest deployment, returns the tail with error lines highlighted. Use `follow:true` if a build is still running and you want to wait for it to finish before getting the logs.",
|
|
212
219
|
inputSchema: {
|
|
213
220
|
project_id: z.string().optional(),
|
|
214
221
|
project: z.string().optional(),
|
|
@@ -219,11 +226,12 @@ export function registerWorkflowTools(server, ctx) {
|
|
|
219
226
|
},
|
|
220
227
|
annotations: { readOnlyHint: true },
|
|
221
228
|
handler: async (args) => {
|
|
229
|
+
const environment = requireEnvironment(args.environment);
|
|
222
230
|
const { project } = await resolveProject(ctx, args);
|
|
223
|
-
const deployments = await ctx.client.request(`/projects/${project.id}/deployments`, { query: { environment
|
|
231
|
+
const deployments = await ctx.client.request(`/projects/${project.id}/deployments`, { query: { environment, limit: 1 } });
|
|
224
232
|
const latest = (deployments.deployments ?? [])[0];
|
|
225
233
|
if (!latest) {
|
|
226
|
-
return { text: `(no ${
|
|
234
|
+
return { text: `(no ${environment} deployments yet for ${project.name})` };
|
|
227
235
|
}
|
|
228
236
|
let current = latest;
|
|
229
237
|
let logs = await ctx.client.request(`/deployments/${current.id}/logs`).catch(() => []);
|
|
@@ -346,5 +354,149 @@ export function registerWorkflowTools(server, ctx) {
|
|
|
346
354
|
return { text: lines.join("\n"), data: { binding, project } };
|
|
347
355
|
},
|
|
348
356
|
});
|
|
357
|
+
// -----------------------------------------------------------------
|
|
358
|
+
// High-intent shortcuts for non-technical users.
|
|
359
|
+
//
|
|
360
|
+
// These tools accept loose English ("show me my site", "what's broken",
|
|
361
|
+
// "redeploy") and resolve to the same backend operations as the lower-level
|
|
362
|
+
// tools, with friendlier defaults and output. The AI is free to use the
|
|
363
|
+
// primitive tools instead — these are pure ergonomics.
|
|
364
|
+
// -----------------------------------------------------------------
|
|
365
|
+
registerTool(server, ctx, {
|
|
366
|
+
name: "whats_my_url",
|
|
367
|
+
description: "Show the live URL(s) of a project. Returns the primary preview and production URLs (with SSL status) so you can share or open them.",
|
|
368
|
+
inputSchema: {
|
|
369
|
+
project_id: z.string().optional(),
|
|
370
|
+
project: z.string().optional(),
|
|
371
|
+
},
|
|
372
|
+
annotations: { readOnlyHint: true },
|
|
373
|
+
handler: async (args) => {
|
|
374
|
+
const { project } = await resolveProject(ctx, args);
|
|
375
|
+
const domains = await ctx.client.request(`/projects/${project.id}/domains`);
|
|
376
|
+
const groupByEnv = { preview: [], production: [] };
|
|
377
|
+
for (const d of domains)
|
|
378
|
+
groupByEnv[d.environment].push(d);
|
|
379
|
+
const pickPrimary = (list) => list.find((d) => d.is_primary && d.dns_verified && d.ssl_status === "active") ??
|
|
380
|
+
list.find((d) => d.is_primary) ??
|
|
381
|
+
list[0];
|
|
382
|
+
const linesOut = [`URLs for '${project.name}':`];
|
|
383
|
+
for (const env of ["preview", "production"]) {
|
|
384
|
+
const list = groupByEnv[env];
|
|
385
|
+
const primary = pickPrimary(list);
|
|
386
|
+
if (!primary) {
|
|
387
|
+
linesOut.push(` ${env.padEnd(11)} (no ${env} domains yet)`);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
const proto = primary.ssl_status === "active" ? "https://" : "http://";
|
|
391
|
+
const flag = primary.ssl_status === "active" ? "live" : `${primary.ssl_status} SSL · DNS ${primary.dns_verified ? "ok" : "pending"}`;
|
|
392
|
+
linesOut.push(` ${env.padEnd(11)} ${proto}${primary.domain} (${flag})`);
|
|
393
|
+
const others = list.filter((d) => d !== primary);
|
|
394
|
+
for (const d of others) {
|
|
395
|
+
linesOut.push(` also: ${d.ssl_status === "active" ? "https://" : "http://"}${d.domain}`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return { text: linesOut.join("\n"), data: { domains, primary: { preview: pickPrimary(groupByEnv.preview), production: pickPrimary(groupByEnv.production) } } };
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
registerTool(server, ctx, {
|
|
402
|
+
name: "whats_broken",
|
|
403
|
+
description: "Find the most recent failed deployment — either for a specific project, or (if none specified) the freshest failure across every project this token can see. Returns the deployment summary + a log tail with error lines highlighted, so the AI can suggest a fix in one shot.",
|
|
404
|
+
inputSchema: {
|
|
405
|
+
project_id: z.string().optional(),
|
|
406
|
+
project: z.string().optional(),
|
|
407
|
+
environment: ENV.optional(),
|
|
408
|
+
},
|
|
409
|
+
annotations: { readOnlyHint: true },
|
|
410
|
+
handler: async (args) => {
|
|
411
|
+
const envFilter = args.environment ? requireEnvironment(args.environment) : undefined;
|
|
412
|
+
let candidateProjects;
|
|
413
|
+
if (args.project_id || args.project) {
|
|
414
|
+
const { project } = await resolveProject(ctx, args);
|
|
415
|
+
candidateProjects = [project];
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
candidateProjects = await fetchProjects(ctx);
|
|
419
|
+
}
|
|
420
|
+
const failures = [];
|
|
421
|
+
for (const project of candidateProjects) {
|
|
422
|
+
const list = await ctx.client
|
|
423
|
+
.request(`/projects/${project.id}/deployments`, {
|
|
424
|
+
query: { status: "failed", environment: envFilter, limit: 1 },
|
|
425
|
+
})
|
|
426
|
+
.catch(() => ({ deployments: [], total: 0 }));
|
|
427
|
+
const dep = (list.deployments ?? [])[0];
|
|
428
|
+
if (dep)
|
|
429
|
+
failures.push({ project, dep });
|
|
430
|
+
}
|
|
431
|
+
if (failures.length === 0) {
|
|
432
|
+
return { text: `No failed deployments found${args.project || args.project_id ? " for this project" : ""}.` };
|
|
433
|
+
}
|
|
434
|
+
failures.sort((a, b) => (b.dep.created_at > a.dep.created_at ? 1 : -1));
|
|
435
|
+
const winner = failures[0];
|
|
436
|
+
const logs = await ctx.client.request(`/deployments/${winner.dep.id}/logs`).catch(() => []);
|
|
437
|
+
const formatted = formatLogs(logs, { maxLines: 60, tail: true });
|
|
438
|
+
const header = `Project '${winner.project.name}' has a failed ${winner.dep.environment} deployment.\n${renderDeploymentSummary(winner.dep)}`;
|
|
439
|
+
const anchors = formatted.errorAnchors.length > 0 ? `\nError anchors: lines ${formatted.errorAnchors.join(", ")}` : "";
|
|
440
|
+
return {
|
|
441
|
+
text: `${header}\n\nLog tail (${formatted.returnedLines}/${formatted.totalLines}):\n${formatted.text}${anchors}`,
|
|
442
|
+
data: { project: winner.project, deployment: winner.dep, logsMeta: formatted, otherFailures: failures.length - 1 },
|
|
443
|
+
};
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
registerTool(server, ctx, {
|
|
447
|
+
name: "redeploy",
|
|
448
|
+
description: "Re-run the last build for a project. Convenience alias around deploy_project — defaults to the preview environment and waits for the build to finish. Use this when someone says 'redeploy', 'try again', or 'kick another build'.",
|
|
449
|
+
inputSchema: {
|
|
450
|
+
project_id: z.string().optional(),
|
|
451
|
+
project: z.string().optional(),
|
|
452
|
+
environment: ENV.default("preview"),
|
|
453
|
+
wait: z.boolean().default(true),
|
|
454
|
+
timeout_ms: z.number().int().positive().max(600_000).default(240_000),
|
|
455
|
+
confirm: z.boolean().optional(),
|
|
456
|
+
confirm_name: z.string().optional(),
|
|
457
|
+
},
|
|
458
|
+
audit: true,
|
|
459
|
+
handler: async (args) => {
|
|
460
|
+
const environment = requireEnvironment(args.environment);
|
|
461
|
+
const { project } = await resolveProject(ctx, args);
|
|
462
|
+
if (environment === "production") {
|
|
463
|
+
const safety = checkSafety({
|
|
464
|
+
toolName: "redeploy",
|
|
465
|
+
tier: "destructive_production",
|
|
466
|
+
expectedName: project.name,
|
|
467
|
+
impact: { project: project.name, environment, branch: project.branch },
|
|
468
|
+
}, { confirm: args.confirm, confirm_name: args.confirm_name });
|
|
469
|
+
if (!safety.proceed)
|
|
470
|
+
return { text: renderDryRun(safety.dryRun) };
|
|
471
|
+
}
|
|
472
|
+
const dep = await ctx.client.request(`/projects/${project.id}/deployments`, {
|
|
473
|
+
method: "POST",
|
|
474
|
+
body: { environment },
|
|
475
|
+
});
|
|
476
|
+
if (!args.wait)
|
|
477
|
+
return { text: `Redeploy queued for ${project.name} (${environment}).\n${renderDeploymentSummary(dep)}`, data: dep };
|
|
478
|
+
const final = await pollUntil(() => ctx.client.request(`/projects/${project.id}/deployments/${dep.id}`), (d) => TERMINAL_STATUSES.has(d.status), { intervalMs: 2000, timeoutMs: args.timeout_ms, initialDelayMs: 1500 });
|
|
479
|
+
const status = final.value;
|
|
480
|
+
if (status.status === "live") {
|
|
481
|
+
const domains = await ctx.client.request(`/projects/${project.id}/domains`).catch(() => []);
|
|
482
|
+
const primary = domains.find((d) => d.environment === environment && d.is_primary) ?? domains.find((d) => d.environment === environment);
|
|
483
|
+
const url = primary ? (primary.ssl_status === "active" ? "https://" : "http://") + primary.domain : null;
|
|
484
|
+
return {
|
|
485
|
+
text: [
|
|
486
|
+
`✅ ${project.name} (${environment}) is live again.`,
|
|
487
|
+
url ? `URL: ${url}` : "",
|
|
488
|
+
renderDeploymentSummary(status),
|
|
489
|
+
].filter(Boolean).join("\n"),
|
|
490
|
+
data: status,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
const logs = await ctx.client.request(`/deployments/${status.id}/logs`).catch(() => []);
|
|
494
|
+
const formatted = formatLogs(logs, { maxLines: 80, tail: true });
|
|
495
|
+
return {
|
|
496
|
+
text: `❌ Redeploy of ${project.name} (${environment}) ended as '${status.status}'.\n${renderDeploymentSummary(status)}\n\nLog tail:\n${formatted.text}`,
|
|
497
|
+
data: status,
|
|
498
|
+
};
|
|
499
|
+
},
|
|
500
|
+
});
|
|
349
501
|
}
|
|
350
502
|
//# sourceMappingURL=workflows.js.map
|