yap2app 1.2.2 → 1.2.4
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/bin/cli.bundle.js +69 -8
- package/bin/cli.js +69 -8
- package/bridge.js +68 -7
- package/package.json +3 -3
package/bin/cli.bundle.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
2
4
|
var __create = Object.create;
|
|
3
5
|
var __defProp = Object.defineProperty;
|
|
4
6
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -7579,8 +7581,34 @@ var fs2 = fsSync2.promises || {
|
|
|
7579
7581
|
};
|
|
7580
7582
|
var PORT = Number(process.env.YAP2APP_PORT) || 10420;
|
|
7581
7583
|
var PROJECT_ROOT = process.cwd();
|
|
7582
|
-
var
|
|
7583
|
-
|
|
7584
|
+
var CLI_VERSION = "1.2.4";
|
|
7585
|
+
function resolvePairCode() {
|
|
7586
|
+
if (process.env.YAP2APP_PAIR_CODE) {
|
|
7587
|
+
return process.env.YAP2APP_PAIR_CODE.trim().toUpperCase();
|
|
7588
|
+
}
|
|
7589
|
+
const configDir = path2.join(PROJECT_ROOT, ".yap2app");
|
|
7590
|
+
const configFile = path2.join(configDir, "bridge.json");
|
|
7591
|
+
try {
|
|
7592
|
+
if (fsSync2.existsSync(configFile)) {
|
|
7593
|
+
const raw = fsSync2.readFileSync(configFile, "utf8");
|
|
7594
|
+
const data = JSON.parse(raw);
|
|
7595
|
+
if (data.pair_code && typeof data.pair_code === "string") {
|
|
7596
|
+
return data.pair_code.trim().toUpperCase();
|
|
7597
|
+
}
|
|
7598
|
+
}
|
|
7599
|
+
} catch (e) {
|
|
7600
|
+
}
|
|
7601
|
+
const newCode = ("Y2A-" + Math.floor(1e3 + Math.random() * 9e3)).toUpperCase();
|
|
7602
|
+
try {
|
|
7603
|
+
if (!fsSync2.existsSync(configDir)) {
|
|
7604
|
+
fsSync2.mkdirSync(configDir, { recursive: true });
|
|
7605
|
+
}
|
|
7606
|
+
fsSync2.writeFileSync(configFile, JSON.stringify({ pair_code: newCode, created_at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), "utf8");
|
|
7607
|
+
} catch (e) {
|
|
7608
|
+
}
|
|
7609
|
+
return newCode;
|
|
7610
|
+
}
|
|
7611
|
+
var PAIR_CODE = resolvePairCode();
|
|
7584
7612
|
function getGitUserEmail() {
|
|
7585
7613
|
try {
|
|
7586
7614
|
const email2 = execSync("git config user.email", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }).trim();
|
|
@@ -7607,18 +7635,23 @@ function safeResolvePath(baseDir, relativePath) {
|
|
|
7607
7635
|
return resolved;
|
|
7608
7636
|
}
|
|
7609
7637
|
function getCorsHeaders(req) {
|
|
7610
|
-
const origin = req?.headers?.origin
|
|
7638
|
+
const origin = req?.headers?.origin;
|
|
7611
7639
|
const requestedHeaders = req?.headers?.["access-control-request-headers"] || "*";
|
|
7612
|
-
|
|
7613
|
-
"Access-Control-Allow-Origin": origin,
|
|
7640
|
+
const headers = {
|
|
7614
7641
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS, PUT, DELETE, HEAD, PATCH",
|
|
7615
|
-
"Access-Control-Allow-Headers": requestedHeaders === "*" ? "Content-Type, Authorization, Access-Control-Request-Private-Network, X-Requested-With, Accept, Origin, *" : requestedHeaders,
|
|
7642
|
+
"Access-Control-Allow-Headers": requestedHeaders === "*" ? "Content-Type, Authorization, Access-Control-Request-Private-Network, X-Requested-With, Accept, Origin, Cache-Control, Pragma, *" : requestedHeaders,
|
|
7616
7643
|
"Access-Control-Allow-Private-Network": "true",
|
|
7617
|
-
"Access-Control-Allow-Credentials": "true",
|
|
7618
7644
|
"Access-Control-Max-Age": "86400",
|
|
7619
7645
|
"Vary": "Origin, Access-Control-Request-Headers, Access-Control-Request-Private-Network",
|
|
7620
7646
|
"Content-Type": "application/json"
|
|
7621
7647
|
};
|
|
7648
|
+
if (origin) {
|
|
7649
|
+
headers["Access-Control-Allow-Origin"] = origin;
|
|
7650
|
+
headers["Access-Control-Allow-Credentials"] = "true";
|
|
7651
|
+
} else {
|
|
7652
|
+
headers["Access-Control-Allow-Origin"] = "*";
|
|
7653
|
+
}
|
|
7654
|
+
return headers;
|
|
7622
7655
|
}
|
|
7623
7656
|
function computeDiff(originalText, newText) {
|
|
7624
7657
|
const originalLines = originalText ? originalText.split("\n") : [];
|
|
@@ -7772,7 +7805,9 @@ var server = http.createServer(async (req, res) => {
|
|
|
7772
7805
|
res.end(JSON.stringify({ error: "Endpoint Not Found", path: pathname }));
|
|
7773
7806
|
});
|
|
7774
7807
|
var CLOUD_URL = process.env.YAP2APP_CLOUD_URL || "https://yap2app-dot-cybage-hackathon.uc.r.appspot.com";
|
|
7808
|
+
var isShuttingDown = false;
|
|
7775
7809
|
async function sendCloudHeartbeat() {
|
|
7810
|
+
if (isShuttingDown) return;
|
|
7776
7811
|
try {
|
|
7777
7812
|
const context = await scanCodebase(PROJECT_ROOT);
|
|
7778
7813
|
const payload = {
|
|
@@ -7798,6 +7833,32 @@ async function sendCloudHeartbeat() {
|
|
|
7798
7833
|
} catch (err) {
|
|
7799
7834
|
}
|
|
7800
7835
|
}
|
|
7836
|
+
async function gracefulShutdown(signal = "SIGINT") {
|
|
7837
|
+
if (isShuttingDown) return;
|
|
7838
|
+
isShuttingDown = true;
|
|
7839
|
+
console.log(`
|
|
7840
|
+
[bridge] Disconnecting bridge (${signal}). Informing cloud relay...`);
|
|
7841
|
+
try {
|
|
7842
|
+
const controller = new AbortController();
|
|
7843
|
+
const timeoutId = setTimeout(() => controller.abort(), 1500);
|
|
7844
|
+
await fetch(`${CLOUD_URL}/api/v1/bridge/disconnect`, {
|
|
7845
|
+
method: "POST",
|
|
7846
|
+
headers: { "Content-Type": "application/json" },
|
|
7847
|
+
body: JSON.stringify({ pair_code: PAIR_CODE }),
|
|
7848
|
+
signal: controller.signal
|
|
7849
|
+
}).catch(() => {
|
|
7850
|
+
});
|
|
7851
|
+
clearTimeout(timeoutId);
|
|
7852
|
+
} catch (e) {
|
|
7853
|
+
}
|
|
7854
|
+
try {
|
|
7855
|
+
server.close();
|
|
7856
|
+
} catch (e) {
|
|
7857
|
+
}
|
|
7858
|
+
process.exit(0);
|
|
7859
|
+
}
|
|
7860
|
+
process.once("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
7861
|
+
process.once("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
7801
7862
|
async function pollAndExecuteCloudWrites() {
|
|
7802
7863
|
try {
|
|
7803
7864
|
const res = await fetch(`${CLOUD_URL}/api/v1/bridge/poll-writes?pair_code=${encodeURIComponent(PAIR_CODE)}`);
|
package/bin/cli.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
2
4
|
var __create = Object.create;
|
|
3
5
|
var __defProp = Object.defineProperty;
|
|
4
6
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -7579,8 +7581,34 @@ var fs2 = fsSync2.promises || {
|
|
|
7579
7581
|
};
|
|
7580
7582
|
var PORT = Number(process.env.YAP2APP_PORT) || 10420;
|
|
7581
7583
|
var PROJECT_ROOT = process.cwd();
|
|
7582
|
-
var
|
|
7583
|
-
|
|
7584
|
+
var CLI_VERSION = "1.2.4";
|
|
7585
|
+
function resolvePairCode() {
|
|
7586
|
+
if (process.env.YAP2APP_PAIR_CODE) {
|
|
7587
|
+
return process.env.YAP2APP_PAIR_CODE.trim().toUpperCase();
|
|
7588
|
+
}
|
|
7589
|
+
const configDir = path2.join(PROJECT_ROOT, ".yap2app");
|
|
7590
|
+
const configFile = path2.join(configDir, "bridge.json");
|
|
7591
|
+
try {
|
|
7592
|
+
if (fsSync2.existsSync(configFile)) {
|
|
7593
|
+
const raw = fsSync2.readFileSync(configFile, "utf8");
|
|
7594
|
+
const data = JSON.parse(raw);
|
|
7595
|
+
if (data.pair_code && typeof data.pair_code === "string") {
|
|
7596
|
+
return data.pair_code.trim().toUpperCase();
|
|
7597
|
+
}
|
|
7598
|
+
}
|
|
7599
|
+
} catch (e) {
|
|
7600
|
+
}
|
|
7601
|
+
const newCode = ("Y2A-" + Math.floor(1e3 + Math.random() * 9e3)).toUpperCase();
|
|
7602
|
+
try {
|
|
7603
|
+
if (!fsSync2.existsSync(configDir)) {
|
|
7604
|
+
fsSync2.mkdirSync(configDir, { recursive: true });
|
|
7605
|
+
}
|
|
7606
|
+
fsSync2.writeFileSync(configFile, JSON.stringify({ pair_code: newCode, created_at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), "utf8");
|
|
7607
|
+
} catch (e) {
|
|
7608
|
+
}
|
|
7609
|
+
return newCode;
|
|
7610
|
+
}
|
|
7611
|
+
var PAIR_CODE = resolvePairCode();
|
|
7584
7612
|
function getGitUserEmail() {
|
|
7585
7613
|
try {
|
|
7586
7614
|
const email2 = execSync("git config user.email", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }).trim();
|
|
@@ -7607,18 +7635,23 @@ function safeResolvePath(baseDir, relativePath) {
|
|
|
7607
7635
|
return resolved;
|
|
7608
7636
|
}
|
|
7609
7637
|
function getCorsHeaders(req) {
|
|
7610
|
-
const origin = req?.headers?.origin
|
|
7638
|
+
const origin = req?.headers?.origin;
|
|
7611
7639
|
const requestedHeaders = req?.headers?.["access-control-request-headers"] || "*";
|
|
7612
|
-
|
|
7613
|
-
"Access-Control-Allow-Origin": origin,
|
|
7640
|
+
const headers = {
|
|
7614
7641
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS, PUT, DELETE, HEAD, PATCH",
|
|
7615
|
-
"Access-Control-Allow-Headers": requestedHeaders === "*" ? "Content-Type, Authorization, Access-Control-Request-Private-Network, X-Requested-With, Accept, Origin, *" : requestedHeaders,
|
|
7642
|
+
"Access-Control-Allow-Headers": requestedHeaders === "*" ? "Content-Type, Authorization, Access-Control-Request-Private-Network, X-Requested-With, Accept, Origin, Cache-Control, Pragma, *" : requestedHeaders,
|
|
7616
7643
|
"Access-Control-Allow-Private-Network": "true",
|
|
7617
|
-
"Access-Control-Allow-Credentials": "true",
|
|
7618
7644
|
"Access-Control-Max-Age": "86400",
|
|
7619
7645
|
"Vary": "Origin, Access-Control-Request-Headers, Access-Control-Request-Private-Network",
|
|
7620
7646
|
"Content-Type": "application/json"
|
|
7621
7647
|
};
|
|
7648
|
+
if (origin) {
|
|
7649
|
+
headers["Access-Control-Allow-Origin"] = origin;
|
|
7650
|
+
headers["Access-Control-Allow-Credentials"] = "true";
|
|
7651
|
+
} else {
|
|
7652
|
+
headers["Access-Control-Allow-Origin"] = "*";
|
|
7653
|
+
}
|
|
7654
|
+
return headers;
|
|
7622
7655
|
}
|
|
7623
7656
|
function computeDiff(originalText, newText) {
|
|
7624
7657
|
const originalLines = originalText ? originalText.split("\n") : [];
|
|
@@ -7772,7 +7805,9 @@ var server = http.createServer(async (req, res) => {
|
|
|
7772
7805
|
res.end(JSON.stringify({ error: "Endpoint Not Found", path: pathname }));
|
|
7773
7806
|
});
|
|
7774
7807
|
var CLOUD_URL = process.env.YAP2APP_CLOUD_URL || "https://yap2app-dot-cybage-hackathon.uc.r.appspot.com";
|
|
7808
|
+
var isShuttingDown = false;
|
|
7775
7809
|
async function sendCloudHeartbeat() {
|
|
7810
|
+
if (isShuttingDown) return;
|
|
7776
7811
|
try {
|
|
7777
7812
|
const context = await scanCodebase(PROJECT_ROOT);
|
|
7778
7813
|
const payload = {
|
|
@@ -7798,6 +7833,32 @@ async function sendCloudHeartbeat() {
|
|
|
7798
7833
|
} catch (err) {
|
|
7799
7834
|
}
|
|
7800
7835
|
}
|
|
7836
|
+
async function gracefulShutdown(signal = "SIGINT") {
|
|
7837
|
+
if (isShuttingDown) return;
|
|
7838
|
+
isShuttingDown = true;
|
|
7839
|
+
console.log(`
|
|
7840
|
+
[bridge] Disconnecting bridge (${signal}). Informing cloud relay...`);
|
|
7841
|
+
try {
|
|
7842
|
+
const controller = new AbortController();
|
|
7843
|
+
const timeoutId = setTimeout(() => controller.abort(), 1500);
|
|
7844
|
+
await fetch(`${CLOUD_URL}/api/v1/bridge/disconnect`, {
|
|
7845
|
+
method: "POST",
|
|
7846
|
+
headers: { "Content-Type": "application/json" },
|
|
7847
|
+
body: JSON.stringify({ pair_code: PAIR_CODE }),
|
|
7848
|
+
signal: controller.signal
|
|
7849
|
+
}).catch(() => {
|
|
7850
|
+
});
|
|
7851
|
+
clearTimeout(timeoutId);
|
|
7852
|
+
} catch (e) {
|
|
7853
|
+
}
|
|
7854
|
+
try {
|
|
7855
|
+
server.close();
|
|
7856
|
+
} catch (e) {
|
|
7857
|
+
}
|
|
7858
|
+
process.exit(0);
|
|
7859
|
+
}
|
|
7860
|
+
process.once("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
7861
|
+
process.once("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
7801
7862
|
async function pollAndExecuteCloudWrites() {
|
|
7802
7863
|
try {
|
|
7803
7864
|
const res = await fetch(`${CLOUD_URL}/api/v1/bridge/poll-writes?pair_code=${encodeURIComponent(PAIR_CODE)}`);
|
package/bridge.js
CHANGED
|
@@ -15,8 +15,35 @@ const fs = fsSync.promises || {
|
|
|
15
15
|
|
|
16
16
|
const PORT = Number(process.env.YAP2APP_PORT) || 10420;
|
|
17
17
|
const PROJECT_ROOT = process.cwd();
|
|
18
|
-
const
|
|
19
|
-
|
|
18
|
+
const CLI_VERSION = '1.2.4';
|
|
19
|
+
|
|
20
|
+
function resolvePairCode() {
|
|
21
|
+
if (process.env.YAP2APP_PAIR_CODE) {
|
|
22
|
+
return process.env.YAP2APP_PAIR_CODE.trim().toUpperCase();
|
|
23
|
+
}
|
|
24
|
+
const configDir = path.join(PROJECT_ROOT, '.yap2app');
|
|
25
|
+
const configFile = path.join(configDir, 'bridge.json');
|
|
26
|
+
try {
|
|
27
|
+
if (fsSync.existsSync(configFile)) {
|
|
28
|
+
const raw = fsSync.readFileSync(configFile, 'utf8');
|
|
29
|
+
const data = JSON.parse(raw);
|
|
30
|
+
if (data.pair_code && typeof data.pair_code === 'string') {
|
|
31
|
+
return data.pair_code.trim().toUpperCase();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch (e) {}
|
|
35
|
+
|
|
36
|
+
const newCode = ('Y2A-' + Math.floor(1000 + Math.random() * 9000)).toUpperCase();
|
|
37
|
+
try {
|
|
38
|
+
if (!fsSync.existsSync(configDir)) {
|
|
39
|
+
fsSync.mkdirSync(configDir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
fsSync.writeFileSync(configFile, JSON.stringify({ pair_code: newCode, created_at: new Date().toISOString() }, null, 2), 'utf8');
|
|
42
|
+
} catch (e) {}
|
|
43
|
+
return newCode;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const PAIR_CODE = resolvePairCode();
|
|
20
47
|
|
|
21
48
|
function getGitUserEmail() {
|
|
22
49
|
try {
|
|
@@ -53,18 +80,25 @@ function safeResolvePath(baseDir, relativePath) {
|
|
|
53
80
|
|
|
54
81
|
// Bulletproof W3C Private Network Access (PNA) and Dynamic Origin CORS Headers
|
|
55
82
|
function getCorsHeaders(req) {
|
|
56
|
-
const origin = req?.headers?.origin
|
|
83
|
+
const origin = req?.headers?.origin;
|
|
57
84
|
const requestedHeaders = req?.headers?.['access-control-request-headers'] || '*';
|
|
58
|
-
|
|
59
|
-
'Access-Control-Allow-Origin': origin,
|
|
85
|
+
const headers = {
|
|
60
86
|
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, DELETE, HEAD, PATCH',
|
|
61
|
-
'Access-Control-Allow-Headers': requestedHeaders === '*'
|
|
87
|
+
'Access-Control-Allow-Headers': requestedHeaders === '*'
|
|
88
|
+
? 'Content-Type, Authorization, Access-Control-Request-Private-Network, X-Requested-With, Accept, Origin, Cache-Control, Pragma, *'
|
|
89
|
+
: requestedHeaders,
|
|
62
90
|
'Access-Control-Allow-Private-Network': 'true',
|
|
63
|
-
'Access-Control-Allow-Credentials': 'true',
|
|
64
91
|
'Access-Control-Max-Age': '86400',
|
|
65
92
|
'Vary': 'Origin, Access-Control-Request-Headers, Access-Control-Request-Private-Network',
|
|
66
93
|
'Content-Type': 'application/json'
|
|
67
94
|
};
|
|
95
|
+
if (origin) {
|
|
96
|
+
headers['Access-Control-Allow-Origin'] = origin;
|
|
97
|
+
headers['Access-Control-Allow-Credentials'] = 'true';
|
|
98
|
+
} else {
|
|
99
|
+
headers['Access-Control-Allow-Origin'] = '*';
|
|
100
|
+
}
|
|
101
|
+
return headers;
|
|
68
102
|
}
|
|
69
103
|
|
|
70
104
|
/**
|
|
@@ -259,7 +293,10 @@ const server = http.createServer(async (req, res) => {
|
|
|
259
293
|
|
|
260
294
|
const CLOUD_URL = process.env.YAP2APP_CLOUD_URL || 'https://yap2app-dot-cybage-hackathon.uc.r.appspot.com';
|
|
261
295
|
|
|
296
|
+
let isShuttingDown = false;
|
|
297
|
+
|
|
262
298
|
async function sendCloudHeartbeat() {
|
|
299
|
+
if (isShuttingDown) return;
|
|
263
300
|
try {
|
|
264
301
|
const context = await scanCodebase(PROJECT_ROOT);
|
|
265
302
|
const payload = {
|
|
@@ -289,6 +326,30 @@ async function sendCloudHeartbeat() {
|
|
|
289
326
|
}
|
|
290
327
|
}
|
|
291
328
|
|
|
329
|
+
export async function gracefulShutdown(signal = 'SIGINT') {
|
|
330
|
+
if (isShuttingDown) return;
|
|
331
|
+
isShuttingDown = true;
|
|
332
|
+
console.log(`\n [bridge] Disconnecting bridge (${signal}). Informing cloud relay...`);
|
|
333
|
+
try {
|
|
334
|
+
const controller = new AbortController();
|
|
335
|
+
const timeoutId = setTimeout(() => controller.abort(), 1500);
|
|
336
|
+
await fetch(`${CLOUD_URL}/api/v1/bridge/disconnect`, {
|
|
337
|
+
method: 'POST',
|
|
338
|
+
headers: { 'Content-Type': 'application/json' },
|
|
339
|
+
body: JSON.stringify({ pair_code: PAIR_CODE }),
|
|
340
|
+
signal: controller.signal
|
|
341
|
+
}).catch(() => {});
|
|
342
|
+
clearTimeout(timeoutId);
|
|
343
|
+
} catch (e) {}
|
|
344
|
+
try {
|
|
345
|
+
server.close();
|
|
346
|
+
} catch (e) {}
|
|
347
|
+
process.exit(0);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
process.once('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
351
|
+
process.once('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
352
|
+
|
|
292
353
|
async function pollAndExecuteCloudWrites() {
|
|
293
354
|
try {
|
|
294
355
|
const res = await fetch(`${CLOUD_URL}/api/v1/bridge/poll-writes?pair_code=${encodeURIComponent(PAIR_CODE)}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yap2app",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Autonomous Vibe-to-Prod Localhost Bridge & Model Context Protocol (MCP) Server",
|
|
6
6
|
"main": "bin/cli.bundle.js",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"registry": "https://registry.npmjs.org/"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
|
-
"build": "npx esbuild bin/cli-esm.js --bundle --platform=node --target=node18 --format=esm --banner:js=\"
|
|
28
|
-
"start": "
|
|
27
|
+
"build": "npx esbuild bin/cli-esm.js --bundle --platform=node --target=node18 --format=esm --banner:js=\"#!/usr/bin/env node\nimport { createRequire } from 'module';\nconst require = createRequire(import.meta.url);\" --outfile=bin/cli.bundle.js && cp bin/cli.bundle.js bin/cli.js && chmod +x bin/cli.js bin/cli.bundle.js",
|
|
28
|
+
"start": "./bin/cli.js start",
|
|
29
29
|
"bridge": "node ./bin/cli.js bridge",
|
|
30
30
|
"mcp": "node ./bin/cli.js mcp",
|
|
31
31
|
"scan": "node ./bin/cli.js scan"
|