@offlocal/mcp 0.3.4 → 0.3.6
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 +24 -0
- package/dist/index.js +7 -7
- package/dist/tools/plan.js +36 -14
- package/dist/tools/providers.js +42 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,9 +57,33 @@ Paste this into a fresh agent chat:
|
|
|
57
57
|
| Tool | What it does |
|
|
58
58
|
| --- | --- |
|
|
59
59
|
| `offlocal_ping` | Verify the agent is connected. Returns your email if so. |
|
|
60
|
+
| `offlocal_plan` | Inspect a project, identify required resources, and guide the agent through intent-driven platform choices. |
|
|
60
61
|
| `offlocal_deploy` | Detect runtime, zip the source, upload, kick off the build + deploy pipeline. Returns a `deploymentId`. |
|
|
61
62
|
| `offlocal_status` | Poll a deployment. Returns the current phase, ETA, and the live URL on success. |
|
|
62
63
|
| `offlocal_logs` | Read the most recent container logs from CloudWatch. |
|
|
64
|
+
| `offlocal_resolve_requirement` | Compare Offlocal-managed resources, Offlocal connectors, and existing env values. |
|
|
65
|
+
| `offlocal_connect` | Link the user's provider account to Offlocal through OAuth. |
|
|
66
|
+
| `offlocal_provision` | Preview and provision resources through an Offlocal-connected provider. |
|
|
67
|
+
|
|
68
|
+
## Agent behavior
|
|
69
|
+
|
|
70
|
+
The MCP supplies tools, structured plans, and instructions to the host AI agent;
|
|
71
|
+
it does not generate the conversation itself.
|
|
72
|
+
|
|
73
|
+
- Offlocal-managed resources run on Offlocal infrastructure.
|
|
74
|
+
- Offlocal connectors link the user's provider account to Offlocal. Offlocal
|
|
75
|
+
orchestrates through the connection; the provider hosts the resources and may
|
|
76
|
+
bill the user.
|
|
77
|
+
- Clear user intent covers the matching workflow. Requests such as "use
|
|
78
|
+
Railway", "go ahead", or "use whatever is fastest" should not trigger repeated
|
|
79
|
+
confirmation questions.
|
|
80
|
+
- If infrastructure intent is missing, the agent asks only for the unresolved
|
|
81
|
+
choice.
|
|
82
|
+
- Provision previews remain visible for scope and cost transparency. The agent
|
|
83
|
+
continues automatically when the preview matches established intent, and
|
|
84
|
+
pauses only for a material scope or cost change.
|
|
85
|
+
- Saved preferences influence future deployment plans but are only written when
|
|
86
|
+
the user expresses an ongoing preference.
|
|
63
87
|
|
|
64
88
|
## Env
|
|
65
89
|
|
package/dist/index.js
CHANGED
|
@@ -31,19 +31,19 @@ async function main() {
|
|
|
31
31
|
version: MCP_VERSION,
|
|
32
32
|
});
|
|
33
33
|
server.tool("offlocal_ping", "Verify the Offlocal connection. Returns the signed-in email if connected, or an error message if not. This tool does NOT deploy anything, upload anything, or provision any resources - it is the right tool to call when the user asks to 'check my Offlocal connection' or similar.", PingInputShape, async (args) => pingHandler(PingSchema.parse(args)));
|
|
34
|
-
server.tool("offlocal_plan", "START HERE
|
|
35
|
-
server.tool("offlocal_set_preference", "Remember
|
|
34
|
+
server.tool("offlocal_plan", "START HERE for deployment/setup requests. Detects the project, required resources, saved preferences, eligible platforms, and unresolved choices. Follow the authorization and recommendation guidance returned with the plan.", PlanInputShape, async (args) => planHandler(PlanSchema.parse(args)));
|
|
35
|
+
server.tool("offlocal_set_preference", "Remember an explicit platform preference for future plans. Use only when the plan guidance and conversation indicate the choice should persist.", SetPreferenceInputShape, async (args) => setPreferenceHandler(SetPreferenceSchema.parse(args)));
|
|
36
36
|
server.tool("offlocal_deploy", "Deploy an app from a local directory to Offlocal. Returns a deploymentId you can poll with offlocal_status. Supports Node, Python, Go, Rust, and static sites out of the box (auto-detecting runtime/framework/port), and uses the project's own Dockerfile as-is when one exists. For an unrecognized stack with no Dockerfile, it returns instructions asking you to add a Dockerfile and re-run - follow them, then call this again. You can override appName and port. Only call this when the user explicitly asks to deploy.", DeployInputShape, async (args) => deployHandler(DeploySchema.parse(args)));
|
|
37
37
|
server.tool("offlocal_status", "Get the current status of a deployment. Poll this every few seconds after offlocal_deploy until status is 'live' or 'failed'.", StatusInputShape, async (args) => statusHandler(StatusSchema.parse(args)));
|
|
38
38
|
server.tool("offlocal_logs", "Read recent container logs for a deployment. Useful for debugging a failed deploy or watching live output.", LogsInputShape, async (args) => logsHandler(LogsSchema.parse(args)));
|
|
39
39
|
server.tool("offlocal_rollback", "Roll a project back to a previous build. Re-deploys an existing image (skips the build, ~1 min), so it's fast and safe. Defaults to the most recent prior build; pass toDeploymentId to target a specific one. Use when a new deploy is broken or the user wants the last working version back.", RollbackInputShape, async (args) => rollbackHandler(RollbackSchema.parse(args)));
|
|
40
40
|
server.tool("offlocal_plans", "Show the user's current Offlocal.ai plan, the full tier catalog (prices, limits, features), and upcoming features. Use it to answer plan and pricing questions, and to show upgrade options when the user needs more capacity (projects, databases, custom domains). Includes the billing link.", PlansInputShape, async () => plansHandler());
|
|
41
|
-
server.tool("offlocal_resolve_requirement", "
|
|
42
|
-
server.tool("offlocal_connect", "Get
|
|
43
|
-
server.tool("offlocal_list_connections", "List the user's provider
|
|
44
|
-
server.tool("offlocal_provision", "
|
|
41
|
+
server.tool("offlocal_resolve_requirement", "Resolve an unsettled resource requirement into eligible Offlocal-managed, Offlocal-connected, and existing-env options.", ResolveRequirementInputShape, async (args) => resolveRequirementHandler(ResolveRequirementSchema.parse(args)));
|
|
42
|
+
server.tool("offlocal_connect", "Get an Offlocal OAuth link for a provider selected by the user or plan. Share the link because the user must complete OAuth, then poll the connection status and continue the active workflow.", ConnectInputShape, async (args) => connectHandler(ConnectSchema.parse(args)));
|
|
43
|
+
server.tool("offlocal_list_connections", "List provider accounts connected to Offlocal and their status. These Offlocal connectors let Offlocal provision/manage resources through the user's provider account; the provider still hosts and may bill for those resources. Use this to inspect choices or poll after offlocal_connect no faster than every 5 seconds.", ListConnectionsInputShape, async () => listConnectionsHandler());
|
|
44
|
+
server.tool("offlocal_provision", "Preview and provision a resource through an Offlocal-connected provider. Call with confirm omitted/false first, then follow the returned preview guidance. Pass projectId so generated env vars are auto-injected.", ProvisionInputShape, async (args) => provisionHandler(ProvisionSchema.parse(args)));
|
|
45
45
|
server.tool("offlocal_provision_status", "Poll an in-progress provision (DigitalOcean/Railway) by its actionId until it's ready. Poll no faster than once every ~20 seconds.", ProvisionStatusInputShape, async (args) => provisionStatusHandler(ProvisionStatusSchema.parse(args)));
|
|
46
|
-
server.tool("offlocal_create_managed_database", "Create
|
|
46
|
+
server.tool("offlocal_create_managed_database", "Create Offlocal Managed Postgres for a project when selected by the user or the authenticated plan guidance. DATABASE_URL is wired automatically.", CreateManagedDatabaseInputShape, async (args) => createManagedDatabaseHandler(CreateManagedDatabaseSchema.parse(args)));
|
|
47
47
|
server.tool("offlocal_set_env", "Set environment variables on a project (stored encrypted, injected on the next deploy). Use this for cross-app wiring in a monorepo — e.g. set NEXT_PUBLIC_API_URL on the web project to the API project's live URL so the frontend can reach the backend. Build-time vars (NEXT_PUBLIC_*, VITE_*) require a fresh deploy to take effect.", SetEnvInputShape, async (args) => setEnvHandler(SetEnvSchema.parse(args)));
|
|
48
48
|
process.on("uncaughtException", (err) => {
|
|
49
49
|
console.error("offlocal-mcp uncaught exception (continuing):", err);
|
package/dist/tools/plan.js
CHANGED
|
@@ -11,13 +11,25 @@ export const PlanInputShape = {
|
|
|
11
11
|
};
|
|
12
12
|
export async function planHandler(input) {
|
|
13
13
|
const snapshot = await gatherPlanSnapshot(input.sourceDir);
|
|
14
|
-
const { multi, components, requirements, items, links, dbTarget } = await planProject(snapshot);
|
|
14
|
+
const { multi, components, requirements, items, links, dbTarget, agentGuidance, } = await planProject(snapshot);
|
|
15
15
|
const planLines = items.map((it, i) => {
|
|
16
|
-
const emoji = it.isOfflocal
|
|
16
|
+
const emoji = it.isOfflocal
|
|
17
|
+
? ICON.offlocal
|
|
18
|
+
: it.recommended
|
|
19
|
+
? ICON.connected
|
|
20
|
+
: ICON.open;
|
|
17
21
|
const connect = it.needsConnect ? ` ${ICON.warn} first-time connect (~30s)` : "";
|
|
18
22
|
const saved = it.fromPreference ? " _(your saved choice)_" : "";
|
|
23
|
+
const suggestion = it.requiresChoice && it.recommended
|
|
24
|
+
? " _(suggested path; use automatically only if the user delegated the choice)_"
|
|
25
|
+
: "";
|
|
19
26
|
const note = it.note ? ` ${ICON.soon} _${it.note}_` : "";
|
|
20
|
-
|
|
27
|
+
const alternatives = it.requiresChoice
|
|
28
|
+
? `\n Options: ${it.options
|
|
29
|
+
.map((o) => `${o.label} (${o.managed ? "Offlocal-managed" : "Offlocal connector"}; ${o.status}; ${o.cost})`)
|
|
30
|
+
.join(" · ")} · existing env value`
|
|
31
|
+
: "";
|
|
32
|
+
return ` ${i + 1}. ${emoji} ${it.label} → **${it.recommendedLabel}**${saved}${suggestion}${connect} · _${it.cost}_${note}${alternatives}`;
|
|
21
33
|
});
|
|
22
34
|
const wiringBlock = links.length
|
|
23
35
|
? `\n**Wiring** _(automatic)_\n` +
|
|
@@ -38,25 +50,27 @@ export async function planHandler(input) {
|
|
|
38
50
|
const hasDatastore = requirements.some((r) => r.capability === "postgres" || r.capability === "mysql");
|
|
39
51
|
const hasServerComponent = components.some((c) => c.role !== "web");
|
|
40
52
|
const noteEphemeral = !hasDatastore && hasServerComponent;
|
|
41
|
-
const show = `${ICON.search} **Offlocal
|
|
53
|
+
const show = `${ICON.search} **Offlocal — deployment plan**\n\n` +
|
|
42
54
|
`**Detected:** ${detectedLine}\n\n` +
|
|
43
55
|
`**Plan**\n${planLines.join("\n")}\n` +
|
|
44
56
|
wiringBlock +
|
|
45
57
|
`\n**Setup checklist** ${progressBar(0, checklist.length)}\n${checklistLines.join("\n")}\n\n` +
|
|
46
|
-
`
|
|
47
|
-
`
|
|
58
|
+
`Offlocal is the control plane for both paths. Offlocal-managed resources run on ` +
|
|
59
|
+
`Offlocal infrastructure; Offlocal-connected providers run in the user's linked provider ` +
|
|
60
|
+
`account and may be billed by that provider.`;
|
|
48
61
|
const persistenceCheck = noteEphemeral
|
|
49
62
|
? `\n\nNote: this host's storage is ephemeral. Mention data persistence only if the user asks about keeping data between deploys — a database covers it.`
|
|
50
63
|
: "";
|
|
51
|
-
const agentNote =
|
|
52
|
-
|
|
64
|
+
const agentNote = `${agentGuidance.authorization} ${agentGuidance.delegatedChoice} ` +
|
|
65
|
+
`${agentGuidance.unresolvedChoice} ${agentGuidance.preference}` +
|
|
53
66
|
persistenceCheck +
|
|
54
|
-
`\n\n**Execution order once
|
|
67
|
+
`\n\n**Execution order once intent covers the listed actions — walk the checklist top to bottom, ` +
|
|
55
68
|
`re-showing it with ${ICON.done} on finished steps so they see live progress:**\n` +
|
|
56
69
|
executionSteps(items, links, dbTarget, input.sourceDir).join("\n");
|
|
57
70
|
return ui({
|
|
58
71
|
show,
|
|
59
|
-
next: `
|
|
72
|
+
next: `Use the user's existing request to decide the next action. Execute immediately when intent ` +
|
|
73
|
+
`covers the plan; otherwise ask only for unresolved infrastructure choices.`,
|
|
60
74
|
agentNote,
|
|
61
75
|
structured: {
|
|
62
76
|
multiComponent: multi,
|
|
@@ -66,6 +80,7 @@ export async function planHandler(input) {
|
|
|
66
80
|
links,
|
|
67
81
|
checklist,
|
|
68
82
|
dbTargetComponent: dbTarget,
|
|
83
|
+
agentGuidance,
|
|
69
84
|
},
|
|
70
85
|
});
|
|
71
86
|
}
|
|
@@ -91,7 +106,11 @@ function buildChecklist(items, links, dbTarget) {
|
|
|
91
106
|
out.push({
|
|
92
107
|
n,
|
|
93
108
|
kind: "resource",
|
|
94
|
-
text:
|
|
109
|
+
text: it.requiresChoice
|
|
110
|
+
? `Resolve ${it.label} platform choice${it.recommended
|
|
111
|
+
? ` (integrated suggestion: ${it.recommendedLabel})`
|
|
112
|
+
: ""}`
|
|
113
|
+
: `Provision ${it.label} → ${it.recommendedLabel}${connect}`,
|
|
95
114
|
});
|
|
96
115
|
}
|
|
97
116
|
for (const l of links) {
|
|
@@ -131,19 +150,22 @@ function executionSteps(items, links, dbTarget, sourceDir) {
|
|
|
131
150
|
lines.push(`• ${ICON.rocket} Deploy ${labelShort(it)}: \`offlocal_deploy({ sourceDir: "${absPath(sourceDir, it.componentRelPath)}" })\` → poll \`offlocal_status\` until live → note the **projectId** and **live URL**.`);
|
|
132
151
|
}
|
|
133
152
|
else if (it.recommended) {
|
|
134
|
-
lines.push(`• ${ICON.web} Host ${labelShort(it)} on ${it.recommendedLabel}: ${it.cost === "coming soon" ? `${it.recommendedLabel} support is coming soon.` : `if not connected, \`offlocal_connect\` → share link → poll \`offlocal_list_connections
|
|
153
|
+
lines.push(`• ${ICON.web} Host ${labelShort(it)} on ${it.recommendedLabel}: ${it.cost === "coming soon" ? `${it.recommendedLabel} support is coming soon.` : `if not connected, \`offlocal_connect\` → share the Offlocal OAuth link → poll \`offlocal_list_connections\`, then continue the requested workflow. The connected provider hosts the resource and may bill the user.`}`);
|
|
135
154
|
}
|
|
136
155
|
}
|
|
137
156
|
for (const it of resources) {
|
|
138
157
|
if (it.isOfflocal) {
|
|
139
158
|
lines.push(`• ${ICON.db} ${it.label} (Offlocal Managed Postgres): \`offlocal_create_managed_database({ projectId: <${projName(dbTarget)} projectId> })\`. DATABASE_URL is wired in automatically.`);
|
|
140
159
|
}
|
|
141
|
-
else {
|
|
160
|
+
else if (it.recommended) {
|
|
142
161
|
lines.push(`• ${ICON.db} ${it.label} on ${it.recommendedLabel}: ` +
|
|
143
162
|
(it.needsConnect
|
|
144
163
|
? `\`offlocal_connect\` → share link → poll \`offlocal_list_connections\` until connected, then `
|
|
145
164
|
: "") +
|
|
146
|
-
`\`offlocal_provision({ provider: "${it.recommended}", type: "database" })\` for a PREVIEW + cost
|
|
165
|
+
`\`offlocal_provision({ provider: "${it.recommended}", type: "database" })\` for a visible PREVIEW + cost. If established intent covers it and there is no material surprise, continue with \`confirm: true, projectId: <${projName(dbTarget)} projectId>\` without asking again. If it returns status **"provisioning"**, poll \`offlocal_provision_status({ actionId })\` until ready.`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
lines.push(`• ${ICON.db} ${it.label}: no provisionable platform is currently available. Ask for an existing connection value and store it with \`offlocal_set_env\`.`);
|
|
147
169
|
}
|
|
148
170
|
}
|
|
149
171
|
for (const l of links) {
|
package/dist/tools/providers.js
CHANGED
|
@@ -26,7 +26,9 @@ export async function listConnectionsHandler() {
|
|
|
26
26
|
content: [
|
|
27
27
|
{
|
|
28
28
|
type: "text",
|
|
29
|
-
text: `${ICON.plug} **
|
|
29
|
+
text: `${ICON.plug} **Offlocal connectors**\n` +
|
|
30
|
+
`_These are provider accounts linked to Offlocal. Offlocal orchestrates through the connection; ` +
|
|
31
|
+
`the provider hosts the resources and may bill the user._\n${lines.join("\n")}`,
|
|
30
32
|
},
|
|
31
33
|
],
|
|
32
34
|
structuredContent: { providers },
|
|
@@ -51,25 +53,37 @@ export async function resolveRequirementHandler(input) {
|
|
|
51
53
|
structuredContent: { need, options, pasteEnvVar },
|
|
52
54
|
};
|
|
53
55
|
}
|
|
54
|
-
const
|
|
56
|
+
const optionLine = (o) => {
|
|
55
57
|
const tag = o.managed
|
|
56
58
|
? o.planEligible
|
|
57
59
|
? "managed by Offlocal · included in your plan"
|
|
58
|
-
: "managed by Offlocal ·
|
|
60
|
+
: "managed by Offlocal · included on Hobby+"
|
|
59
61
|
: o.status === "connected"
|
|
60
62
|
? "connected ✓"
|
|
61
63
|
: o.status === "coming_soon"
|
|
62
64
|
? "coming soon"
|
|
63
65
|
: o.planEligible === false
|
|
64
|
-
? "connect
|
|
66
|
+
? "one-click connect (via Offlocal) · plan limit reached (upgrade)"
|
|
65
67
|
: o.connectable
|
|
66
|
-
? "connect
|
|
68
|
+
? "one-click connect (via Offlocal)"
|
|
67
69
|
: "not configured";
|
|
68
70
|
const guide = [o.bestFor, o.cost, o.freeTier ? "free tier" : null]
|
|
69
71
|
.filter(Boolean)
|
|
70
72
|
.join(" · ");
|
|
71
73
|
return ` - ${o.label} (${o.provider}) — ${tag}` + (guide ? `\n ${guide}` : "");
|
|
72
|
-
}
|
|
74
|
+
};
|
|
75
|
+
const managedLines = options.filter((o) => o.managed).map(optionLine);
|
|
76
|
+
const connectorLines = options.filter((o) => !o.managed).map(optionLine);
|
|
77
|
+
const sections = [
|
|
78
|
+
managedLines.length
|
|
79
|
+
? `**Offlocal-managed**\n${managedLines.join("\n")}`
|
|
80
|
+
: null,
|
|
81
|
+
connectorLines.length
|
|
82
|
+
? `**Offlocal connectors**\n${connectorLines.join("\n")}`
|
|
83
|
+
: null,
|
|
84
|
+
]
|
|
85
|
+
.filter(Boolean)
|
|
86
|
+
.join("\n\n");
|
|
73
87
|
const budgetLine = connectorBudget
|
|
74
88
|
? `\n_Connectors: ${connectorBudget.used}/${connectorBudget.limit} used` +
|
|
75
89
|
(connectorBudget.remaining === 0
|
|
@@ -78,23 +92,25 @@ export async function resolveRequirementHandler(input) {
|
|
|
78
92
|
: "";
|
|
79
93
|
const hasActionable = options.some((o) => o.connected || o.connectable || (o.managed && o.planEligible));
|
|
80
94
|
const intro = hasActionable
|
|
81
|
-
? `This app needs "${need}". Present
|
|
82
|
-
`
|
|
83
|
-
`
|
|
95
|
+
? `This app needs "${need}". Present the options neutrally if the user's intent or saved ` +
|
|
96
|
+
`preferences do not already settle the choice. Offlocal-managed resources are hosted and ` +
|
|
97
|
+
`managed by Offlocal. Offlocal-connected providers are the user's linked provider accounts: ` +
|
|
98
|
+
`Offlocal orchestrates provisioning and wiring, while the provider hosts the resources and ` +
|
|
99
|
+
`may bill the user. An existing connection string is also valid. Options:`
|
|
84
100
|
: `This app needs "${need}", but nothing is connectable on this account right now. ` +
|
|
85
|
-
`
|
|
86
|
-
`
|
|
101
|
+
`Present any eligible managed option shown below, or ask for an existing connection ` +
|
|
102
|
+
`string to set with offlocal_set_env. Options:`;
|
|
87
103
|
return {
|
|
88
104
|
content: [
|
|
89
105
|
{
|
|
90
106
|
type: "text",
|
|
91
107
|
text: `${intro}\n` +
|
|
92
|
-
`${
|
|
108
|
+
`${sections}${budgetLine}\n\n` +
|
|
93
109
|
`If they pick **Offlocal Managed Postgres** (provider "offlocal"), call ` +
|
|
94
110
|
`offlocal_create_managed_database with the projectId — no connect step. ` +
|
|
95
|
-
`If they pick a provider
|
|
96
|
-
`
|
|
97
|
-
`connected,
|
|
111
|
+
`If they pick a connectable provider, call offlocal_connect and share the Offlocal OAuth ` +
|
|
112
|
+
`link. After authorization, continue the already-requested workflow. If it is already ` +
|
|
113
|
+
`connected, go straight to offlocal_provision.\n` +
|
|
98
114
|
`Alternative: ${pasteEnvVar.hint}`,
|
|
99
115
|
},
|
|
100
116
|
],
|
|
@@ -114,12 +130,15 @@ export async function connectHandler(input) {
|
|
|
114
130
|
{
|
|
115
131
|
type: "text",
|
|
116
132
|
text: `${ICON.plug} **Connect ${provider}** — first time only (~30s)\n\n` +
|
|
133
|
+
`This Offlocal OAuth link connects the user's ${provider} account to Offlocal. ` +
|
|
134
|
+
`${provider} still hosts the resources and may bill the user.\n\n` +
|
|
117
135
|
`Open this link to authorize:\n\n${url}\n\n` +
|
|
118
136
|
`You'll be redirected back when it's done.\n\n` +
|
|
119
137
|
`———\n` +
|
|
120
138
|
`${ICON.arrow} **Next (you, the agent):** share the link above, then wait. Poll ` +
|
|
121
139
|
`\`offlocal_list_connections\` no faster than every 5s. When **${provider}** shows ` +
|
|
122
|
-
`${ICON.connected} connected, continue
|
|
140
|
+
`${ICON.connected} connected, continue the workflow the user already requested; do not ` +
|
|
141
|
+
`ask for the same approval again. You cannot ` +
|
|
123
142
|
`complete the OAuth yourself.`,
|
|
124
143
|
},
|
|
125
144
|
],
|
|
@@ -130,14 +149,14 @@ export const ProvisionInputShape = {
|
|
|
130
149
|
provider: z
|
|
131
150
|
.string()
|
|
132
151
|
.min(1)
|
|
133
|
-
.describe("The
|
|
152
|
+
.describe("The Offlocal-connected provider to provision through: supabase, planetscale, digitalocean, or railway. The provider hosts the resource and may bill the user. For Offlocal Managed Postgres, use offlocal_create_managed_database."),
|
|
134
153
|
type: z
|
|
135
154
|
.enum(["project", "database", "deployment", "env_vars", "branch"])
|
|
136
155
|
.describe("The kind of resource to create."),
|
|
137
156
|
confirm: z
|
|
138
157
|
.boolean()
|
|
139
158
|
.optional()
|
|
140
|
-
.describe("Leave false/omitted
|
|
159
|
+
.describe("Leave false/omitted for a no-charge PREVIEW + current cost. Set true when the user's established intent covers this provider/resource and the preview has no material surprise. Ask again only for a material scope or cost change."),
|
|
141
160
|
projectId: z
|
|
142
161
|
.string()
|
|
143
162
|
.optional()
|
|
@@ -153,7 +172,7 @@ export async function provisionHandler(input) {
|
|
|
153
172
|
const { provider, confirm, projectId, ...spec } = input;
|
|
154
173
|
if (["offlocal", "offlocal.ai"].includes(provider.toLowerCase())) {
|
|
155
174
|
return text(`${ICON.warn} "offlocal" isn't a provision provider. For an Offlocal Managed Postgres database, call ` +
|
|
156
|
-
`\`offlocal_create_managed_database({ projectId })\`. \`offlocal_provision\` is only for connected
|
|
175
|
+
`\`offlocal_create_managed_database({ projectId })\`. \`offlocal_provision\` is only for Offlocal-connected ` +
|
|
157
176
|
`providers: **supabase, planetscale, digitalocean, railway**.`);
|
|
158
177
|
}
|
|
159
178
|
const res = await provisionResource(provider, spec, {
|
|
@@ -170,9 +189,8 @@ export async function provisionHandler(input) {
|
|
|
170
189
|
const cost = result.cost ? `\n${ICON.spark} **Cost:** ${result.cost.note}` : "";
|
|
171
190
|
return text(`${ICON.warn} **PREVIEW — nothing created yet, no charge.**\n${result.preview.summary}${cost}\n\n` +
|
|
172
191
|
`———\n` +
|
|
173
|
-
`${ICON.arrow} **Next (you, the agent):**
|
|
174
|
-
|
|
175
|
-
`\`confirm: true\` and \`projectId\` (to auto-inject the DATABASE_URL).`, res);
|
|
192
|
+
`${ICON.arrow} **Next (you, the agent):** ${res.agentGuidance ??
|
|
193
|
+
"State the preview and follow the active plan's authorization guidance."}`, res);
|
|
176
194
|
}
|
|
177
195
|
if (result.status === "provisioning") {
|
|
178
196
|
return text(`${ICON.busy} **Provisioning on ${provider}** (a few minutes).\n\n` +
|
|
@@ -244,5 +262,6 @@ export async function createManagedDatabaseHandler(input) {
|
|
|
244
262
|
return text(`${ICON.fail} Couldn't create the managed database: ${res.message ?? res.error}.${planHint}`, res);
|
|
245
263
|
}
|
|
246
264
|
return text(`${ICON.db} **Offlocal Managed Postgres created** (${res.database?.status}). ` +
|
|
247
|
-
`${ICON.spark} DATABASE_URL is wired into this project automatically
|
|
265
|
+
`${ICON.spark} DATABASE_URL is wired into this project automatically. Continue the ` +
|
|
266
|
+
`user's requested workflow with the required redeploy.`, res);
|
|
248
267
|
}
|
package/package.json
CHANGED