@shipsafe/cli 0.1.0
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/README.md +167 -0
- package/dist/bin/shipsafe.d.ts +3 -0
- package/dist/bin/shipsafe.d.ts.map +1 -0
- package/dist/bin/shipsafe.js +33 -0
- package/dist/bin/shipsafe.js.map +1 -0
- package/dist/src/autofix/pr-generator.d.ts +48 -0
- package/dist/src/autofix/pr-generator.d.ts.map +1 -0
- package/dist/src/autofix/pr-generator.js +359 -0
- package/dist/src/autofix/pr-generator.js.map +1 -0
- package/dist/src/autofix/scaffolding.d.ts +26 -0
- package/dist/src/autofix/scaffolding.d.ts.map +1 -0
- package/dist/src/autofix/scaffolding.js +249 -0
- package/dist/src/autofix/scaffolding.js.map +1 -0
- package/dist/src/autofix/secret-fixer.d.ts +27 -0
- package/dist/src/autofix/secret-fixer.d.ts.map +1 -0
- package/dist/src/autofix/secret-fixer.js +138 -0
- package/dist/src/autofix/secret-fixer.js.map +1 -0
- package/dist/src/claude-md/manager.d.ts +17 -0
- package/dist/src/claude-md/manager.d.ts.map +1 -0
- package/dist/src/claude-md/manager.js +143 -0
- package/dist/src/claude-md/manager.js.map +1 -0
- package/dist/src/cli/activate.d.ts +4 -0
- package/dist/src/cli/activate.d.ts.map +1 -0
- package/dist/src/cli/activate.js +53 -0
- package/dist/src/cli/activate.js.map +1 -0
- package/dist/src/cli/config.d.ts +21 -0
- package/dist/src/cli/config.d.ts.map +1 -0
- package/dist/src/cli/config.js +128 -0
- package/dist/src/cli/config.js.map +1 -0
- package/dist/src/cli/connect.d.ts +36 -0
- package/dist/src/cli/connect.d.ts.map +1 -0
- package/dist/src/cli/connect.js +107 -0
- package/dist/src/cli/connect.js.map +1 -0
- package/dist/src/cli/init.d.ts +12 -0
- package/dist/src/cli/init.d.ts.map +1 -0
- package/dist/src/cli/init.js +45 -0
- package/dist/src/cli/init.js.map +1 -0
- package/dist/src/cli/license-check.d.ts +7 -0
- package/dist/src/cli/license-check.d.ts.map +1 -0
- package/dist/src/cli/license-check.js +69 -0
- package/dist/src/cli/license-check.js.map +1 -0
- package/dist/src/cli/license-gate.d.ts +9 -0
- package/dist/src/cli/license-gate.d.ts.map +1 -0
- package/dist/src/cli/license-gate.js +25 -0
- package/dist/src/cli/license-gate.js.map +1 -0
- package/dist/src/cli/scan.d.ts +9 -0
- package/dist/src/cli/scan.d.ts.map +1 -0
- package/dist/src/cli/scan.js +75 -0
- package/dist/src/cli/scan.js.map +1 -0
- package/dist/src/cli/setup.d.ts +27 -0
- package/dist/src/cli/setup.d.ts.map +1 -0
- package/dist/src/cli/setup.js +134 -0
- package/dist/src/cli/setup.js.map +1 -0
- package/dist/src/cli/status.d.ts +4 -0
- package/dist/src/cli/status.d.ts.map +1 -0
- package/dist/src/cli/status.js +52 -0
- package/dist/src/cli/status.js.map +1 -0
- package/dist/src/cli/upload-sourcemaps.d.ts +13 -0
- package/dist/src/cli/upload-sourcemaps.d.ts.map +1 -0
- package/dist/src/cli/upload-sourcemaps.js +157 -0
- package/dist/src/cli/upload-sourcemaps.js.map +1 -0
- package/dist/src/config/manager.d.ts +37 -0
- package/dist/src/config/manager.d.ts.map +1 -0
- package/dist/src/config/manager.js +131 -0
- package/dist/src/config/manager.js.map +1 -0
- package/dist/src/constants.d.ts +28 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +34 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/engines/graph/data-flow.d.ts +36 -0
- package/dist/src/engines/graph/data-flow.d.ts.map +1 -0
- package/dist/src/engines/graph/data-flow.js +189 -0
- package/dist/src/engines/graph/data-flow.js.map +1 -0
- package/dist/src/engines/graph/index.d.ts +20 -0
- package/dist/src/engines/graph/index.d.ts.map +1 -0
- package/dist/src/engines/graph/index.js +100 -0
- package/dist/src/engines/graph/index.js.map +1 -0
- package/dist/src/engines/graph/parser.d.ts +13 -0
- package/dist/src/engines/graph/parser.d.ts.map +1 -0
- package/dist/src/engines/graph/parser.js +620 -0
- package/dist/src/engines/graph/parser.js.map +1 -0
- package/dist/src/engines/graph/queries.d.ts +11 -0
- package/dist/src/engines/graph/queries.d.ts.map +1 -0
- package/dist/src/engines/graph/queries.js +196 -0
- package/dist/src/engines/graph/queries.js.map +1 -0
- package/dist/src/engines/graph/store.d.ts +35 -0
- package/dist/src/engines/graph/store.d.ts.map +1 -0
- package/dist/src/engines/graph/store.js +284 -0
- package/dist/src/engines/graph/store.js.map +1 -0
- package/dist/src/engines/pattern/gitleaks.d.ts +4 -0
- package/dist/src/engines/pattern/gitleaks.d.ts.map +1 -0
- package/dist/src/engines/pattern/gitleaks.js +78 -0
- package/dist/src/engines/pattern/gitleaks.js.map +1 -0
- package/dist/src/engines/pattern/index.d.ts +11 -0
- package/dist/src/engines/pattern/index.d.ts.map +1 -0
- package/dist/src/engines/pattern/index.js +111 -0
- package/dist/src/engines/pattern/index.js.map +1 -0
- package/dist/src/engines/pattern/semgrep.d.ts +4 -0
- package/dist/src/engines/pattern/semgrep.d.ts.map +1 -0
- package/dist/src/engines/pattern/semgrep.js +83 -0
- package/dist/src/engines/pattern/semgrep.js.map +1 -0
- package/dist/src/engines/pattern/trivy.d.ts +4 -0
- package/dist/src/engines/pattern/trivy.d.ts.map +1 -0
- package/dist/src/engines/pattern/trivy.js +90 -0
- package/dist/src/engines/pattern/trivy.js.map +1 -0
- package/dist/src/github/api.d.ts +19 -0
- package/dist/src/github/api.d.ts.map +1 -0
- package/dist/src/github/api.js +75 -0
- package/dist/src/github/api.js.map +1 -0
- package/dist/src/github/app-manifest.d.ts +28 -0
- package/dist/src/github/app-manifest.d.ts.map +1 -0
- package/dist/src/github/app-manifest.js +27 -0
- package/dist/src/github/app-manifest.js.map +1 -0
- package/dist/src/github/checks.d.ts +36 -0
- package/dist/src/github/checks.d.ts.map +1 -0
- package/dist/src/github/checks.js +90 -0
- package/dist/src/github/checks.js.map +1 -0
- package/dist/src/github/scanner.d.ts +20 -0
- package/dist/src/github/scanner.d.ts.map +1 -0
- package/dist/src/github/scanner.js +78 -0
- package/dist/src/github/scanner.js.map +1 -0
- package/dist/src/github/webhook.d.ts +39 -0
- package/dist/src/github/webhook.d.ts.map +1 -0
- package/dist/src/github/webhook.js +80 -0
- package/dist/src/github/webhook.js.map +1 -0
- package/dist/src/hooks/installer.d.ts +4 -0
- package/dist/src/hooks/installer.d.ts.map +1 -0
- package/dist/src/hooks/installer.js +146 -0
- package/dist/src/hooks/installer.js.map +1 -0
- package/dist/src/mcp/server.d.ts +2 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +96 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/mcp/tools/check-package.d.ts +30 -0
- package/dist/src/mcp/tools/check-package.d.ts.map +1 -0
- package/dist/src/mcp/tools/check-package.js +196 -0
- package/dist/src/mcp/tools/check-package.js.map +1 -0
- package/dist/src/mcp/tools/fix.d.ts +41 -0
- package/dist/src/mcp/tools/fix.d.ts.map +1 -0
- package/dist/src/mcp/tools/fix.js +98 -0
- package/dist/src/mcp/tools/fix.js.map +1 -0
- package/dist/src/mcp/tools/graph-query.d.ts +7 -0
- package/dist/src/mcp/tools/graph-query.d.ts.map +1 -0
- package/dist/src/mcp/tools/graph-query.js +139 -0
- package/dist/src/mcp/tools/graph-query.js.map +1 -0
- package/dist/src/mcp/tools/production-errors.d.ts +23 -0
- package/dist/src/mcp/tools/production-errors.d.ts.map +1 -0
- package/dist/src/mcp/tools/production-errors.js +46 -0
- package/dist/src/mcp/tools/production-errors.js.map +1 -0
- package/dist/src/mcp/tools/scan.d.ts +7 -0
- package/dist/src/mcp/tools/scan.d.ts.map +1 -0
- package/dist/src/mcp/tools/scan.js +9 -0
- package/dist/src/mcp/tools/scan.js.map +1 -0
- package/dist/src/mcp/tools/status.d.ts +9 -0
- package/dist/src/mcp/tools/status.d.ts.map +1 -0
- package/dist/src/mcp/tools/status.js +18 -0
- package/dist/src/mcp/tools/status.js.map +1 -0
- package/dist/src/mcp/tools/verify-resolution.d.ts +12 -0
- package/dist/src/mcp/tools/verify-resolution.d.ts.map +1 -0
- package/dist/src/mcp/tools/verify-resolution.js +45 -0
- package/dist/src/mcp/tools/verify-resolution.js.map +1 -0
- package/dist/src/types.d.ts +136 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ScanResult } from '../types.js';
|
|
2
|
+
export interface PrScanOptions {
|
|
3
|
+
repoFullName: string;
|
|
4
|
+
prNumber: number;
|
|
5
|
+
headSha: string;
|
|
6
|
+
baseSha: string;
|
|
7
|
+
installationId: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Get changed files in a PR and scan them.
|
|
11
|
+
*
|
|
12
|
+
* 1. Fetches list of changed files via GitHub API
|
|
13
|
+
* 2. Downloads content of each changed file
|
|
14
|
+
* 3. Writes them to a temp directory
|
|
15
|
+
* 4. Runs pattern engine scan
|
|
16
|
+
* 5. Cleans up temp directory
|
|
17
|
+
* 6. Returns scan result
|
|
18
|
+
*/
|
|
19
|
+
export declare function scanPullRequest(options: PrScanOptions): Promise<ScanResult>;
|
|
20
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../../src/github/scanner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAI9C,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AASD;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,CAkDjF"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { getInstallationToken, githubApi } from './api.js';
|
|
5
|
+
import { runPatternEngine } from '../engines/pattern/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Get changed files in a PR and scan them.
|
|
8
|
+
*
|
|
9
|
+
* 1. Fetches list of changed files via GitHub API
|
|
10
|
+
* 2. Downloads content of each changed file
|
|
11
|
+
* 3. Writes them to a temp directory
|
|
12
|
+
* 4. Runs pattern engine scan
|
|
13
|
+
* 5. Cleans up temp directory
|
|
14
|
+
* 6. Returns scan result
|
|
15
|
+
*/
|
|
16
|
+
export async function scanPullRequest(options) {
|
|
17
|
+
const token = await getInstallationToken(options.installationId);
|
|
18
|
+
// 1. Fetch changed files
|
|
19
|
+
const files = await fetchChangedFiles(options.repoFullName, options.prNumber, token);
|
|
20
|
+
// Filter out deleted files — nothing to scan
|
|
21
|
+
const filesToScan = files.filter((f) => f.status !== 'removed');
|
|
22
|
+
if (filesToScan.length === 0) {
|
|
23
|
+
return {
|
|
24
|
+
status: 'pass',
|
|
25
|
+
score: 'A',
|
|
26
|
+
findings: [],
|
|
27
|
+
scan_duration_ms: 0,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// 2. Create temp directory
|
|
31
|
+
const tmpDir = await fs.mkdtemp(path.join(tmpdir(), 'shipsafe-pr-'));
|
|
32
|
+
try {
|
|
33
|
+
// 3. Download and write each file
|
|
34
|
+
const filePaths = [];
|
|
35
|
+
for (const file of filesToScan) {
|
|
36
|
+
const content = await fetchFileContent(options.repoFullName, file.filename, options.headSha, token);
|
|
37
|
+
const filePath = path.join(tmpDir, file.filename);
|
|
38
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
39
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
40
|
+
filePaths.push(file.filename);
|
|
41
|
+
}
|
|
42
|
+
// 4. Run pattern engine scan on the temp directory
|
|
43
|
+
const result = await runPatternEngine({
|
|
44
|
+
targetPath: tmpDir,
|
|
45
|
+
scope: 'all',
|
|
46
|
+
stagedFiles: filePaths,
|
|
47
|
+
});
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
// 5. Clean up temp directory
|
|
52
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Fetch the list of files changed in a PR.
|
|
57
|
+
*/
|
|
58
|
+
async function fetchChangedFiles(repoFullName, prNumber, token) {
|
|
59
|
+
const result = (await githubApi(`/repos/${repoFullName}/pulls/${prNumber}/files`, {
|
|
60
|
+
token,
|
|
61
|
+
}));
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Fetch the raw content of a file at a specific commit SHA.
|
|
66
|
+
*/
|
|
67
|
+
async function fetchFileContent(repoFullName, filePath, sha, token) {
|
|
68
|
+
const result = (await githubApi(`/repos/${repoFullName}/contents/${filePath}?ref=${sha}`, {
|
|
69
|
+
token,
|
|
70
|
+
}));
|
|
71
|
+
if (result.content && result.encoding === 'base64') {
|
|
72
|
+
return Buffer.from(result.content, 'base64').toString('utf-8');
|
|
73
|
+
}
|
|
74
|
+
// Fallback: try raw download
|
|
75
|
+
const rawResult = (await githubApi(`https://raw.githubusercontent.com/${repoFullName}/${sha}/${filePath}`, { token }));
|
|
76
|
+
return rawResult;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../../src/github/scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface WebhookEvent {
|
|
2
|
+
action: string;
|
|
3
|
+
pull_request: {
|
|
4
|
+
number: number;
|
|
5
|
+
head: {
|
|
6
|
+
sha: string;
|
|
7
|
+
ref: string;
|
|
8
|
+
};
|
|
9
|
+
base: {
|
|
10
|
+
sha: string;
|
|
11
|
+
ref: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
repository: {
|
|
15
|
+
full_name: string;
|
|
16
|
+
clone_url: string;
|
|
17
|
+
};
|
|
18
|
+
installation: {
|
|
19
|
+
id: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Verify a GitHub webhook signature using HMAC-SHA256.
|
|
24
|
+
* Compares the computed signature against the provided one using timing-safe comparison.
|
|
25
|
+
*/
|
|
26
|
+
export declare function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Process a GitHub webhook event.
|
|
29
|
+
*
|
|
30
|
+
* Flow:
|
|
31
|
+
* 1. Verify webhook signature
|
|
32
|
+
* 2. If action is 'opened' or 'synchronize':
|
|
33
|
+
* a. Create a pending check run via GitHub Checks API
|
|
34
|
+
* b. Scan the PR diff
|
|
35
|
+
* c. Post check run result (pass/fail with findings summary)
|
|
36
|
+
* 3. Ignore other actions
|
|
37
|
+
*/
|
|
38
|
+
export declare function handleWebhook(event: WebhookEvent, signature: string, secret: string): Promise<void>;
|
|
39
|
+
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../../src/github/webhook.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC;QACnC,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC;KACpC,CAAC;IACF,UAAU,EAAE;QACV,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,YAAY,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9B;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAaT;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAqDf"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { createCheckRun, completeCheckRun } from './checks.js';
|
|
3
|
+
import { scanPullRequest } from './scanner.js';
|
|
4
|
+
/**
|
|
5
|
+
* Verify a GitHub webhook signature using HMAC-SHA256.
|
|
6
|
+
* Compares the computed signature against the provided one using timing-safe comparison.
|
|
7
|
+
*/
|
|
8
|
+
export function verifyWebhookSignature(payload, signature, secret) {
|
|
9
|
+
const computed = 'sha256=' + createHmac('sha256', secret).update(payload).digest('hex');
|
|
10
|
+
// Both must have the same length for timingSafeEqual
|
|
11
|
+
if (computed.length !== signature.length) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
return timingSafeEqual(Buffer.from(computed), Buffer.from(signature));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Process a GitHub webhook event.
|
|
23
|
+
*
|
|
24
|
+
* Flow:
|
|
25
|
+
* 1. Verify webhook signature
|
|
26
|
+
* 2. If action is 'opened' or 'synchronize':
|
|
27
|
+
* a. Create a pending check run via GitHub Checks API
|
|
28
|
+
* b. Scan the PR diff
|
|
29
|
+
* c. Post check run result (pass/fail with findings summary)
|
|
30
|
+
* 3. Ignore other actions
|
|
31
|
+
*/
|
|
32
|
+
export async function handleWebhook(event, signature, secret) {
|
|
33
|
+
// 1. Verify signature
|
|
34
|
+
const payload = JSON.stringify(event);
|
|
35
|
+
if (!verifyWebhookSignature(payload, signature, secret)) {
|
|
36
|
+
throw new Error('Invalid webhook signature');
|
|
37
|
+
}
|
|
38
|
+
// 2. Only process PR opened and synchronize events
|
|
39
|
+
if (event.action !== 'opened' && event.action !== 'synchronize') {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const { pull_request: pr, repository, installation } = event;
|
|
43
|
+
// 2a. Create a pending check run
|
|
44
|
+
const checkRunId = await createCheckRun({
|
|
45
|
+
repoFullName: repository.full_name,
|
|
46
|
+
headSha: pr.head.sha,
|
|
47
|
+
installationId: installation.id,
|
|
48
|
+
});
|
|
49
|
+
try {
|
|
50
|
+
// 2b. Scan the PR diff
|
|
51
|
+
const scanResult = await scanPullRequest({
|
|
52
|
+
repoFullName: repository.full_name,
|
|
53
|
+
prNumber: pr.number,
|
|
54
|
+
headSha: pr.head.sha,
|
|
55
|
+
baseSha: pr.base.sha,
|
|
56
|
+
installationId: installation.id,
|
|
57
|
+
});
|
|
58
|
+
// 2c. Determine conclusion and post result
|
|
59
|
+
const conclusion = scanResult.status === 'fail' ? 'failure' : 'success';
|
|
60
|
+
await completeCheckRun({
|
|
61
|
+
repoFullName: repository.full_name,
|
|
62
|
+
checkRunId,
|
|
63
|
+
installationId: installation.id,
|
|
64
|
+
conclusion,
|
|
65
|
+
findings: scanResult.findings,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
// If scanning fails, mark the check as neutral with an error message
|
|
70
|
+
await completeCheckRun({
|
|
71
|
+
repoFullName: repository.full_name,
|
|
72
|
+
checkRunId,
|
|
73
|
+
installationId: installation.id,
|
|
74
|
+
conclusion: 'neutral',
|
|
75
|
+
findings: [],
|
|
76
|
+
});
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../../src/github/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAgB/C;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAe,EACf,SAAiB,EACjB,MAAc;IAEd,MAAM,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAExF,qDAAqD;IACrD,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC;QACzC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC;QACH,OAAO,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IACxE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAAmB,EACnB,SAAiB,EACjB,MAAc;IAEd,sBAAsB;IACtB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACtC,IAAI,CAAC,sBAAsB,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC/C,CAAC;IAED,mDAAmD;IACnD,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;QAChE,OAAO;IACT,CAAC;IAED,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC;IAE7D,iCAAiC;IACjC,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC;QACtC,YAAY,EAAE,UAAU,CAAC,SAAS;QAClC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG;QACpB,cAAc,EAAE,YAAY,CAAC,EAAE;KAChC,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,uBAAuB;QACvB,MAAM,UAAU,GAAG,MAAM,eAAe,CAAC;YACvC,YAAY,EAAE,UAAU,CAAC,SAAS;YAClC,QAAQ,EAAE,EAAE,CAAC,MAAM;YACnB,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG;YACpB,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG;YACpB,cAAc,EAAE,YAAY,CAAC,EAAE;SAChC,CAAC,CAAC;QAEH,2CAA2C;QAC3C,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QAExE,MAAM,gBAAgB,CAAC;YACrB,YAAY,EAAE,UAAU,CAAC,SAAS;YAClC,UAAU;YACV,cAAc,EAAE,YAAY,CAAC,EAAE;YAC/B,UAAU;YACV,QAAQ,EAAE,UAAU,CAAC,QAAQ;SAC9B,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,qEAAqE;QACrE,MAAM,gBAAgB,CAAC;YACrB,YAAY,EAAE,UAAU,CAAC,SAAS;YAClC,UAAU;YACV,cAAc,EAAE,YAAY,CAAC,EAAE;YAC/B,UAAU,EAAE,SAAS;YACrB,QAAQ,EAAE,EAAE;SACb,CAAC,CAAC;QAEH,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function installHooks(projectDir?: string): Promise<void>;
|
|
2
|
+
export declare function uninstallHooks(projectDir?: string): Promise<void>;
|
|
3
|
+
export declare function checkHooksInstalled(projectDir?: string): Promise<boolean>;
|
|
4
|
+
//# sourceMappingURL=installer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../../../src/hooks/installer.ts"],"names":[],"mappings":"AAuEA,wBAAsB,YAAY,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BrE;AAED,wBAAsB,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCvE;AAED,wBAAsB,mBAAmB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiB/E"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { HOOK_MARKER } from '../constants.js';
|
|
4
|
+
const PRE_COMMIT_HOOK = `#!/bin/sh
|
|
5
|
+
${HOOK_MARKER}
|
|
6
|
+
# ShipSafe pre-commit hook — scans staged files before commit
|
|
7
|
+
|
|
8
|
+
SHIPSAFE=\$(command -v shipsafe 2>/dev/null)
|
|
9
|
+
if [ -z "\$SHIPSAFE" ]; then
|
|
10
|
+
SHIPSAFE="npx shipsafe"
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
echo "ShipSafe: Scanning staged files..."
|
|
14
|
+
\$SHIPSAFE scan --scope staged
|
|
15
|
+
|
|
16
|
+
EXIT_CODE=\$?
|
|
17
|
+
if [ \$EXIT_CODE -ne 0 ]; then
|
|
18
|
+
echo ""
|
|
19
|
+
echo "ShipSafe: Critical/high security issues found. Fix before committing."
|
|
20
|
+
echo "To bypass (not recommended): git commit --no-verify"
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
exit 0
|
|
25
|
+
`;
|
|
26
|
+
const PRE_PUSH_HOOK = `#!/bin/sh
|
|
27
|
+
${HOOK_MARKER}
|
|
28
|
+
# ShipSafe pre-push hook — runs full scan before push
|
|
29
|
+
|
|
30
|
+
SHIPSAFE=\$(command -v shipsafe 2>/dev/null)
|
|
31
|
+
if [ -z "\$SHIPSAFE" ]; then
|
|
32
|
+
SHIPSAFE="npx shipsafe"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
echo "ShipSafe: Running full scan before push..."
|
|
36
|
+
\$SHIPSAFE scan --scope all
|
|
37
|
+
|
|
38
|
+
EXIT_CODE=\$?
|
|
39
|
+
if [ \$EXIT_CODE -ne 0 ]; then
|
|
40
|
+
echo ""
|
|
41
|
+
echo "ShipSafe: Critical/high security issues found. Fix before pushing."
|
|
42
|
+
echo "To bypass (not recommended): git push --no-verify"
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
exit 0
|
|
47
|
+
`;
|
|
48
|
+
const HOOKS = [
|
|
49
|
+
{ name: 'pre-commit', content: PRE_COMMIT_HOOK },
|
|
50
|
+
{ name: 'pre-push', content: PRE_PUSH_HOOK },
|
|
51
|
+
];
|
|
52
|
+
async function getHooksDir(projectDir) {
|
|
53
|
+
const gitDir = path.join(projectDir, '.git');
|
|
54
|
+
try {
|
|
55
|
+
const stat = await fs.stat(gitDir);
|
|
56
|
+
if (!stat.isDirectory()) {
|
|
57
|
+
throw new Error('Not a git repository');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
if (err.code === 'ENOENT') {
|
|
62
|
+
throw new Error('Not a git repository');
|
|
63
|
+
}
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
return path.join(gitDir, 'hooks');
|
|
67
|
+
}
|
|
68
|
+
export async function installHooks(projectDir) {
|
|
69
|
+
const dir = projectDir ?? process.cwd();
|
|
70
|
+
const hooksDir = await getHooksDir(dir);
|
|
71
|
+
await fs.mkdir(hooksDir, { recursive: true });
|
|
72
|
+
for (const hook of HOOKS) {
|
|
73
|
+
const hookPath = path.join(hooksDir, hook.name);
|
|
74
|
+
const backupPath = path.join(hooksDir, `${hook.name}.pre-shipsafe`);
|
|
75
|
+
// Check if hook file already exists
|
|
76
|
+
let existingContent = null;
|
|
77
|
+
try {
|
|
78
|
+
existingContent = await fs.readFile(hookPath, 'utf-8');
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// File doesn't exist, that's fine
|
|
82
|
+
}
|
|
83
|
+
if (existingContent !== null && !existingContent.includes(HOOK_MARKER)) {
|
|
84
|
+
// Existing non-ShipSafe hook — back it up
|
|
85
|
+
await fs.writeFile(backupPath, existingContent);
|
|
86
|
+
}
|
|
87
|
+
// Write the ShipSafe hook
|
|
88
|
+
await fs.writeFile(hookPath, hook.content, { mode: 0o755 });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export async function uninstallHooks(projectDir) {
|
|
92
|
+
const dir = projectDir ?? process.cwd();
|
|
93
|
+
let hooksDir;
|
|
94
|
+
try {
|
|
95
|
+
hooksDir = await getHooksDir(dir);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Not a git repo or no .git dir — nothing to uninstall
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
for (const hook of HOOKS) {
|
|
102
|
+
const hookPath = path.join(hooksDir, hook.name);
|
|
103
|
+
const backupPath = path.join(hooksDir, `${hook.name}.pre-shipsafe`);
|
|
104
|
+
// Check if current hook is a ShipSafe hook
|
|
105
|
+
let content = null;
|
|
106
|
+
try {
|
|
107
|
+
content = await fs.readFile(hookPath, 'utf-8');
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Hook doesn't exist, skip
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (content !== null && content.includes(HOOK_MARKER)) {
|
|
114
|
+
// Remove the ShipSafe hook
|
|
115
|
+
await fs.unlink(hookPath);
|
|
116
|
+
// Restore backup if it exists
|
|
117
|
+
try {
|
|
118
|
+
const backup = await fs.readFile(backupPath, 'utf-8');
|
|
119
|
+
await fs.writeFile(hookPath, backup, { mode: 0o755 });
|
|
120
|
+
await fs.unlink(backupPath);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// No backup to restore, that's fine
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
export async function checkHooksInstalled(projectDir) {
|
|
129
|
+
const dir = projectDir ?? process.cwd();
|
|
130
|
+
let hooksDir;
|
|
131
|
+
try {
|
|
132
|
+
hooksDir = await getHooksDir(dir);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
const preCommitPath = path.join(hooksDir, 'pre-commit');
|
|
138
|
+
try {
|
|
139
|
+
const content = await fs.readFile(preCommitPath, 'utf-8');
|
|
140
|
+
return content.includes(HOOK_MARKER);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=installer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../../../src/hooks/installer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/mcp/server.ts"],"names":[],"mappings":"AAWA,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CA8HpD"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { handleScan } from './tools/scan.js';
|
|
5
|
+
import { handleStatus } from './tools/status.js';
|
|
6
|
+
import { handleGraphQuery } from './tools/graph-query.js';
|
|
7
|
+
import { handleProductionErrors } from './tools/production-errors.js';
|
|
8
|
+
import { handleFix } from './tools/fix.js';
|
|
9
|
+
import { handleVerifyResolution } from './tools/verify-resolution.js';
|
|
10
|
+
import { handleCheckPackage } from './tools/check-package.js';
|
|
11
|
+
export async function startMcpServer() {
|
|
12
|
+
const server = new McpServer({
|
|
13
|
+
name: 'shipsafe',
|
|
14
|
+
version: '0.1.0',
|
|
15
|
+
});
|
|
16
|
+
// Register shipsafe_scan tool
|
|
17
|
+
server.tool('shipsafe_scan', 'Run security scan on the current project. Checks for vulnerabilities, hardcoded secrets, and dependency CVEs.', {
|
|
18
|
+
scope: z.string().optional().describe('Scan scope: staged (default), all, or file:<path>'),
|
|
19
|
+
fix: z.boolean().optional().describe('Attempt auto-fix (default: false)'),
|
|
20
|
+
}, async (params) => {
|
|
21
|
+
const result = await handleScan(params);
|
|
22
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
23
|
+
});
|
|
24
|
+
// Register shipsafe_status tool
|
|
25
|
+
server.tool('shipsafe_status', 'Get current project security status, hook state, and scanner availability.', async () => {
|
|
26
|
+
const result = await handleStatus();
|
|
27
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
28
|
+
});
|
|
29
|
+
// Register shipsafe_graph_query tool
|
|
30
|
+
server.tool('shipsafe_graph_query', 'Query the project knowledge graph for callers, callees, attack paths, blast radius, or missing auth chains.', {
|
|
31
|
+
query_type: z.enum(['callers', 'callees', 'data_flow', 'attack_paths', 'blast_radius', 'auth_chain'])
|
|
32
|
+
.describe('Type of graph query to run'),
|
|
33
|
+
target: z.string().optional()
|
|
34
|
+
.describe('Target function name (required for callers, callees, data_flow, blast_radius)'),
|
|
35
|
+
depth: z.number().optional()
|
|
36
|
+
.describe('Max traversal depth (default: 3 for callers/callees, 5 for data_flow)'),
|
|
37
|
+
}, async (params) => {
|
|
38
|
+
const result = await handleGraphQuery({
|
|
39
|
+
query_type: params.query_type,
|
|
40
|
+
target: params.target,
|
|
41
|
+
depth: params.depth,
|
|
42
|
+
});
|
|
43
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
44
|
+
});
|
|
45
|
+
// Register shipsafe_production_errors tool
|
|
46
|
+
server.tool('shipsafe_production_errors', 'Fetch queued production errors for this project. Returns errors with stack traces, root causes, and suggested fixes.', {
|
|
47
|
+
severity: z.enum(['all', 'critical', 'high', 'medium', 'low']).optional()
|
|
48
|
+
.describe('Filter by severity (default: all)'),
|
|
49
|
+
status: z.enum(['open', 'resolved', 'all']).optional()
|
|
50
|
+
.describe('Filter by status (default: open)'),
|
|
51
|
+
}, async (params) => {
|
|
52
|
+
const result = await handleProductionErrors({
|
|
53
|
+
severity: params.severity,
|
|
54
|
+
status: params.status,
|
|
55
|
+
});
|
|
56
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
57
|
+
});
|
|
58
|
+
// Register shipsafe_fix tool
|
|
59
|
+
server.tool('shipsafe_fix', 'Apply a fix for a scan finding. For hardcoded secrets, moves them to .env automatically. For other findings, returns the suggestion.', {
|
|
60
|
+
finding_id: z.string().describe('The ID of the finding to fix (from shipsafe_scan results)'),
|
|
61
|
+
strategy: z.enum(['suggested', 'custom']).optional()
|
|
62
|
+
.describe('Fix strategy: suggested (default) or custom'),
|
|
63
|
+
}, async (params) => {
|
|
64
|
+
const result = await handleFix({
|
|
65
|
+
finding_id: params.finding_id,
|
|
66
|
+
strategy: params.strategy,
|
|
67
|
+
});
|
|
68
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
69
|
+
});
|
|
70
|
+
// Register shipsafe_verify_resolution tool
|
|
71
|
+
server.tool('shipsafe_verify_resolution', 'Check if a production error has been resolved or is still recurring.', {
|
|
72
|
+
error_id: z.string().describe('The ID of the production error to check'),
|
|
73
|
+
}, async (params) => {
|
|
74
|
+
const result = await handleVerifyResolution({
|
|
75
|
+
error_id: params.error_id,
|
|
76
|
+
});
|
|
77
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
78
|
+
});
|
|
79
|
+
// Register shipsafe_check_package tool
|
|
80
|
+
server.tool('shipsafe_check_package', 'Vet an npm package before installing. Checks for typosquatting, maintenance status, license compatibility, and known vulnerabilities.', {
|
|
81
|
+
name: z.string().describe('Package name to check'),
|
|
82
|
+
version: z.string().optional().describe('Specific version to check (default: latest)'),
|
|
83
|
+
registry: z.enum(['npm', 'pip', 'cargo']).optional()
|
|
84
|
+
.describe('Package registry (default: npm, only npm currently supported)'),
|
|
85
|
+
}, async (params) => {
|
|
86
|
+
const result = await handleCheckPackage({
|
|
87
|
+
name: params.name,
|
|
88
|
+
version: params.version,
|
|
89
|
+
registry: params.registry,
|
|
90
|
+
});
|
|
91
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
92
|
+
});
|
|
93
|
+
const transport = new StdioServerTransport();
|
|
94
|
+
await server.connect(transport);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/mcp/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface CheckPackageParams {
|
|
2
|
+
name: string;
|
|
3
|
+
version?: string;
|
|
4
|
+
registry?: 'npm' | 'pip' | 'cargo';
|
|
5
|
+
}
|
|
6
|
+
export interface CheckPackageResult {
|
|
7
|
+
name: string;
|
|
8
|
+
version: string;
|
|
9
|
+
safe: boolean;
|
|
10
|
+
cves: string[];
|
|
11
|
+
license: string;
|
|
12
|
+
license_compatible: boolean;
|
|
13
|
+
maintained: boolean;
|
|
14
|
+
last_publish: string;
|
|
15
|
+
typosquat_warning: boolean;
|
|
16
|
+
recommendation: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Compute Levenshtein edit distance between two strings.
|
|
20
|
+
*/
|
|
21
|
+
export declare function editDistance(a: string, b: string): number;
|
|
22
|
+
/**
|
|
23
|
+
* Check if a package name looks like a typosquat of a popular package.
|
|
24
|
+
*/
|
|
25
|
+
export declare function checkTyposquat(name: string): {
|
|
26
|
+
isTyposquat: boolean;
|
|
27
|
+
similarTo?: string;
|
|
28
|
+
};
|
|
29
|
+
export declare function handleCheckPackage(params: CheckPackageParams): Promise<CheckPackageResult>;
|
|
30
|
+
//# sourceMappingURL=check-package.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-package.d.ts","sourceRoot":"","sources":["../../../../src/mcp/tools/check-package.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;CACxB;AAoCD;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAuBzD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,WAAW,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAYzF;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,kBAAkB,CAAC,CAoJ7B"}
|