@dypai-ai/mcp 1.4.3 → 1.4.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/package.json +1 -1
- package/src/api.js +14 -2
- package/src/auto-update.js +44 -1
- package/src/index.js +260 -19
- package/src/tools/deploy.js +49 -1
- package/src/tools/frontend.js +59 -6
- package/src/tools/scaffold.js +6 -2
- package/src/tools/search-logs-offload.js +151 -0
- package/src/tools/sync/diff.js +88 -7
- package/src/tools/sync/pull.js +75 -8
- package/src/tools/sync/push.js +129 -96
- package/src/tools/sync/test-endpoint.js +217 -73
- package/src/tools/sync/validate.js +415 -48
- package/src/tools/sync.js +85 -13
- package/src/tools/status.js +0 -94
package/src/tools/sync.js
CHANGED
|
@@ -12,11 +12,61 @@
|
|
|
12
12
|
* node_modules, .vscode, etc.) are preserved.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { writeFileSync, mkdirSync, existsSync } from "fs"
|
|
15
|
+
import { writeFileSync, mkdirSync, existsSync, readFileSync } from "fs"
|
|
16
16
|
import { join, dirname } from "path"
|
|
17
17
|
import { createHash } from "crypto"
|
|
18
18
|
import { api } from "../api.js"
|
|
19
19
|
|
|
20
|
+
// Engine hostname suffix. Production hosts of the form `<project_id>.dypai.dev`
|
|
21
|
+
// serve LIVE traffic; `dev-<project_id>.dypai.dev` serves the LAYER 2.5 draft
|
|
22
|
+
// overlay (drafts when present, live as fallback). Override only for local
|
|
23
|
+
// engine development against `*.localhost`.
|
|
24
|
+
const DEFAULT_ENGINE_BASE = "dypai.dev"
|
|
25
|
+
|
|
26
|
+
// Detect the frontend framework by inspecting `package.json` so we know which
|
|
27
|
+
// env-var prefix the bundler will inject at build time. We only need to
|
|
28
|
+
// distinguish Next.js from everything else (Vite/React/Astro/SvelteKit/etc.
|
|
29
|
+
// all consume `VITE_*` style or accept it as fallback). Returns "next" |
|
|
30
|
+
// "vite" | "unknown" — when "unknown" we write BOTH prefixes (cheap, harmless).
|
|
31
|
+
function detectFramework(targetDirectory) {
|
|
32
|
+
try {
|
|
33
|
+
const pkgPath = join(targetDirectory, "package.json")
|
|
34
|
+
if (!existsSync(pkgPath)) return "unknown"
|
|
35
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"))
|
|
36
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }
|
|
37
|
+
if (deps.next) return "next"
|
|
38
|
+
if (deps.vite || deps["@vitejs/plugin-react"]) return "vite"
|
|
39
|
+
return "unknown"
|
|
40
|
+
} catch {
|
|
41
|
+
return "unknown"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Build the contents of `.env.local`. We point at the DRAFT OVERLAY host
|
|
46
|
+
// (`dev-<project_id>.<base>`), not at live, so an unpublished `dypai_push`
|
|
47
|
+
// is visible end-to-end from the local frontend. On production deploy the
|
|
48
|
+
// platform PATCHes `<PREFIX>DYPAI_URL` to the LIVE host as a CF Pages build
|
|
49
|
+
// env var, so this file is only ever consumed by `vite dev` / `next dev`.
|
|
50
|
+
function buildEnvLocalContents(project_id, framework, engineBase) {
|
|
51
|
+
const draftUrl = `https://dev-${project_id}.${engineBase}`
|
|
52
|
+
const lines = [
|
|
53
|
+
`# DYPAI — generated by manage_frontend(sync). Safe to edit; do NOT commit.`,
|
|
54
|
+
`# Points at the LAYER 2.5 draft overlay so unpublished \`dypai_push\` changes`,
|
|
55
|
+
`# are picked up by the local dev server. Production builds receive the LIVE`,
|
|
56
|
+
`# URL automatically as a CF Pages build env var (no action needed).`,
|
|
57
|
+
``,
|
|
58
|
+
]
|
|
59
|
+
if (framework === "next" || framework === "unknown") {
|
|
60
|
+
lines.push(`NEXT_PUBLIC_DYPAI_URL=${draftUrl}`)
|
|
61
|
+
lines.push(`NEXT_PUBLIC_PROJECT_ID=${project_id}`)
|
|
62
|
+
}
|
|
63
|
+
if (framework === "vite" || framework === "unknown") {
|
|
64
|
+
lines.push(`VITE_DYPAI_URL=${draftUrl}`)
|
|
65
|
+
lines.push(`VITE_PROJECT_ID=${project_id}`)
|
|
66
|
+
}
|
|
67
|
+
return lines.join("\n") + "\n"
|
|
68
|
+
}
|
|
69
|
+
|
|
20
70
|
export async function syncFromRemote({ project_id, targetDirectory, overwrite = false }) {
|
|
21
71
|
if (!project_id) {
|
|
22
72
|
return { success: false, error: "project_id is required." }
|
|
@@ -132,19 +182,41 @@ export async function syncFromRemote({ project_id, targetDirectory, overwrite =
|
|
|
132
182
|
)
|
|
133
183
|
}
|
|
134
184
|
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
185
|
+
// `.env` / `.env.local` are gitignored → the source API does NOT return them
|
|
186
|
+
// → the frontend wouldn't know which engine URL to hit. We auto-write
|
|
187
|
+
// `.env.local` with the LAYER 2.5 draft-overlay URL (`dev-<project_id>.<base>`)
|
|
188
|
+
// so the local dev server picks up unpublished `dypai_push` changes
|
|
189
|
+
// immediately. Production builds receive the LIVE URL via CF Pages build
|
|
190
|
+
// env vars set by the control plane (api/services/frontend/pages_service.py).
|
|
191
|
+
//
|
|
192
|
+
// We only WRITE if `.env.local` (and the legacy `.env`) don't exist —
|
|
193
|
+
// never clobber a user-authored config.
|
|
194
|
+
const engineBase = process.env.DYPAI_ENGINE_BASE || DEFAULT_ENGINE_BASE
|
|
195
|
+
const envLocalPath = join(targetDirectory, ".env.local")
|
|
196
|
+
const envPath = join(targetDirectory, ".env")
|
|
197
|
+
const hasUserEnv = existsSync(envLocalPath) || existsSync(envPath)
|
|
198
|
+
let envWritten = false
|
|
199
|
+
if (!hasUserEnv) {
|
|
200
|
+
try {
|
|
201
|
+
const framework = detectFramework(targetDirectory)
|
|
202
|
+
writeFileSync(envLocalPath, buildEnvLocalContents(project_id, framework, engineBase))
|
|
203
|
+
envWritten = true
|
|
204
|
+
next_steps.push(
|
|
205
|
+
`Wrote \`.env.local\` pointing at the draft-overlay host \`https://dev-${project_id}.${engineBase}\`. ` +
|
|
206
|
+
`Local dev (\`vite dev\` / \`next dev\`) will see unpublished \`dypai_push\` changes immediately. ` +
|
|
207
|
+
`Production builds get the LIVE URL injected automatically — no manual switch needed.`
|
|
208
|
+
)
|
|
209
|
+
} catch (e) {
|
|
210
|
+
failures.push({ path: ".env.local", reason: `Could not write env file: ${e.message}` })
|
|
211
|
+
next_steps.push(
|
|
212
|
+
`Could not auto-write .env.local (${e.message}). Create it manually with ` +
|
|
213
|
+
`\`VITE_DYPAI_URL=https://dev-${project_id}.${engineBase}\` (or \`NEXT_PUBLIC_DYPAI_URL=...\` for Next.js).`
|
|
214
|
+
)
|
|
215
|
+
}
|
|
147
216
|
}
|
|
217
|
+
// Surface env_file_missing as before (back-compat for older agents that
|
|
218
|
+
// gate on it). Treats `.env.local` as satisfying the requirement.
|
|
219
|
+
const envMissing = !hasUserEnv && !envWritten
|
|
148
220
|
|
|
149
221
|
return {
|
|
150
222
|
success: written > 0,
|
package/src/tools/status.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Frontend status + build status tools.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { api } from "../api.js"
|
|
6
|
-
|
|
7
|
-
export const listDeploymentsTool = {
|
|
8
|
-
name: "list_deployments",
|
|
9
|
-
description: `List deployment history for the project's frontend. Shows recent deploys with status, commit, duration, and URL.`,
|
|
10
|
-
|
|
11
|
-
inputSchema: {
|
|
12
|
-
type: "object",
|
|
13
|
-
properties: {
|
|
14
|
-
project_id: { type: "string", description: "Project UUID." },
|
|
15
|
-
limit: { type: "number", description: "Max deployments to return (default 10, max 20)." },
|
|
16
|
-
},
|
|
17
|
-
required: ["project_id"],
|
|
18
|
-
},
|
|
19
|
-
|
|
20
|
-
async execute({ project_id, limit }) {
|
|
21
|
-
try {
|
|
22
|
-
return await api.get(`/api/engine/${project_id}/frontend/deployments?limit=${limit || 10}`)
|
|
23
|
-
} catch (e) {
|
|
24
|
-
return { error: e.message }
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const getDeploymentLogsTool = {
|
|
30
|
-
name: "get_deployment_logs",
|
|
31
|
-
description: `Get build logs for a specific deployment. Use list_deployments first to get the deployment ID. Useful for debugging failed builds.`,
|
|
32
|
-
|
|
33
|
-
inputSchema: {
|
|
34
|
-
type: "object",
|
|
35
|
-
properties: {
|
|
36
|
-
project_id: { type: "string", description: "Project UUID." },
|
|
37
|
-
deployment_id: { type: "string", description: "Deployment UUID from list_deployments." },
|
|
38
|
-
},
|
|
39
|
-
required: ["project_id", "deployment_id"],
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
async execute({ project_id, deployment_id }) {
|
|
43
|
-
try {
|
|
44
|
-
return await api.get(`/api/engine/${project_id}/frontend/deployments/${deployment_id}/logs`)
|
|
45
|
-
} catch (e) {
|
|
46
|
-
return { error: e.message }
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export const frontendStatusTool = {
|
|
52
|
-
name: "get_frontend_status",
|
|
53
|
-
description: "Get the current frontend deployment status — URL, status, last deploy time, size.",
|
|
54
|
-
|
|
55
|
-
inputSchema: {
|
|
56
|
-
type: "object",
|
|
57
|
-
properties: {
|
|
58
|
-
project_id: { type: "string", description: "Project UUID." },
|
|
59
|
-
},
|
|
60
|
-
required: ["project_id"],
|
|
61
|
-
},
|
|
62
|
-
|
|
63
|
-
async execute({ project_id }) {
|
|
64
|
-
try {
|
|
65
|
-
return await api.get(`/api/engine/${project_id}/frontend`)
|
|
66
|
-
} catch (e) {
|
|
67
|
-
return { error: e.message }
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export const buildStatusTool = {
|
|
73
|
-
name: "get_build_status",
|
|
74
|
-
description: `Get the latest build status from Cloudflare Pages.
|
|
75
|
-
|
|
76
|
-
Returns: status (queued/building/success/failure), current stage, progress %, URL.
|
|
77
|
-
Useful to check if a deploy has finished building.`,
|
|
78
|
-
|
|
79
|
-
inputSchema: {
|
|
80
|
-
type: "object",
|
|
81
|
-
properties: {
|
|
82
|
-
project_id: { type: "string", description: "Project UUID." },
|
|
83
|
-
},
|
|
84
|
-
required: ["project_id"],
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
async execute({ project_id }) {
|
|
88
|
-
try {
|
|
89
|
-
return await api.get(`/api/engine/${project_id}/frontend/build-status`)
|
|
90
|
-
} catch (e) {
|
|
91
|
-
return { error: e.message }
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
}
|