@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/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
- // .env is gitignored → the source API does NOT return it → the frontend won't
136
- // know the engine URL. If it's missing after sync, tell the agent to create it.
137
- // Engine URL convention: https://<project_id>.dypai.app (override with DYPAI_ENGINE_BASE).
138
- const envMissing = !existsSync(join(targetDirectory, ".env"))
139
- if (envMissing) {
140
- const engineBase = process.env.DYPAI_ENGINE_BASE || "dypai.app"
141
- const engineUrl = `https://${project_id}.${engineBase}`
142
- next_steps.push(
143
- `Create .env in ${targetDirectory} with \`VITE_DYPAI_URL=${engineUrl}\` (and \`VITE_PROJECT_ID=${project_id}\`). ` +
144
- `Use NEXT_PUBLIC_DYPAI_URL instead of VITE_DYPAI_URL for Next.js projects. ` +
145
- `The frontend SDK reads this to reach the engine — without .env, API calls will fail with network errors.`
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,
@@ -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
- }