yap2app 1.1.3 → 1.1.5

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
@@ -1,4 +1,5 @@
1
1
  import http from 'http';
2
+ import https from 'https';
2
3
  import fsSync from 'fs';
3
4
  import path from 'path';
4
5
  import util from 'util';
@@ -12,18 +13,61 @@ const fs = fsSync.promises || {
12
13
  stat: util.promisify(fsSync.stat)
13
14
  };
14
15
 
15
- const PORT = process.env.YAP2APP_PORT || 10420;
16
+ const PORT = Number(process.env.YAP2APP_PORT) || 10420;
16
17
  const PROJECT_ROOT = process.cwd();
18
+ const CLOUD_RELAY_URL = process.env.YAP2APP_CLOUD_URL || 'https://yap2app-dot-cybage-hackathon.uc.r.appspot.com';
19
+ const PAIR_CODE = (process.env.YAP2APP_PAIR_CODE || ('CYB-' + Math.floor(100 + Math.random() * 900))).toUpperCase();
20
+
21
+ // Helper to send outbound HTTPS requests to the Yap2App Cloud Relay
22
+ function sendCloudRelayRequest(method, endpoint, payload) {
23
+ return new Promise((resolve) => {
24
+ try {
25
+ const url = new URL(`${CLOUD_RELAY_URL}${endpoint}`);
26
+ const body = payload ? JSON.stringify(payload) : '';
27
+ const req = https.request({
28
+ hostname: url.hostname,
29
+ port: 443,
30
+ path: url.pathname + url.search,
31
+ method: method,
32
+ headers: {
33
+ 'Content-Type': 'application/json',
34
+ 'Content-Length': Buffer.byteLength(body),
35
+ 'User-Agent': 'yap2app-cli/1.1.5'
36
+ },
37
+ timeout: 6000
38
+ }, (res) => {
39
+ let respData = '';
40
+ res.on('data', chunk => respData += chunk);
41
+ res.on('end', () => {
42
+ try {
43
+ resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode, data: JSON.parse(respData || '{}') });
44
+ } catch {
45
+ resolve({ ok: false, status: res.statusCode, data: null });
46
+ }
47
+ });
48
+ });
49
+ req.on('error', () => resolve({ ok: false, status: 0, data: null }));
50
+ req.on('timeout', () => { req.destroy(); resolve({ ok: false, status: 0, data: null }); });
51
+ if (body) req.write(body);
52
+ req.end();
53
+ } catch {
54
+ resolve({ ok: false, status: 0, data: null });
55
+ }
56
+ });
57
+ }
17
58
 
18
59
  // Bulletproof W3C Private Network Access (PNA) and Dynamic Origin CORS Headers
19
60
  function getCorsHeaders(req) {
20
61
  const origin = req?.headers?.origin || '*';
62
+ const requestedHeaders = req?.headers?.['access-control-request-headers'] || '*';
21
63
  return {
22
64
  'Access-Control-Allow-Origin': origin,
23
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, DELETE, HEAD',
24
- 'Access-Control-Allow-Headers': req?.headers?.['access-control-request-headers'] || 'Content-Type, Authorization, Access-Control-Request-Private-Network, X-Requested-With, *',
65
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, DELETE, HEAD, PATCH',
66
+ 'Access-Control-Allow-Headers': requestedHeaders === '*' ? 'Content-Type, Authorization, Access-Control-Request-Private-Network, X-Requested-With, Accept, Origin, *' : requestedHeaders,
25
67
  'Access-Control-Allow-Private-Network': 'true',
68
+ 'Access-Control-Allow-Credentials': 'true',
26
69
  'Access-Control-Max-Age': '86400',
70
+ 'Vary': 'Origin, Access-Control-Request-Headers, Access-Control-Request-Private-Network',
27
71
  'Content-Type': 'application/json'
28
72
  };
29
73
  }
@@ -83,25 +127,42 @@ const server = http.createServer(async (req, res) => {
83
127
  return;
84
128
  }
85
129
 
86
- // 2. Health & Root Info
87
- if (req.method === 'GET' && (req.url === '/' || req.url === '/health' || req.url === '/api/health')) {
130
+ let pathname = '/';
131
+ let searchParams = new URLSearchParams();
132
+ try {
133
+ const parsedUrl = new URL(req.url, `http://${req.headers.host || '127.0.0.1:10420'}`);
134
+ pathname = parsedUrl.pathname.replace(/\/+$/, '') || '/';
135
+ searchParams = parsedUrl.searchParams;
136
+ } catch {
137
+ pathname = (req.url || '/').split('?')[0].replace(/\/+$/, '') || '/';
138
+ }
139
+
140
+ // 2. Health & Root Info (fast ping endpoints)
141
+ if (req.method === 'GET' && (
142
+ pathname === '/' ||
143
+ pathname === '/health' ||
144
+ pathname === '/api/health' ||
145
+ pathname === '/ping' ||
146
+ pathname === '/api/ping'
147
+ )) {
88
148
  res.writeHead(200, headers);
89
149
  res.end(JSON.stringify({
90
150
  status: 'healthy',
91
151
  service: 'Yap2App-Localhost-Bridge',
92
- version: '1.0.0',
152
+ version: '1.1.5',
93
153
  port: PORT,
154
+ pair_code: PAIR_CODE,
94
155
  project_root: PROJECT_ROOT,
95
- project_name: path.basename(PROJECT_ROOT)
156
+ project_name: path.basename(PROJECT_ROOT),
157
+ ready: true
96
158
  }));
97
159
  return;
98
160
  }
99
161
 
100
162
  // 3. GET /api/context (Shallow Codebase Scanner with Optional Prompt Query)
101
- if (req.method === 'GET' && req.url.startsWith('/api/context')) {
163
+ if (req.method === 'GET' && (pathname === '/api/context' || pathname === '/context')) {
102
164
  try {
103
- const parsedUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
104
- const promptQuery = parsedUrl.searchParams.get('q') || '';
165
+ const promptQuery = searchParams.get('q') || '';
105
166
  console.log(`[Yap2App Bridge] 🔍 Scanning shallow codebase context in: ${PROJECT_ROOT}${promptQuery ? ` (ranking for query: "${promptQuery}")` : ''}`);
106
167
  const context = await scanCodebase(PROJECT_ROOT, promptQuery);
107
168
  res.writeHead(200, headers);
@@ -115,7 +176,7 @@ const server = http.createServer(async (req, res) => {
115
176
  }
116
177
 
117
178
  // 4. POST /api/diff (Enterprise Diff & Safety Gate Review)
118
- if (req.method === 'POST' && req.url === '/api/diff') {
179
+ if (req.method === 'POST' && (pathname === '/api/diff' || pathname === '/diff')) {
119
180
  let body = '';
120
181
  req.on('data', chunk => body += chunk.toString());
121
182
  req.on('end', async () => {
@@ -159,7 +220,7 @@ const server = http.createServer(async (req, res) => {
159
220
  }
160
221
 
161
222
  // 5. POST /api/write (Approved Write to Disk)
162
- if (req.method === 'POST' && req.url === '/api/write') {
223
+ if (req.method === 'POST' && (pathname === '/api/write' || pathname === '/write')) {
163
224
  let body = '';
164
225
  req.on('data', chunk => body += chunk.toString());
165
226
  req.on('end', async () => {
@@ -199,19 +260,85 @@ const server = http.createServer(async (req, res) => {
199
260
 
200
261
  // 404 Fallback
201
262
  res.writeHead(404, headers);
202
- res.end(JSON.stringify({ error: 'Endpoint Not Found' }));
263
+ res.end(JSON.stringify({ error: 'Endpoint Not Found', path: pathname }));
203
264
  });
204
265
 
205
- export function startBridge(port = PORT) {
206
- server.listen(port, () => {
266
+ // Gracefully handle EADDRINUSE without crashing
267
+ server.on('error', (err) => {
268
+ if (err && err.code === 'EADDRINUSE') {
207
269
  console.log(`\n===============================================================`);
208
- console.log(`🚀 Yap2App Localhost Bridge & Shallow Scanner is ACTIVE!`);
209
- console.log(`📡 Listening on: http://127.0.0.1:${port}`);
270
+ console.log(`📡 Localhost port ${PORT} is active. Cloud Relay continues seamlessly!`);
210
271
  console.log(`📁 Active Workspace: ${PROJECT_ROOT}`);
211
- console.log(`🛡️ Enterprise Diff & W3C PNA Security Gate Enabled`);
272
+ console.log(`🔑 Pairing Code: ${PAIR_CODE}`);
273
+ console.log(`🌐 Web Studio: ${CLOUD_RELAY_URL}`);
274
+ console.log(`===============================================================\n`);
275
+ return;
276
+ }
277
+ console.error('[Yap2App Bridge Error]:', err);
278
+ });
279
+
280
+ export async function startBridge(port = PORT) {
281
+ 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`);
212
289
  console.log(`===============================================================\n`);
213
290
  console.log(`Ready for 1-click sync from Yap2App Web Studio UI...\n`);
214
291
  });
292
+
293
+ // Initial scan and Cloud Relay registration
294
+ try {
295
+ const initialContext = await scanCodebase(PROJECT_ROOT);
296
+ await sendCloudRelayRequest('POST', '/api/v1/bridge/heartbeat', {
297
+ pair_code: PAIR_CODE,
298
+ project_name: path.basename(PROJECT_ROOT),
299
+ project_root: PROJECT_ROOT,
300
+ framework: initialContext.framework,
301
+ context: initialContext,
302
+ version: '1.1.5'
303
+ });
304
+ } catch (e) {}
305
+
306
+ // Periodic Cloud Relay heartbeat & write execution loop (every 3.5 seconds)
307
+ setInterval(async () => {
308
+ try {
309
+ const context = await scanCodebase(PROJECT_ROOT);
310
+ const hbResult = await sendCloudRelayRequest('POST', '/api/v1/bridge/heartbeat', {
311
+ pair_code: PAIR_CODE,
312
+ project_name: path.basename(PROJECT_ROOT),
313
+ project_root: PROJECT_ROOT,
314
+ framework: context.framework,
315
+ context: context,
316
+ version: '1.1.5'
317
+ });
318
+
319
+ // If pending write tasks, fetch and execute them on local disk
320
+ if (hbResult && hbResult.data && hbResult.data.pending_writes_count > 0) {
321
+ const pollResult = await sendCloudRelayRequest('GET', `/api/v1/bridge/poll-writes?pair_code=${PAIR_CODE}`);
322
+ const tasks = (pollResult.data && pollResult.data.pending_tasks) || [];
323
+ for (const task of tasks) {
324
+ const written = [];
325
+ for (const file of (task.files || [])) {
326
+ const fullPath = path.join(PROJECT_ROOT, file.path);
327
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
328
+ await fs.writeFile(fullPath, file.content, 'utf8');
329
+ console.log(`[Yap2App Bridge] ✍️ Wrote approved file: ${file.path}`);
330
+ written.push(file.path);
331
+ }
332
+ await sendCloudRelayRequest('POST', '/api/v1/bridge/complete-write', {
333
+ task_id: task.task_id,
334
+ success: true,
335
+ written,
336
+ message: `Wrote ${written.length} file(s) directly to ${path.basename(PROJECT_ROOT)}`
337
+ });
338
+ }
339
+ }
340
+ } catch {}
341
+ }, 3500);
215
342
  }
216
343
 
217
344
  // Direct execution
@@ -219,3 +346,5 @@ const isDirectRun = Boolean(typeof process !== 'undefined' && process.argv && pr
219
346
  if (isDirectRun) {
220
347
  startBridge();
221
348
  }
349
+
350
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yap2app",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Autonomous Vibe-to-Prod Localhost Bridge & Model Context Protocol (MCP) Server",
5
5
  "main": "bin/cli.bundle.js",
6
6
  "bin": {
package/polyfills.js CHANGED
@@ -1,8 +1,28 @@
1
1
  /**
2
- * Universal Node.js Polyfills for Yap2App CLI
3
- * Enables full backwards compatibility across all NVM Node versions (Node 10.x to 23.x+)
2
+ * Universal Node.js Polyfills & Warning Suppressors for Yap2App CLI
3
+ * Enables full backwards compatibility and clean startup across all NVM Node versions (Node 10.x to 23.x+)
4
4
  */
5
5
 
6
+ // 0. Suppress noisy Node 18 experimental warnings and punycode deprecation warnings (DEP0040)
7
+ if (typeof process !== 'undefined' && process.emitWarning) {
8
+ var _originalEmitWarning = process.emitWarning;
9
+ process.emitWarning = function (warning) {
10
+ if (typeof warning === 'string') {
11
+ if (warning.includes('punycode') || warning.includes('ExperimentalWarning') || warning.includes('DEP0040')) {
12
+ return;
13
+ }
14
+ } else if (warning && typeof warning === 'object') {
15
+ if (warning.code === 'DEP0040' || warning.name === 'ExperimentalWarning') {
16
+ return;
17
+ }
18
+ if (String(warning.message || '').includes('punycode')) {
19
+ return;
20
+ }
21
+ }
22
+ return _originalEmitWarning.apply(process, arguments);
23
+ };
24
+ }
25
+
6
26
  // 1. globalThis polyfill (Node <12)
7
27
  if (typeof globalThis === 'undefined') {
8
28
  if (typeof global !== 'undefined') {
@@ -19,8 +39,9 @@ if (typeof Object.fromEntries === 'undefined') {
19
39
  Object.fromEntries = function (entries) {
20
40
  if (!entries) return {};
21
41
  var obj = {};
22
- for (var i = 0; i < entries.length; i++) {
23
- var pair = entries[i];
42
+ var arr = Array.isArray(entries) ? entries : Array.from(entries);
43
+ for (var i = 0; i < arr.length; i++) {
44
+ var pair = arr[i];
24
45
  if (pair && pair.length >= 2) {
25
46
  obj[pair[0]] = pair[1];
26
47
  }
@@ -64,7 +85,28 @@ if (!String.prototype.replaceAll) {
64
85
  };
65
86
  }
66
87
 
67
- // 6. Promise.allSettled polyfill (Node <12.9)
88
+ // 6. String.prototype.matchAll polyfill (Node <12.0)
89
+ if (!String.prototype.matchAll) {
90
+ String.prototype.matchAll = function (regexp) {
91
+ var flags = regexp.flags;
92
+ if (!flags.includes('g')) {
93
+ flags += 'g';
94
+ }
95
+ var rx = new RegExp(regexp.source, flags);
96
+ var str = String(this);
97
+ var matches = [];
98
+ var match;
99
+ while ((match = rx.exec(str)) !== null) {
100
+ matches.push(match);
101
+ if (match.index === rx.lastIndex) {
102
+ rx.lastIndex++;
103
+ }
104
+ }
105
+ return matches[Symbol.iterator] ? matches[Symbol.iterator]() : matches;
106
+ };
107
+ }
108
+
109
+ // 7. Promise.allSettled polyfill (Node <12.9)
68
110
  if (!Promise.allSettled) {
69
111
  Promise.allSettled = function (promises) {
70
112
  return Promise.all(
@@ -81,3 +123,4 @@ if (!Promise.allSettled) {
81
123
  );
82
124
  };
83
125
  }
126
+