@dypai-ai/mcp 1.5.13 → 1.5.14
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/index.js +1 -1
- package/src/tools/capability-kits.js +275 -12
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1121,7 +1121,7 @@ async function handleRequest(msg) {
|
|
|
1121
1121
|
return makeResponse(id, {
|
|
1122
1122
|
protocolVersion: "2024-11-05",
|
|
1123
1123
|
capabilities: { tools: {} },
|
|
1124
|
-
serverInfo: { name: "dypai", version: "1.5.
|
|
1124
|
+
serverInfo: { name: "dypai", version: "1.5.14" },
|
|
1125
1125
|
instructions: SERVER_INSTRUCTIONS,
|
|
1126
1126
|
})
|
|
1127
1127
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import crypto from "crypto"
|
|
1
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, copyFileSync } from "fs"
|
|
3
|
+
import { tmpdir } from "os"
|
|
2
4
|
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "path"
|
|
3
5
|
import { fileURLToPath } from "url"
|
|
6
|
+
import { proxyToolCall } from "./proxy.js"
|
|
4
7
|
|
|
5
8
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
6
9
|
const DEFAULT_LIMIT = 5
|
|
10
|
+
const DEFAULT_R2_BUCKET = "organizations-storage"
|
|
11
|
+
const DEFAULT_R2_PREFIX = "capability-kits"
|
|
7
12
|
|
|
8
13
|
function readJson(path) {
|
|
9
14
|
return JSON.parse(readFileSync(path, "utf8"))
|
|
@@ -46,10 +51,220 @@ function resolveKitsRoot(inputRoot) {
|
|
|
46
51
|
if (fromThisFile) return join(fromThisFile, "dypai-capability-kits")
|
|
47
52
|
|
|
48
53
|
throw new Error(
|
|
49
|
-
"Capability kit repo not found. Set DYPAI_CAPABILITY_KITS_ROOT
|
|
54
|
+
"Capability kit repo not found locally. Set DYPAI_CAPABILITY_KITS_ROOT for local authoring or configure R2 credentials for remote kit install.",
|
|
50
55
|
)
|
|
51
56
|
}
|
|
52
57
|
|
|
58
|
+
function tryResolveKitsRoot(inputRoot) {
|
|
59
|
+
try {
|
|
60
|
+
return resolveKitsRoot(inputRoot)
|
|
61
|
+
} catch {
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function hasLocalKit(root, slug, version) {
|
|
67
|
+
return Boolean(root) && existsSync(join(root, "kits", slug, version, "kit.json"))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function sha256Buffer(buffer) {
|
|
71
|
+
return crypto.createHash("sha256").update(buffer).digest("hex")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function safeLogicalPath(path) {
|
|
75
|
+
if (typeof path !== "string" || !path.trim()) throw new Error("Remote kit file path is invalid")
|
|
76
|
+
const normalized = path.replace(/\\/g, "/").replace(/^\/+/, "")
|
|
77
|
+
if (!normalized || normalized.includes("..") || isAbsolute(normalized)) {
|
|
78
|
+
throw new Error(`Unsafe remote kit file path: ${path}`)
|
|
79
|
+
}
|
|
80
|
+
return normalized
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function parseR2SourceUri(sourceUri, slug) {
|
|
84
|
+
if (typeof sourceUri !== "string" || !sourceUri.startsWith("r2://")) return null
|
|
85
|
+
const withoutScheme = sourceUri.slice("r2://".length)
|
|
86
|
+
const slash = withoutScheme.indexOf("/")
|
|
87
|
+
if (slash <= 0) throw new Error(`Invalid capability kit source_uri: ${sourceUri}`)
|
|
88
|
+
const bucket = withoutScheme.slice(0, slash)
|
|
89
|
+
const rootKey = withoutScheme.slice(slash + 1).replace(/^\/+|\/+$/g, "")
|
|
90
|
+
if (!rootKey || !rootKey.split("/").includes(slug)) {
|
|
91
|
+
throw new Error(`Capability kit source_uri does not look like a kit root for ${slug}: ${sourceUri}`)
|
|
92
|
+
}
|
|
93
|
+
return { bucket, rootKey }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resolveRemoteKitLocation(slug, sourceUri) {
|
|
97
|
+
const parsed = parseR2SourceUri(sourceUri, slug)
|
|
98
|
+
if (parsed) return parsed
|
|
99
|
+
const bucket = process.env.CAPABILITY_KITS_R2_BUCKET
|
|
100
|
+
|| process.env.DYPAI_CAPABILITY_KITS_R2_BUCKET
|
|
101
|
+
|| process.env.CLOUDFLARE_R2_CAPABILITY_KITS_BUCKET
|
|
102
|
+
|| DEFAULT_R2_BUCKET
|
|
103
|
+
const prefix = (process.env.CAPABILITY_KITS_R2_PREFIX || DEFAULT_R2_PREFIX).replace(/^\/+|\/+$/g, "")
|
|
104
|
+
return {
|
|
105
|
+
bucket,
|
|
106
|
+
rootKey: `${prefix}/${slug}`.replace(/^\/+|\/+$/g, "").replace(/\/+/g, "/"),
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function resolveR2Config(slug, sourceUri) {
|
|
111
|
+
const endpoint = (process.env.CLOUDFLARE_R2_ENDPOINT_URL || "").replace(/\/$/, "")
|
|
112
|
+
const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID || ""
|
|
113
|
+
const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY || ""
|
|
114
|
+
if (!endpoint || !accessKeyId || !secretAccessKey) {
|
|
115
|
+
throw new Error("Capability kit R2 storage is not configured. Set CLOUDFLARE_R2_ENDPOINT_URL, CLOUDFLARE_R2_ACCESS_KEY_ID, and CLOUDFLARE_R2_SECRET_ACCESS_KEY.")
|
|
116
|
+
}
|
|
117
|
+
return { endpoint, accessKeyId, secretAccessKey, ...resolveRemoteKitLocation(slug, sourceUri) }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function hmac(key, value, encoding) {
|
|
121
|
+
return crypto.createHmac("sha256", key).update(value).digest(encoding)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function sha256Hex(value) {
|
|
125
|
+
return crypto.createHash("sha256").update(value).digest("hex")
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function encodeRfc3986(value) {
|
|
129
|
+
return encodeURIComponent(value).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function r2Url(config, key) {
|
|
133
|
+
const encodedKey = key.split("/").map(encodeRfc3986).join("/")
|
|
134
|
+
return new URL(`${config.endpoint}/${encodeRfc3986(config.bucket)}/${encodedKey}`)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function signingKey(secretAccessKey, dateStamp) {
|
|
138
|
+
const dateKey = hmac(`AWS4${secretAccessKey}`, dateStamp)
|
|
139
|
+
const dateRegionKey = hmac(dateKey, "auto")
|
|
140
|
+
const dateRegionServiceKey = hmac(dateRegionKey, "s3")
|
|
141
|
+
return hmac(dateRegionServiceKey, "aws4_request")
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function signedR2Headers(config, method, url, payloadHash) {
|
|
145
|
+
const now = new Date()
|
|
146
|
+
const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "")
|
|
147
|
+
const dateStamp = amzDate.slice(0, 8)
|
|
148
|
+
const canonicalHeaders = [
|
|
149
|
+
`host:${url.host}`,
|
|
150
|
+
`x-amz-content-sha256:${payloadHash}`,
|
|
151
|
+
`x-amz-date:${amzDate}`,
|
|
152
|
+
"",
|
|
153
|
+
].join("\n")
|
|
154
|
+
const signedHeaders = "host;x-amz-content-sha256;x-amz-date"
|
|
155
|
+
const canonicalRequest = [
|
|
156
|
+
method,
|
|
157
|
+
url.pathname,
|
|
158
|
+
url.searchParams.toString(),
|
|
159
|
+
canonicalHeaders,
|
|
160
|
+
signedHeaders,
|
|
161
|
+
payloadHash,
|
|
162
|
+
].join("\n")
|
|
163
|
+
const credentialScope = `${dateStamp}/auto/s3/aws4_request`
|
|
164
|
+
const stringToSign = [
|
|
165
|
+
"AWS4-HMAC-SHA256",
|
|
166
|
+
amzDate,
|
|
167
|
+
credentialScope,
|
|
168
|
+
sha256Hex(canonicalRequest),
|
|
169
|
+
].join("\n")
|
|
170
|
+
const signature = hmac(signingKey(config.secretAccessKey, dateStamp), stringToSign, "hex")
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
Authorization: `AWS4-HMAC-SHA256 Credential=${config.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`,
|
|
174
|
+
"x-amz-content-sha256": payloadHash,
|
|
175
|
+
"x-amz-date": amzDate,
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function r2Get(config, key) {
|
|
180
|
+
const url = r2Url(config, key)
|
|
181
|
+
const response = await fetch(url, {
|
|
182
|
+
method: "GET",
|
|
183
|
+
headers: signedR2Headers(config, "GET", url, sha256Hex(Buffer.alloc(0))),
|
|
184
|
+
})
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
throw new Error(`R2 GET failed for ${key}: ${response.status} ${await response.text()}`)
|
|
187
|
+
}
|
|
188
|
+
return Buffer.from(await response.arrayBuffer())
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function parseRemoteFilesManifest(value, slug, requestedVersion) {
|
|
192
|
+
const parsed = JSON.parse(value)
|
|
193
|
+
if (!isObject(parsed)) throw new Error("Remote kit manifest must be a JSON object")
|
|
194
|
+
if (parsed.slug && parsed.slug !== slug) throw new Error(`Remote kit manifest slug mismatch: expected ${slug}, got ${parsed.slug}`)
|
|
195
|
+
if (parsed.version && parsed.version !== requestedVersion) {
|
|
196
|
+
throw new Error(`Remote kit manifest version mismatch: expected ${requestedVersion}, got ${parsed.version}`)
|
|
197
|
+
}
|
|
198
|
+
if (!Array.isArray(parsed.files) || !parsed.files.length) {
|
|
199
|
+
throw new Error(`Remote kit manifest for ${slug} has no files`)
|
|
200
|
+
}
|
|
201
|
+
return parsed
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function cachedRemoteKitComplete(kitDirPath, manifest) {
|
|
205
|
+
return (manifest.files || []).every((file) => {
|
|
206
|
+
try {
|
|
207
|
+
const rel = safeLogicalPath(file.path)
|
|
208
|
+
const path = join(kitDirPath, rel)
|
|
209
|
+
if (!existsSync(path)) return false
|
|
210
|
+
return file.sha256 ? sha256Buffer(readFileSync(path)) === file.sha256 : true
|
|
211
|
+
} catch {
|
|
212
|
+
return false
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function remoteKitKey(config, logicalPath) {
|
|
218
|
+
return `${config.rootKey}/${logicalPath}`.replace(/\/+/g, "/").replace(/^\/+/, "")
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function downloadRemoteCapabilityKit(slug, version, sourceUri) {
|
|
222
|
+
const config = resolveR2Config(slug, sourceUri)
|
|
223
|
+
const manifestKey = remoteKitKey(config, "manifest.files.json")
|
|
224
|
+
const manifestText = (await r2Get(config, manifestKey)).toString("utf8")
|
|
225
|
+
const manifest = parseRemoteFilesManifest(manifestText, slug, version)
|
|
226
|
+
const contentHash = typeof manifest.contentHash === "string" && manifest.contentHash.trim()
|
|
227
|
+
? manifest.contentHash.trim()
|
|
228
|
+
: sha256Buffer(Buffer.from(manifestText))
|
|
229
|
+
const cacheRoot = join(tmpdir(), "dypai-capability-kits", slug, contentHash)
|
|
230
|
+
const kitDirPath = join(cacheRoot, "kits", slug, version)
|
|
231
|
+
const localManifestPath = join(kitDirPath, "manifest.files.json")
|
|
232
|
+
|
|
233
|
+
if (existsSync(localManifestPath) && cachedRemoteKitComplete(kitDirPath, manifest)) {
|
|
234
|
+
return cacheRoot
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
mkdirSync(kitDirPath, { recursive: true })
|
|
238
|
+
for (const file of manifest.files || []) {
|
|
239
|
+
const rel = safeLogicalPath(file.path)
|
|
240
|
+
const buffer = await r2Get(config, remoteKitKey(config, rel))
|
|
241
|
+
if (file.sha256 && sha256Buffer(buffer) !== file.sha256) {
|
|
242
|
+
throw new Error(`Capability kit file hash mismatch for ${slug}/${rel}`)
|
|
243
|
+
}
|
|
244
|
+
const target = join(kitDirPath, rel)
|
|
245
|
+
mkdirSync(dirname(target), { recursive: true })
|
|
246
|
+
writeFileSync(target, buffer)
|
|
247
|
+
}
|
|
248
|
+
writeFileSync(localManifestPath, manifestText)
|
|
249
|
+
return cacheRoot
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function resolveKitsRootForKit({ kits_root, slug, version, source_uri }) {
|
|
253
|
+
const sourceMode = (process.env.DYPAI_CAPABILITY_KITS_SOURCE || "").toLowerCase()
|
|
254
|
+
const localRoot = sourceMode === "remote" ? null : tryResolveKitsRoot(kits_root)
|
|
255
|
+
if (hasLocalKit(localRoot, slug, version)) return { root: localRoot, source: "local_repo" }
|
|
256
|
+
const remoteRoot = await downloadRemoteCapabilityKit(slug, version, source_uri)
|
|
257
|
+
return { root: remoteRoot, source: "r2" }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function shouldSearchRemote(kitsRoot) {
|
|
261
|
+
const source = (process.env.DYPAI_CAPABILITY_KITS_SOURCE || "").toLowerCase()
|
|
262
|
+
if (source === "local") return false
|
|
263
|
+
if (source === "remote") return true
|
|
264
|
+
if (kitsRoot || process.env.DYPAI_CAPABILITY_KITS_ROOT) return false
|
|
265
|
+
return Boolean(process.env.DYPAI_TOKEN)
|
|
266
|
+
}
|
|
267
|
+
|
|
53
268
|
function resolveWorkspaceRoot(inputRoot) {
|
|
54
269
|
if (inputRoot) {
|
|
55
270
|
const root = resolve(inputRoot)
|
|
@@ -306,7 +521,7 @@ function writeTemplateFile({ sourceAbs, targetRel, workspaceRoot, manifest, nami
|
|
|
306
521
|
if (overwrite === "fail") throw new Error(`Target already exists: ${targetRel}`)
|
|
307
522
|
if (overwrite !== "replace" || !ownedTargets(record, manifest.slug, manifest.version).has(targetRel)) {
|
|
308
523
|
skipped.push(targetRel)
|
|
309
|
-
return
|
|
524
|
+
return false
|
|
310
525
|
}
|
|
311
526
|
}
|
|
312
527
|
|
|
@@ -319,6 +534,7 @@ function writeTemplateFile({ sourceAbs, targetRel, workspaceRoot, manifest, nami
|
|
|
319
534
|
copyFileSync(sourceAbs, targetAbs)
|
|
320
535
|
}
|
|
321
536
|
copied.push(targetRel)
|
|
537
|
+
return true
|
|
322
538
|
}
|
|
323
539
|
|
|
324
540
|
export const searchCapabilityKitsTool = {
|
|
@@ -330,11 +546,24 @@ export const searchCapabilityKitsTool = {
|
|
|
330
546
|
query: { type: "string", description: "Feature need and app domain, e.g. booking calendar for hotel reservations." },
|
|
331
547
|
limit: { type: "integer", default: DEFAULT_LIMIT, minimum: 1, maximum: 10 },
|
|
332
548
|
filters: { type: "object", description: "Optional filters: category, maturity, app_type, screen_type, feature_tag, requires." },
|
|
333
|
-
kits_root: { type: "string", description: "Optional local path to dypai-capability-kits.
|
|
549
|
+
kits_root: { type: "string", description: "Optional local path to dypai-capability-kits. When omitted, the tool may use the remote MCP registry so local behavior matches production." },
|
|
334
550
|
},
|
|
335
551
|
required: ["query"],
|
|
336
552
|
},
|
|
337
553
|
async execute({ query, limit = DEFAULT_LIMIT, filters = {}, kits_root }) {
|
|
554
|
+
if (shouldSearchRemote(kits_root)) {
|
|
555
|
+
try {
|
|
556
|
+
const remote = await proxyToolCall("search_capability_kits", { query, limit, filters })
|
|
557
|
+
if (isObject(remote)) {
|
|
558
|
+
return { ...remote, source: remote.source || "mcp_cloud" }
|
|
559
|
+
}
|
|
560
|
+
return remote
|
|
561
|
+
} catch (error) {
|
|
562
|
+
if (!tryResolveKitsRoot(kits_root)) throw error
|
|
563
|
+
process.stderr.write(`Capability kit remote search failed, falling back to local repo: ${error.message}\n`)
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
338
567
|
const root = resolveKitsRoot(kits_root)
|
|
339
568
|
const kits = listKits(root)
|
|
340
569
|
.filter(({ manifest }) => matchesFilter(manifest, filters))
|
|
@@ -368,13 +597,13 @@ export const searchCapabilityKitsTool = {
|
|
|
368
597
|
},
|
|
369
598
|
}
|
|
370
599
|
|
|
371
|
-
function inspectCapabilityKit({ slug, version = "1.0.0", kits_root }) {
|
|
372
|
-
const root =
|
|
600
|
+
async function inspectCapabilityKit({ slug, version = "1.0.0", kits_root, source_uri }) {
|
|
601
|
+
const { root, source } = await resolveKitsRootForKit({ kits_root, slug, version, source_uri })
|
|
373
602
|
const { dir, manifest } = loadKit(root, slug, version)
|
|
374
603
|
return {
|
|
375
604
|
ok: true,
|
|
376
605
|
operation: "inspect",
|
|
377
|
-
source
|
|
606
|
+
source,
|
|
378
607
|
kitsRoot: root,
|
|
379
608
|
kit: manifest,
|
|
380
609
|
assets: assetIndex(dir),
|
|
@@ -395,13 +624,20 @@ async function applyCapabilityKit({
|
|
|
395
624
|
overwrite = "skip",
|
|
396
625
|
workspace_root,
|
|
397
626
|
kits_root,
|
|
627
|
+
source_uri,
|
|
398
628
|
}) {
|
|
399
|
-
const root =
|
|
629
|
+
const { root, source } = await resolveKitsRootForKit({ kits_root, slug, version, source_uri })
|
|
400
630
|
const workspaceRoot = resolveWorkspaceRoot(workspace_root)
|
|
401
631
|
const { dir, manifest } = loadKit(root, slug, version)
|
|
402
632
|
const record = readInstallRecord(workspaceRoot)
|
|
403
633
|
const previousEntries = (record.kits || []).filter((item) => item.slug === manifest.slug && item.version === manifest.version)
|
|
404
634
|
const copied = []
|
|
635
|
+
const copiedByKind = {
|
|
636
|
+
frontend: [],
|
|
637
|
+
backend: [],
|
|
638
|
+
database: [],
|
|
639
|
+
}
|
|
640
|
+
const databaseTargetsBySource = new Map()
|
|
405
641
|
const skipped = []
|
|
406
642
|
|
|
407
643
|
const installFrontend = install.frontend !== false
|
|
@@ -416,7 +652,9 @@ async function applyCapabilityKit({
|
|
|
416
652
|
if (target.frontendDir) {
|
|
417
653
|
targetRel = join(target.frontendDir, stripFrontendPrefix(asset.source)).split(sep).join("/")
|
|
418
654
|
}
|
|
419
|
-
writeTemplateFile({ sourceAbs, targetRel, workspaceRoot, manifest, naming, kind: "frontend", overwrite, record, copied, skipped })
|
|
655
|
+
if (writeTemplateFile({ sourceAbs, targetRel, workspaceRoot, manifest, naming, kind: "frontend", overwrite, record, copied, skipped })) {
|
|
656
|
+
copiedByKind.frontend.push(targetRel)
|
|
657
|
+
}
|
|
420
658
|
}
|
|
421
659
|
}
|
|
422
660
|
|
|
@@ -429,7 +667,9 @@ async function applyCapabilityKit({
|
|
|
429
667
|
if (target.endpointDir) {
|
|
430
668
|
targetRel = join(target.endpointDir, adaptTarget(asset.source.split("/").pop(), manifest, naming)).split(sep).join("/")
|
|
431
669
|
}
|
|
432
|
-
writeTemplateFile({ sourceAbs, targetRel, workspaceRoot, manifest, naming, kind: "backend", overwrite, record, copied, skipped })
|
|
670
|
+
if (writeTemplateFile({ sourceAbs, targetRel, workspaceRoot, manifest, naming, kind: "backend", overwrite, record, copied, skipped })) {
|
|
671
|
+
copiedByKind.backend.push(targetRel)
|
|
672
|
+
}
|
|
433
673
|
}
|
|
434
674
|
}
|
|
435
675
|
|
|
@@ -443,7 +683,10 @@ async function applyCapabilityKit({
|
|
|
443
683
|
if (target.databaseDir) {
|
|
444
684
|
targetRel = join(target.databaseDir, source.split("/").pop()).split(sep).join("/")
|
|
445
685
|
}
|
|
446
|
-
writeTemplateFile({ sourceAbs, targetRel, workspaceRoot, manifest, naming, kind: "database", overwrite, record, copied, skipped })
|
|
686
|
+
if (writeTemplateFile({ sourceAbs, targetRel, workspaceRoot, manifest, naming, kind: "database", overwrite, record, copied, skipped })) {
|
|
687
|
+
copiedByKind.database.push(targetRel)
|
|
688
|
+
databaseTargetsBySource.set(source, targetRel)
|
|
689
|
+
}
|
|
447
690
|
}
|
|
448
691
|
}
|
|
449
692
|
|
|
@@ -460,6 +703,14 @@ async function applyCapabilityKit({
|
|
|
460
703
|
const databaseTables = normalizeList(manifest.databaseManifest?.tables).map((table) =>
|
|
461
704
|
adaptTarget(table, manifest, naming),
|
|
462
705
|
)
|
|
706
|
+
const schemaSource = installDatabase && isObject(manifest.databaseManifest) && typeof manifest.databaseManifest.schema === "string"
|
|
707
|
+
? manifest.databaseManifest.schema
|
|
708
|
+
: ""
|
|
709
|
+
const seedSource = installDatabase && isObject(manifest.databaseManifest) && typeof manifest.databaseManifest.seed === "string"
|
|
710
|
+
? manifest.databaseManifest.seed
|
|
711
|
+
: ""
|
|
712
|
+
const schemaFile = schemaSource ? databaseTargetsBySource.get(schemaSource) : undefined
|
|
713
|
+
const seedFile = seedSource ? databaseTargetsBySource.get(seedSource) : undefined
|
|
463
714
|
|
|
464
715
|
const previousFiles = previousEntries.flatMap((item) => Array.isArray(item.files) ? item.files : [])
|
|
465
716
|
const fileMap = new Map()
|
|
@@ -488,11 +739,15 @@ async function applyCapabilityKit({
|
|
|
488
739
|
return {
|
|
489
740
|
ok: true,
|
|
490
741
|
operation: "apply",
|
|
742
|
+
source,
|
|
491
743
|
slug: manifest.slug,
|
|
492
744
|
version: manifest.version,
|
|
493
745
|
workspaceRoot,
|
|
494
746
|
installed: {
|
|
495
747
|
files: copied,
|
|
748
|
+
frontendFiles: copiedByKind.frontend,
|
|
749
|
+
backendFiles: copiedByKind.backend,
|
|
750
|
+
databaseFiles: copiedByKind.database,
|
|
496
751
|
skipped,
|
|
497
752
|
recordFile: ".dypai/kits/installed.json",
|
|
498
753
|
},
|
|
@@ -503,7 +758,12 @@ async function applyCapabilityKit({
|
|
|
503
758
|
},
|
|
504
759
|
database: {
|
|
505
760
|
tables: databaseTables,
|
|
761
|
+
sqlFiles: copiedByKind.database,
|
|
762
|
+
schemaFile,
|
|
763
|
+
seedFile,
|
|
506
764
|
requiresExecuteSql: Boolean(manifest.install?.requiresExecuteSql || databaseTables.length),
|
|
765
|
+
applyWith: "execute_sql",
|
|
766
|
+
schemaRefresh: "automatic_after_successful_ddl",
|
|
507
767
|
},
|
|
508
768
|
dependencies,
|
|
509
769
|
nextSteps: [
|
|
@@ -511,7 +771,9 @@ async function applyCapabilityKit({
|
|
|
511
771
|
? [`Add any missing package dependencies to the target workspace package.json, then install them locally before building: ${dependencies.join(", ")}.`]
|
|
512
772
|
: []),
|
|
513
773
|
"Wire installed frontend components into the selected app page or route.",
|
|
514
|
-
...(databaseTables.length ? [
|
|
774
|
+
...(databaseTables.length ? [
|
|
775
|
+
`Before backend tests, ensure tables exist (${databaseTables.join(", ")}): read ${schemaFile || "the installed schema SQL"} and execute its safe CREATE/ALTER statements with execute_sql. Do not edit dypai/schema.sql; it refreshes automatically.`,
|
|
776
|
+
] : []),
|
|
515
777
|
...(backendEndpoints.length ? ["Run backend validation and endpoint tests for installed/renamed endpoints."] : []),
|
|
516
778
|
"Run frontend verification after wiring imports and data callbacks.",
|
|
517
779
|
],
|
|
@@ -527,6 +789,7 @@ export const manageCapabilityKitTool = {
|
|
|
527
789
|
operation: { type: "string", enum: ["inspect", "apply"], description: "inspect returns manifest/assets. apply copies kit files into the workspace." },
|
|
528
790
|
slug: { type: "string" },
|
|
529
791
|
version: { type: "string", default: "1.0.0" },
|
|
792
|
+
source_uri: { type: "string", description: "Optional r2://... kit root from search results. Used when the kit is not available locally." },
|
|
530
793
|
targetFeature: { type: "string", description: "Domain/feature being built, e.g. hotel reservations." },
|
|
531
794
|
install: {
|
|
532
795
|
type: "object",
|
|
@@ -555,7 +818,7 @@ export const manageCapabilityKitTool = {
|
|
|
555
818
|
},
|
|
556
819
|
overwrite: { type: "string", enum: ["skip", "fail", "replace"], default: "skip" },
|
|
557
820
|
workspace_root: { type: "string", description: "Optional absolute path to the target app workspace." },
|
|
558
|
-
kits_root: { type: "string", description: "Optional local path to dypai-capability-kits." },
|
|
821
|
+
kits_root: { type: "string", description: "Optional local path to dypai-capability-kits. Pass this to force local authoring source; omit it to allow R2 fallback." },
|
|
559
822
|
},
|
|
560
823
|
required: ["operation", "slug"],
|
|
561
824
|
},
|