yap2app 1.1.6 → 1.1.7

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/bridge.js CHANGED
@@ -18,6 +18,20 @@ const PROJECT_ROOT = process.cwd();
18
18
  const CLOUD_RELAY_URL = process.env.YAP2APP_CLOUD_URL || 'https://yap2app-dot-cybage-hackathon.uc.r.appspot.com';
19
19
  const PAIR_CODE = (process.env.YAP2APP_PAIR_CODE || ('CYB-' + Math.floor(100 + Math.random() * 900))).toUpperCase();
20
20
 
21
+ /**
22
+ * Validates and resolves relative file paths strictly inside PROJECT_ROOT.
23
+ * Prevents directory traversal attacks (e.g. ../../etc/passwd or absolute path escapes).
24
+ */
25
+ function safeResolvePath(baseDir, relativePath) {
26
+ const cleanRel = (relativePath || '').replace(/^[\\\/]+/, '');
27
+ const resolved = path.resolve(baseDir, cleanRel);
28
+ const normalizedBase = path.resolve(baseDir);
29
+ if (!resolved.startsWith(normalizedBase + path.sep) && resolved !== normalizedBase) {
30
+ throw new Error(`Security Exception: Path traversal attempt blocked for path: "${relativePath}"`);
31
+ }
32
+ return resolved;
33
+ }
34
+
21
35
  // Helper to send outbound HTTPS requests to the Yap2App Cloud Relay
22
36
  function sendCloudRelayRequest(method, endpoint, payload) {
23
37
  return new Promise((resolve) => {
@@ -163,7 +177,6 @@ const server = http.createServer(async (req, res) => {
163
177
  if (req.method === 'GET' && (pathname === '/api/context' || pathname === '/context')) {
164
178
  try {
165
179
  const promptQuery = searchParams.get('q') || '';
166
- console.log(` [bridge] Scanning codebase in ${path.basename(PROJECT_ROOT)}${promptQuery ? ` (query: "${promptQuery}")` : ''}`);
167
180
  const context = await scanCodebase(PROJECT_ROOT, promptQuery);
168
181
  res.writeHead(200, headers);
169
182
  res.end(JSON.stringify(context));
@@ -230,7 +243,7 @@ const server = http.createServer(async (req, res) => {
230
243
  const written = [];
231
244
 
232
245
  for (const file of files) {
233
- const fullPath = path.join(PROJECT_ROOT, file.path);
246
+ const fullPath = safeResolvePath(PROJECT_ROOT, file.path);
234
247
 
235
248
  // Ensure parent directory exists
236
249
  await fs.mkdir(path.dirname(fullPath), { recursive: true });
@@ -266,11 +279,16 @@ const server = http.createServer(async (req, res) => {
266
279
  // Gracefully handle EADDRINUSE without crashing
267
280
  server.on('error', (err) => {
268
281
  if (err && err.code === 'EADDRINUSE') {
269
- console.log(`\n Yap2App Bridge\n`);
270
- console.log(` Port ${PORT} is active. Cloud Relay connected.`);
271
- console.log(` Workspace: ${PROJECT_ROOT}`);
272
- console.log(` Pair Code: ${PAIR_CODE}`);
273
- console.log(` Web Studio: ${CLOUD_RELAY_URL}\n`);
282
+ console.log(`\n---------------------------------------------------------------`);
283
+ console.log(`Yap2App Bridge`);
284
+ console.log(`Port ${PORT} is active. Cloud Relay connected.`);
285
+ console.log(`Active Workspace: ${PROJECT_ROOT}`);
286
+ console.log(`Pairing Code: ${PAIR_CODE}`);
287
+ console.log(`Web Studio: ${CLOUD_RELAY_URL}`);
288
+ console.log(`---------------------------------------------------------------\n`);
289
+ console.log(`Bridge is running.`);
290
+ console.log(`Open Web Studio in your browser to sync components to this workspace.`);
291
+ console.log(`Press Ctrl/Cmd+C to stop.\n`);
274
292
  return;
275
293
  }
276
294
  console.error(' [bridge] Error:', err.message || err);
@@ -278,17 +296,25 @@ server.on('error', (err) => {
278
296
 
279
297
  export async function startBridge(port = PORT) {
280
298
  server.listen(port, '0.0.0.0', () => {
281
- console.log(`\n Yap2App Bridge v1.1.6\n`);
282
- console.log(` Local: http://127.0.0.1:${port}`);
283
- console.log(` Workspace: ${PROJECT_ROOT}`);
284
- console.log(` Pair Code: ${PAIR_CODE}`);
285
- console.log(` Web Studio: ${CLOUD_RELAY_URL}\n`);
286
- console.log(` Ready for sync.\n`);
299
+ console.log(`\n---------------------------------------------------------------`);
300
+ console.log(`Yap2App Bridge v1.1.6`);
301
+ console.log(`Local Endpoint: http://127.0.0.1:${port}`);
302
+ console.log(`Active Workspace: ${PROJECT_ROOT}`);
303
+ console.log(`Pairing Code: ${PAIR_CODE}`);
304
+ console.log(`Web Studio: ${CLOUD_RELAY_URL}`);
305
+ console.log(`---------------------------------------------------------------\n`);
306
+ console.log(`Bridge is running.`);
307
+ console.log(`Open Web Studio in your browser to sync components to this workspace.`);
308
+ console.log(`Press Ctrl/Cmd+C to stop.\n`);
287
309
  });
288
310
 
289
311
  // Initial scan and Cloud Relay registration
290
312
  try {
291
313
  const initialContext = await scanCodebase(PROJECT_ROOT);
314
+ if (initialContext && initialContext.total_files_count === 0) {
315
+ console.log(` ℹ️ Fresh workspace detected (0 code files).`);
316
+ console.log(` Yap2App will automatically scaffold directories and write files when synced from Web Studio.\n`);
317
+ }
292
318
  await sendCloudRelayRequest('POST', '/api/v1/bridge/heartbeat', {
293
319
  pair_code: PAIR_CODE,
294
320
  project_name: path.basename(PROJECT_ROOT),
@@ -319,7 +345,7 @@ export async function startBridge(port = PORT) {
319
345
  for (const task of tasks) {
320
346
  const written = [];
321
347
  for (const file of (task.files || [])) {
322
- const fullPath = path.join(PROJECT_ROOT, file.path);
348
+ const fullPath = safeResolvePath(PROJECT_ROOT, file.path);
323
349
  await fs.mkdir(path.dirname(fullPath), { recursive: true });
324
350
  await fs.writeFile(fullPath, file.content, 'utf8');
325
351
  console.log(` [bridge] Wrote ${file.path}`);
package/mcp.js CHANGED
@@ -41,19 +41,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
41
41
  required: ["componentName"]
42
42
  }
43
43
  },
44
- {
45
- name: "yap2app_mount_snippet",
46
- description: "Generates the parent import statement and JSX mounting code snippet for integrating a new component into an existing page or parent component.",
47
- inputSchema: {
48
- type: "object",
49
- properties: {
50
- componentName: { type: "string", description: "The component name" },
51
- filePath: { type: "string", description: "The component's relative file path" },
52
- parentFile: { type: "string", description: "Optional parent file (e.g. 'src/App.tsx' or 'src/app/page.tsx')" }
53
- },
54
- required: ["componentName", "filePath"]
55
- }
56
- },
57
44
  {
58
45
  name: "yap2app_write_component",
59
46
  description: "Safely writes a React/Tailwind component to the local filesystem, ensuring directories exist.",
@@ -106,7 +93,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
106
93
  const suggestion = {
107
94
  component_name: componentName,
108
95
  target_file_path: relativePath,
109
- import_statement: `import { ${componentName} } from "${importPath}";`,
110
96
  usage_example: `<${componentName} />`
111
97
  };
112
98
 
@@ -115,22 +101,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
115
101
  };
116
102
  }
117
103
 
118
- // 3. yap2app_mount_snippet
119
- if (name === "yap2app_mount_snippet") {
120
- const { componentName, filePath, parentFile } = args;
121
- const cleanImport = filePath.replace(/\.[^/.]+$/, '').replace(/^src\//, '@/');
122
- const snippet = {
123
- target_parent_file: parentFile || "src/App.tsx (or src/app/page.tsx)",
124
- import_line: `import { ${componentName} } from "${cleanImport}";`,
125
- jsx_insertion: `{/* Render ${componentName} */}\n<${componentName} />`
126
- };
127
-
128
- return {
129
- content: [{ type: "text", text: JSON.stringify(snippet, null, 2) }]
130
- };
131
- }
132
-
133
- // 4. yap2app_write_component
104
+ // 3. yap2app_write_component
134
105
  if (name === "yap2app_write_component") {
135
106
  const { filePath, code } = args;
136
107
  const fullPath = path.join(TARGET_DIR, filePath);
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "yap2app",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
4
+ "type": "module",
4
5
  "description": "Autonomous Vibe-to-Prod Localhost Bridge & Model Context Protocol (MCP) Server",
5
6
  "main": "bin/cli.bundle.js",
6
7
  "bin": {
package/scanner.js CHANGED
@@ -106,6 +106,13 @@ function parsePackageJson(content) {
106
106
  * Returns null if no verified signature matches with high confidence.
107
107
  */
108
108
  function detectVerifiedFramework(manifests, allFiles, extensionHistogram) {
109
+ if (!allFiles || allFiles.length === 0) {
110
+ return {
111
+ framework: 'plain_html_css_js',
112
+ displayName: 'Plain HTML / CSS / JS (Fresh Workspace)'
113
+ };
114
+ }
115
+
109
116
  // 1. Google Brightspot CMS / Glue-Bundle / Handlebars Frontend
110
117
  const hasHbsFiles = (extensionHistogram['.hbs'] || 0) > 0 || (extensionHistogram['.handlebars'] || 0) > 0;
111
118
  const hasBrightspotConfigs = allFiles.some(f =>