yap2app 1.1.6 → 1.1.8

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
@@ -17,6 +17,21 @@ const PORT = Number(process.env.YAP2APP_PORT) || 10420;
17
17
  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
+ const CLI_VERSION = '1.1.8';
21
+
22
+ /**
23
+ * Validates and resolves relative file paths strictly inside PROJECT_ROOT.
24
+ * Prevents directory traversal attacks (e.g. ../../etc/passwd or absolute path escapes).
25
+ */
26
+ function safeResolvePath(baseDir, relativePath) {
27
+ const cleanRel = (relativePath || '').replace(/^[\\\/]+/, '');
28
+ const resolved = path.resolve(baseDir, cleanRel);
29
+ const normalizedBase = path.resolve(baseDir);
30
+ if (!resolved.startsWith(normalizedBase + path.sep) && resolved !== normalizedBase) {
31
+ throw new Error(`Security Exception: Path traversal attempt blocked for path: "${relativePath}"`);
32
+ }
33
+ return resolved;
34
+ }
20
35
 
21
36
  // Helper to send outbound HTTPS requests to the Yap2App Cloud Relay
22
37
  function sendCloudRelayRequest(method, endpoint, payload) {
@@ -32,7 +47,7 @@ function sendCloudRelayRequest(method, endpoint, payload) {
32
47
  headers: {
33
48
  'Content-Type': 'application/json',
34
49
  'Content-Length': Buffer.byteLength(body),
35
- 'User-Agent': 'yap2app-cli/1.1.5'
50
+ 'User-Agent': `yap2app-cli/${CLI_VERSION}`
36
51
  },
37
52
  timeout: 6000
38
53
  }, (res) => {
@@ -149,7 +164,7 @@ const server = http.createServer(async (req, res) => {
149
164
  res.end(JSON.stringify({
150
165
  status: 'healthy',
151
166
  service: 'yap2app-bridge',
152
- version: '1.1.6',
167
+ version: CLI_VERSION,
153
168
  port: PORT,
154
169
  pair_code: PAIR_CODE,
155
170
  project_root: PROJECT_ROOT,
@@ -163,7 +178,6 @@ const server = http.createServer(async (req, res) => {
163
178
  if (req.method === 'GET' && (pathname === '/api/context' || pathname === '/context')) {
164
179
  try {
165
180
  const promptQuery = searchParams.get('q') || '';
166
- console.log(` [bridge] Scanning codebase in ${path.basename(PROJECT_ROOT)}${promptQuery ? ` (query: "${promptQuery}")` : ''}`);
167
181
  const context = await scanCodebase(PROJECT_ROOT, promptQuery);
168
182
  res.writeHead(200, headers);
169
183
  res.end(JSON.stringify(context));
@@ -230,7 +244,7 @@ const server = http.createServer(async (req, res) => {
230
244
  const written = [];
231
245
 
232
246
  for (const file of files) {
233
- const fullPath = path.join(PROJECT_ROOT, file.path);
247
+ const fullPath = safeResolvePath(PROJECT_ROOT, file.path);
234
248
 
235
249
  // Ensure parent directory exists
236
250
  await fs.mkdir(path.dirname(fullPath), { recursive: true });
@@ -266,11 +280,16 @@ const server = http.createServer(async (req, res) => {
266
280
  // Gracefully handle EADDRINUSE without crashing
267
281
  server.on('error', (err) => {
268
282
  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`);
283
+ console.log(`\n---------------------------------------------------------------`);
284
+ console.log(`Yap2App Bridge`);
285
+ console.log(`Port ${PORT} is active. Cloud Relay connected.`);
286
+ console.log(`Active Workspace: ${PROJECT_ROOT}`);
287
+ console.log(`Pairing Code: ${PAIR_CODE}`);
288
+ console.log(`Web Studio: ${CLOUD_RELAY_URL}`);
289
+ console.log(`---------------------------------------------------------------\n`);
290
+ console.log(`Bridge is running.`);
291
+ console.log(`Open Web Studio in your browser to sync components to this workspace.`);
292
+ console.log(`Press Ctrl/Cmd+C to stop.\n`);
274
293
  return;
275
294
  }
276
295
  console.error(' [bridge] Error:', err.message || err);
@@ -278,24 +297,32 @@ server.on('error', (err) => {
278
297
 
279
298
  export async function startBridge(port = PORT) {
280
299
  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`);
300
+ console.log(`\n---------------------------------------------------------------`);
301
+ console.log(`Yap2App Bridge v${CLI_VERSION}`);
302
+ console.log(`Local Endpoint: http://127.0.0.1:${port}`);
303
+ console.log(`Active Workspace: ${PROJECT_ROOT}`);
304
+ console.log(`Pairing Code: ${PAIR_CODE}`);
305
+ console.log(`Web Studio: ${CLOUD_RELAY_URL}`);
306
+ console.log(`---------------------------------------------------------------\n`);
307
+ console.log(`Bridge is running.`);
308
+ console.log(`Open Web Studio in your browser to sync components to this workspace.`);
309
+ console.log(`Press Ctrl/Cmd+C to stop.\n`);
287
310
  });
288
311
 
289
312
  // Initial scan and Cloud Relay registration
290
313
  try {
291
314
  const initialContext = await scanCodebase(PROJECT_ROOT);
315
+ if (initialContext && initialContext.total_files_count === 0) {
316
+ console.log(` ℹ️ Fresh workspace detected (0 code files).`);
317
+ console.log(` Yap2App will automatically scaffold directories and write files when synced from Web Studio.\n`);
318
+ }
292
319
  await sendCloudRelayRequest('POST', '/api/v1/bridge/heartbeat', {
293
320
  pair_code: PAIR_CODE,
294
321
  project_name: path.basename(PROJECT_ROOT),
295
322
  project_root: PROJECT_ROOT,
296
323
  framework: initialContext.framework,
297
324
  context: initialContext,
298
- version: '1.1.6'
325
+ version: CLI_VERSION
299
326
  });
300
327
  } catch (e) {}
301
328
 
@@ -309,7 +336,7 @@ export async function startBridge(port = PORT) {
309
336
  project_root: PROJECT_ROOT,
310
337
  framework: context.framework,
311
338
  context: context,
312
- version: '1.1.6'
339
+ version: CLI_VERSION
313
340
  });
314
341
 
315
342
  // If pending write tasks, fetch and execute them on local disk
@@ -319,7 +346,7 @@ export async function startBridge(port = PORT) {
319
346
  for (const task of tasks) {
320
347
  const written = [];
321
348
  for (const file of (task.files || [])) {
322
- const fullPath = path.join(PROJECT_ROOT, file.path);
349
+ const fullPath = safeResolvePath(PROJECT_ROOT, file.path);
323
350
  await fs.mkdir(path.dirname(fullPath), { recursive: true });
324
351
  await fs.writeFile(fullPath, file.content, 'utf8');
325
352
  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.8",
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 =>