yap2app 1.1.5 → 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) => {
@@ -148,8 +162,8 @@ const server = http.createServer(async (req, res) => {
148
162
  res.writeHead(200, headers);
149
163
  res.end(JSON.stringify({
150
164
  status: 'healthy',
151
- service: 'Yap2App-Localhost-Bridge',
152
- version: '1.1.5',
165
+ service: 'yap2app-bridge',
166
+ version: '1.1.6',
153
167
  port: PORT,
154
168
  pair_code: PAIR_CODE,
155
169
  project_root: PROJECT_ROOT,
@@ -163,12 +177,11 @@ 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(`[Yap2App Bridge] 🔍 Scanning shallow codebase context in: ${PROJECT_ROOT}${promptQuery ? ` (ranking for query: "${promptQuery}")` : ''}`);
167
180
  const context = await scanCodebase(PROJECT_ROOT, promptQuery);
168
181
  res.writeHead(200, headers);
169
182
  res.end(JSON.stringify(context));
170
183
  } catch (e) {
171
- console.error('[Yap2App Bridge] Context scan error:', e);
184
+ console.error(' [bridge] Context scan error:', e.message);
172
185
  res.writeHead(500, headers);
173
186
  res.end(JSON.stringify({ error: e.message }));
174
187
  }
@@ -211,7 +224,7 @@ const server = http.createServer(async (req, res) => {
211
224
  diffs: diffResults
212
225
  }));
213
226
  } catch (e) {
214
- console.error('[Yap2App Bridge] Diff error:', e);
227
+ console.error(' [bridge] Diff error:', e.message);
215
228
  res.writeHead(500, headers);
216
229
  res.end(JSON.stringify({ error: e.message }));
217
230
  }
@@ -230,14 +243,14 @@ 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 });
237
250
 
238
251
  // Write file with utf8 encoding
239
252
  await fs.writeFile(fullPath, file.content, 'utf8');
240
- console.log(`[Yap2App Bridge] âœī¸ Successfully wrote: ${file.path}`);
253
+ console.log(` [bridge] Wrote ${file.path}`);
241
254
  written.push(file.path);
242
255
  }
243
256
 
@@ -250,7 +263,7 @@ const server = http.createServer(async (req, res) => {
250
263
  message: `Successfully wrote ${written.length} files to ${path.basename(PROJECT_ROOT)}`
251
264
  }));
252
265
  } catch (e) {
253
- console.error('[Yap2App Bridge] Write to disk error:', e);
266
+ console.error(' [bridge] Write error:', e.message);
254
267
  res.writeHead(500, headers);
255
268
  res.end(JSON.stringify({ error: e.message }));
256
269
  }
@@ -266,40 +279,49 @@ 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===============================================================`);
270
- console.log(`📡 Localhost port ${PORT} is active. Cloud Relay continues seamlessly!`);
271
- console.log(`📁 Active Workspace: ${PROJECT_ROOT}`);
272
- console.log(`🔑 Pairing Code: ${PAIR_CODE}`);
273
- console.log(`🌐 Web Studio: ${CLOUD_RELAY_URL}`);
274
- console.log(`===============================================================\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`);
275
292
  return;
276
293
  }
277
- console.error('[Yap2App Bridge Error]:', err);
294
+ console.error(' [bridge] Error:', err.message || err);
278
295
  });
279
296
 
280
297
  export async function startBridge(port = PORT) {
281
298
  server.listen(port, '0.0.0.0', () => {
282
- console.log(`\n===============================================================`);
283
- console.log(`🚀 Yap2App Localhost Bridge & Telemetry is ACTIVE!`);
284
- console.log(`📡 Local Endpoint: http://127.0.0.1:${port}`);
285
- console.log(`📁 Active Workspace: ${PROJECT_ROOT}`);
286
- console.log(`🔑 Pairing Code: ${PAIR_CODE}`);
287
- console.log(`🌐 Connected to Web Studio: ${CLOUD_RELAY_URL}`);
288
- console.log(`đŸ›Ąī¸ Enterprise Diff, W3C PNA & Cloud Relay Enabled`);
289
- console.log(`===============================================================\n`);
290
- console.log(`Ready for 1-click sync from Yap2App Web Studio UI...\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`);
291
309
  });
292
310
 
293
311
  // Initial scan and Cloud Relay registration
294
312
  try {
295
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
+ }
296
318
  await sendCloudRelayRequest('POST', '/api/v1/bridge/heartbeat', {
297
319
  pair_code: PAIR_CODE,
298
320
  project_name: path.basename(PROJECT_ROOT),
299
321
  project_root: PROJECT_ROOT,
300
322
  framework: initialContext.framework,
301
323
  context: initialContext,
302
- version: '1.1.5'
324
+ version: '1.1.6'
303
325
  });
304
326
  } catch (e) {}
305
327
 
@@ -313,7 +335,7 @@ export async function startBridge(port = PORT) {
313
335
  project_root: PROJECT_ROOT,
314
336
  framework: context.framework,
315
337
  context: context,
316
- version: '1.1.5'
338
+ version: '1.1.6'
317
339
  });
318
340
 
319
341
  // If pending write tasks, fetch and execute them on local disk
@@ -323,10 +345,10 @@ export async function startBridge(port = PORT) {
323
345
  for (const task of tasks) {
324
346
  const written = [];
325
347
  for (const file of (task.files || [])) {
326
- const fullPath = path.join(PROJECT_ROOT, file.path);
348
+ const fullPath = safeResolvePath(PROJECT_ROOT, file.path);
327
349
  await fs.mkdir(path.dirname(fullPath), { recursive: true });
328
350
  await fs.writeFile(fullPath, file.content, 'utf8');
329
- console.log(`[Yap2App Bridge] âœī¸ Wrote approved file: ${file.path}`);
351
+ console.log(` [bridge] Wrote ${file.path}`);
330
352
  written.push(file.path);
331
353
  }
332
354
  await sendCloudRelayRequest('POST', '/api/v1/bridge/complete-write', {
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);
@@ -155,7 +126,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
155
126
  export async function runMcpServer() {
156
127
  const transport = new StdioServerTransport();
157
128
  await server.connect(transport);
158
- console.error("⚡ Yap2App MCP Server running on stdio");
129
+ console.error("Yap2App MCP Server running on stdio");
159
130
  }
160
131
 
161
132
  const isDirectRun = Boolean(typeof process !== 'undefined' && process.argv && process.argv[1] && (process.argv[1].endsWith('mcp.js') || process.argv[1].endsWith('mcp')));
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "yap2app",
3
- "version": "1.1.5",
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 =>