devmap 0.3.8__tar.gz → 0.3.9__tar.gz
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.
- {devmap-0.3.8 → devmap-0.3.9}/PKG-INFO +1 -1
- {devmap-0.3.8 → devmap-0.3.9}/devmap/_devmap.mjs +452 -81
- {devmap-0.3.8 → devmap-0.3.9}/devmap.egg-info/PKG-INFO +1 -1
- {devmap-0.3.8 → devmap-0.3.9}/pyproject.toml +1 -1
- {devmap-0.3.8 → devmap-0.3.9}/README.md +0 -0
- {devmap-0.3.8 → devmap-0.3.9}/devmap/__init__.py +0 -0
- {devmap-0.3.8 → devmap-0.3.9}/devmap.egg-info/SOURCES.txt +0 -0
- {devmap-0.3.8 → devmap-0.3.9}/devmap.egg-info/dependency_links.txt +0 -0
- {devmap-0.3.8 → devmap-0.3.9}/devmap.egg-info/entry_points.txt +0 -0
- {devmap-0.3.8 → devmap-0.3.9}/devmap.egg-info/top_level.txt +0 -0
- {devmap-0.3.8 → devmap-0.3.9}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devmap
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9
|
|
4
4
|
Summary: Map your machine's dev environment and surface issues (PATH conflicts, ghost binaries, multi-version collisions). fastfetch-for-dev-tools. This PyPI package ships the real devmap CLI (a bundled JavaScript app launched via Node.js or Bun), so `pip install devmap && devmap` works standalone.
|
|
5
5
|
Author: devmap contributors
|
|
6
6
|
License: MIT
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/version.ts
|
|
5
|
-
var VERSION = "0.3.
|
|
5
|
+
var VERSION = "0.3.9";
|
|
6
6
|
|
|
7
7
|
// src/install.ts
|
|
8
8
|
function isIsolated(i) {
|
|
@@ -662,6 +662,206 @@ function runScaAudit(cwd) {
|
|
|
662
662
|
return checks;
|
|
663
663
|
}
|
|
664
664
|
|
|
665
|
+
// src/security/secrets.ts
|
|
666
|
+
import { existsSync as existsSync2, readFileSync, statSync as statSync2, readdirSync as readdirSync2 } from "node:fs";
|
|
667
|
+
import { homedir } from "node:os";
|
|
668
|
+
import { join as join2, relative, isAbsolute } from "node:path";
|
|
669
|
+
var RULES = [
|
|
670
|
+
{ name: "AWS Access Key ID", re: /\b(AKIA[0-9A-Z]{16})\b/g, capture: true },
|
|
671
|
+
{ name: "AWS Secret Access Key", re: /aws_secret_access_key\s*[=:]\s*['"]?([A-Za-z0-9/+=]{40})['"]?/gi, capture: true },
|
|
672
|
+
{ name: "GitHub Token", re: /\b(ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{82})\b/g, capture: true },
|
|
673
|
+
{ name: "GitLab Personal Access Token", re: /\bglpat-[A-Za-z0-9_-]{20}\b/g, capture: true },
|
|
674
|
+
{ name: "Slack Token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, capture: true },
|
|
675
|
+
{
|
|
676
|
+
name: "Slack Incoming Webhook",
|
|
677
|
+
re: /https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9/]+/g,
|
|
678
|
+
capture: false,
|
|
679
|
+
mask: "https://hooks.slack.com/services/***"
|
|
680
|
+
},
|
|
681
|
+
{ name: "Stripe Secret Key", re: /\bsk_live_[A-Za-z0-9]{24,}\b/g, capture: true },
|
|
682
|
+
{ name: "OpenAI API Key", re: /\bsk-[A-Za-z0-9]{20,}\b/g, capture: true },
|
|
683
|
+
{ name: "GCP API Key", re: /\bAIza[0-9A-Za-z_-]{35}\b/g, capture: true },
|
|
684
|
+
{ name: "Google OAuth Client Secret", re: /"client_secret"\s*:\s*"([A-Za-z0-9-_]{20,})"/g, capture: true },
|
|
685
|
+
{
|
|
686
|
+
name: "Private Key Block",
|
|
687
|
+
re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/g,
|
|
688
|
+
capture: false,
|
|
689
|
+
mask: "[PRIVATE KEY BLOCK]"
|
|
690
|
+
},
|
|
691
|
+
{ name: "Bearer Token", re: /\bBearer\s+([A-Za-z0-9._-]{20,})\b/g, capture: true },
|
|
692
|
+
{ name: "Database URL with password", re: /(?:postgres|postgresql|mysql|mongodb(?:\+srv)?):\/\/[^:/\s]+:([^@\s]+)@/gi, capture: true },
|
|
693
|
+
{ name: "npm authToken", re: /\/\/registry\.npmjs\.org\/:_authToken=([A-Za-z0-9_]{36,})/g, capture: true },
|
|
694
|
+
{
|
|
695
|
+
name: "Git URL with embedded credentials",
|
|
696
|
+
re: /[a-z]+:\/\/([^\s:@/]+):([^\s:@/]+)@/gi,
|
|
697
|
+
capture: true,
|
|
698
|
+
captureGroup: 2
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
name: "Generic Secret Assignment",
|
|
702
|
+
re: /(?:api[_-]?key|apikey|secret[_-]?key|access[_-]?token|auth[_-]?token|client[_-]?secret|private[_-]?key|secret|passwd|password|token)\s*[=:]\s*['"]?([A-Za-z0-9/+=_\-]{8,64})['"]?/gi,
|
|
703
|
+
capture: true,
|
|
704
|
+
generic: true
|
|
705
|
+
}
|
|
706
|
+
];
|
|
707
|
+
function redactText(v) {
|
|
708
|
+
if (v.length <= 4)
|
|
709
|
+
return "*".repeat(v.length);
|
|
710
|
+
return v.slice(0, 4) + "****" + (v.length > 8 ? " (len " + v.length + ")" : "");
|
|
711
|
+
}
|
|
712
|
+
function looksSecret(v) {
|
|
713
|
+
if (v.length >= 32)
|
|
714
|
+
return true;
|
|
715
|
+
if (v.length < 12)
|
|
716
|
+
return false;
|
|
717
|
+
let classes = 0;
|
|
718
|
+
if (/[a-z]/.test(v))
|
|
719
|
+
classes++;
|
|
720
|
+
if (/[A-Z]/.test(v))
|
|
721
|
+
classes++;
|
|
722
|
+
if (/[0-9]/.test(v))
|
|
723
|
+
classes++;
|
|
724
|
+
if (/[^A-Za-z0-9]/.test(v))
|
|
725
|
+
classes++;
|
|
726
|
+
return classes >= 3;
|
|
727
|
+
}
|
|
728
|
+
function slug(s) {
|
|
729
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
730
|
+
}
|
|
731
|
+
function toRel(p) {
|
|
732
|
+
const home = homedir();
|
|
733
|
+
const lower = p.replace(/\\/g, "/");
|
|
734
|
+
const homeLower = home.replace(/\\/g, "/");
|
|
735
|
+
if (lower.startsWith(homeLower))
|
|
736
|
+
return "~" + lower.slice(homeLower.length);
|
|
737
|
+
try {
|
|
738
|
+
const r = relative(process.cwd(), p).replace(/\\/g, "/");
|
|
739
|
+
if (!r.startsWith("..") && !isAbsolute(r))
|
|
740
|
+
return r;
|
|
741
|
+
} catch {}
|
|
742
|
+
return p;
|
|
743
|
+
}
|
|
744
|
+
function discoverFiles(projectRoots, includeHome) {
|
|
745
|
+
const home = homedir();
|
|
746
|
+
const out = [];
|
|
747
|
+
const seen = new Set;
|
|
748
|
+
const add = (p) => {
|
|
749
|
+
try {
|
|
750
|
+
if (existsSync2(p) && statSync2(p).isFile()) {
|
|
751
|
+
const rp = p.replace(/\\/g, "/");
|
|
752
|
+
if (!seen.has(rp)) {
|
|
753
|
+
seen.add(rp);
|
|
754
|
+
out.push(p);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
} catch {}
|
|
758
|
+
};
|
|
759
|
+
if (includeHome) {
|
|
760
|
+
const homeFiles = [
|
|
761
|
+
".env",
|
|
762
|
+
".bashrc",
|
|
763
|
+
".zshrc",
|
|
764
|
+
".bash_profile",
|
|
765
|
+
".profile",
|
|
766
|
+
".npmrc",
|
|
767
|
+
".pypirc",
|
|
768
|
+
".netrc",
|
|
769
|
+
".git-credentials",
|
|
770
|
+
".gitconfig",
|
|
771
|
+
".docker/config.json",
|
|
772
|
+
".kube/config",
|
|
773
|
+
".aws/credentials",
|
|
774
|
+
".config/gcloud/application_default_credentials.json",
|
|
775
|
+
".azure/accessTokens.json",
|
|
776
|
+
".ssh/id_rsa",
|
|
777
|
+
".ssh/id_ecdsa",
|
|
778
|
+
".ssh/id_ed25519",
|
|
779
|
+
".ssh/id_dsa",
|
|
780
|
+
".envrc"
|
|
781
|
+
];
|
|
782
|
+
for (const f of homeFiles)
|
|
783
|
+
add(join2(home, f));
|
|
784
|
+
try {
|
|
785
|
+
for (const e of readdirSync2(home)) {
|
|
786
|
+
if (e.startsWith(".env") && e !== ".env")
|
|
787
|
+
add(join2(home, e));
|
|
788
|
+
}
|
|
789
|
+
} catch {}
|
|
790
|
+
}
|
|
791
|
+
const roots = [...new Set([process.cwd(), ...projectRoots])];
|
|
792
|
+
for (const root of roots) {
|
|
793
|
+
let entries = [];
|
|
794
|
+
try {
|
|
795
|
+
entries = readdirSync2(root);
|
|
796
|
+
} catch {
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
for (const e of entries) {
|
|
800
|
+
if (e === ".env" || e.startsWith(".env.") || e === ".envrc" || e === ".npmrc" || e === ".pypirc" || e === ".netrc" || e === ".gitconfig" || e === "credentials.json" || e === "secrets.json" || e.endsWith(".pem") || e.endsWith(".key") || e.endsWith(".p12") || e.endsWith(".pfx") || /^service-?account.*\.json$/i.test(e)) {
|
|
801
|
+
add(join2(root, e));
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
add(join2(root, ".git", "config"));
|
|
805
|
+
}
|
|
806
|
+
return out;
|
|
807
|
+
}
|
|
808
|
+
function scanSecrets(projectRoots = [], includeHome = true) {
|
|
809
|
+
const files = discoverFiles(projectRoots, includeHome);
|
|
810
|
+
const checks = [];
|
|
811
|
+
let scanned = 0;
|
|
812
|
+
for (const f of files) {
|
|
813
|
+
let content;
|
|
814
|
+
try {
|
|
815
|
+
const st = statSync2(f);
|
|
816
|
+
if (!st.isFile() || st.size > 512 * 1024)
|
|
817
|
+
continue;
|
|
818
|
+
content = readFileSync(f, "utf8");
|
|
819
|
+
} catch {
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
scanned++;
|
|
823
|
+
const lines = content.split(/\r?\n/);
|
|
824
|
+
for (let i = 0;i < lines.length; i++) {
|
|
825
|
+
const line = lines[i];
|
|
826
|
+
const accepted = [];
|
|
827
|
+
for (const rule of RULES) {
|
|
828
|
+
rule.re.lastIndex = 0;
|
|
829
|
+
let m;
|
|
830
|
+
while ((m = rule.re.exec(line)) !== null) {
|
|
831
|
+
if (m.index === rule.re.lastIndex)
|
|
832
|
+
rule.re.lastIndex++;
|
|
833
|
+
const start = m.index;
|
|
834
|
+
const end = start + m[0].length;
|
|
835
|
+
if (rule.generic === true) {
|
|
836
|
+
const val = (rule.captureGroup ? m[rule.captureGroup] : m[1]) || m[0];
|
|
837
|
+
if (!looksSecret(val))
|
|
838
|
+
continue;
|
|
839
|
+
if (accepted.some(([s, e]) => start < e && end > s))
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
const masked = redactValue(rule, m);
|
|
843
|
+
const rel = toRel(f);
|
|
844
|
+
const id = `security.secrets.${slug(rule.name)}.${checks.length + 1}`;
|
|
845
|
+
checks.push(makeCheck(id, "secret", "warn", `${rule.name} in ${rel}:${i + 1} (${masked})`, `${rule.name} detected at ${f}:${i + 1}. The value is redacted (${masked}) and never printed in full. ` + `Rotate or remove the credential and prefer a secret manager or build-time env injection.`, [f]));
|
|
846
|
+
accepted.push([start, end]);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
if (checks.length === 0) {
|
|
852
|
+
checks.push(makeCheck("security.secrets.ok", "secret", "info", `No hardcoded secrets found in ${scanned} scanned config file(s)`, `Scanned ${scanned} credential/config file(s) under HOME and project roots (opt-in --scan-secrets). ` + `No known secret patterns matched.`, files.slice(0, 20)));
|
|
853
|
+
}
|
|
854
|
+
return checks;
|
|
855
|
+
}
|
|
856
|
+
function redactValue(rule, m) {
|
|
857
|
+
if (rule.capture) {
|
|
858
|
+
const g = m[rule.captureGroup ?? 1];
|
|
859
|
+
if (g)
|
|
860
|
+
return redactText(g);
|
|
861
|
+
}
|
|
862
|
+
return rule.mask ?? "[redacted]";
|
|
863
|
+
}
|
|
864
|
+
|
|
665
865
|
// src/health/parse.ts
|
|
666
866
|
function parsePipCheck(text) {
|
|
667
867
|
const lines = (text || "").split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
@@ -1092,7 +1292,8 @@ async function runScan(shells, disabled, opts = {}) {
|
|
|
1092
1292
|
const securityChecks = [
|
|
1093
1293
|
...computeSecurity(byName),
|
|
1094
1294
|
...computeScaInventory(),
|
|
1095
|
-
...opts.auditDeps ? runScaAudit(process.cwd()) : []
|
|
1295
|
+
...opts.auditDeps ? runScaAudit(process.cwd()) : [],
|
|
1296
|
+
...opts.scanSecrets ? scanSecrets(opts.projectRoots) : []
|
|
1096
1297
|
];
|
|
1097
1298
|
const collected = [...allChecks, ...conflicts.checks, ...securityChecks, ...healthChecks].filter((c) => !isCheckDisabled(c.ID, disabled));
|
|
1098
1299
|
const environments = {
|
|
@@ -1171,6 +1372,169 @@ function queryManifest(m, opts) {
|
|
|
1171
1372
|
return { groups, checks, conflicts };
|
|
1172
1373
|
}
|
|
1173
1374
|
|
|
1375
|
+
// src/mcp.ts
|
|
1376
|
+
var ALL_SHELLS = ["current", "powershell", "gitbash", "wsl"];
|
|
1377
|
+
function parseShells(v) {
|
|
1378
|
+
if (typeof v !== "string" || !v)
|
|
1379
|
+
return ["current"];
|
|
1380
|
+
const list = v.split(",").map((s) => s.trim());
|
|
1381
|
+
return list.includes("all") ? ALL_SHELLS : list.length ? list : ["current"];
|
|
1382
|
+
}
|
|
1383
|
+
function parseDisable(v) {
|
|
1384
|
+
if (typeof v !== "string" || !v)
|
|
1385
|
+
return new Set;
|
|
1386
|
+
return new Set(v.split(",").map((s) => s.trim()).filter(Boolean));
|
|
1387
|
+
}
|
|
1388
|
+
var TOOLS = [
|
|
1389
|
+
{
|
|
1390
|
+
name: "scan_environment",
|
|
1391
|
+
description: "Run a full devmap scan of the local development environment and return the structured manifest " + "(detected tools across runtimes/pkg-managers/IDEs, PATH conflicts & ghost binaries, per-env health " + "checks, and the security posture). Use this first to understand a machine before proposing changes.",
|
|
1392
|
+
inputSchema: {
|
|
1393
|
+
type: "object",
|
|
1394
|
+
properties: {
|
|
1395
|
+
shells: {
|
|
1396
|
+
type: "string",
|
|
1397
|
+
description: "Comma-separated shell views to reconstruct: current|powershell|gitbash|wsl|all (default current)"
|
|
1398
|
+
},
|
|
1399
|
+
disable: {
|
|
1400
|
+
type: "string",
|
|
1401
|
+
description: "Comma-separated check-rule prefixes to skip, e.g. 'path.duplicate,health.conda.base'"
|
|
1402
|
+
},
|
|
1403
|
+
auditDeps: {
|
|
1404
|
+
type: "boolean",
|
|
1405
|
+
description: "Opt-in dependency CVE audit via installed SCA tools (OSV-Scanner/pip-audit/govulncheck/Trivy). Uses network."
|
|
1406
|
+
},
|
|
1407
|
+
healthNetwork: {
|
|
1408
|
+
type: "boolean",
|
|
1409
|
+
description: "Also run network health probes (npm doctor / rustup check). Off by default to stay offline-fast."
|
|
1410
|
+
},
|
|
1411
|
+
scanSecrets: {
|
|
1412
|
+
type: "boolean",
|
|
1413
|
+
description: "Opt-in: scan a curated allowlist of HOME/project credential files for hardcoded secrets (offline)."
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
},
|
|
1418
|
+
{
|
|
1419
|
+
name: "query_tool",
|
|
1420
|
+
description: "Query a scan for a specific tool, semantic category, conflict, or issue — e.g. selector 'node', " + "'runtime.node', 'pkg.npm', 'git', or 'python'. Returns matching installs plus any related findings. " + "Cheaper than a full scan when you already know what you are looking for.",
|
|
1421
|
+
inputSchema: {
|
|
1422
|
+
type: "object",
|
|
1423
|
+
properties: {
|
|
1424
|
+
selector: {
|
|
1425
|
+
type: "string",
|
|
1426
|
+
description: "Tool id substring or 'category.tool' dotted form (e.g. node / runtime.node / pkg.npm)"
|
|
1427
|
+
},
|
|
1428
|
+
issuesOnly: { type: "boolean", description: "Only return issues (warn/error/missing) for the match" },
|
|
1429
|
+
conflictsOnly: { type: "boolean", description: "Only return PATH/version/ghost conflicts for the match" },
|
|
1430
|
+
offPathOnly: { type: "boolean", description: "Only return isolated off-PATH installs (version-manager envs excluded)" },
|
|
1431
|
+
shells: { type: "string", description: "Shell views (see scan_environment)" },
|
|
1432
|
+
disable: { type: "string", description: "Check-rule prefixes to skip (see scan_environment)" }
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
];
|
|
1437
|
+
async function callTool(name, args) {
|
|
1438
|
+
if (name === "scan_environment") {
|
|
1439
|
+
const manifest = await runScan(parseShells(args.shells), parseDisable(args.disable), {
|
|
1440
|
+
silent: true,
|
|
1441
|
+
auditDeps: args.auditDeps === true,
|
|
1442
|
+
healthNetwork: args.healthNetwork === true,
|
|
1443
|
+
scanSecrets: args.scanSecrets === true
|
|
1444
|
+
});
|
|
1445
|
+
return { content: [{ type: "text", text: JSON.stringify(manifest, null, 2) }], isError: false };
|
|
1446
|
+
}
|
|
1447
|
+
if (name === "query_tool") {
|
|
1448
|
+
const manifest = await runScan(parseShells(args.shells), parseDisable(args.disable), { silent: true });
|
|
1449
|
+
const result = queryManifest(manifest, {
|
|
1450
|
+
selector: typeof args.selector === "string" ? args.selector : undefined,
|
|
1451
|
+
issues: args.issuesOnly === true,
|
|
1452
|
+
conflicts: args.conflictsOnly === true,
|
|
1453
|
+
offPath: args.offPathOnly === true
|
|
1454
|
+
});
|
|
1455
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], isError: false };
|
|
1456
|
+
}
|
|
1457
|
+
return { content: [{ type: "text", text: `unknown tool: ${name}` }], isError: true };
|
|
1458
|
+
}
|
|
1459
|
+
async function startMcpServer() {
|
|
1460
|
+
const toErr = (...a) => process.stderr.write(a.map((x) => typeof x === "string" ? x : String(x)).join(" ") + `
|
|
1461
|
+
`);
|
|
1462
|
+
console.log = toErr;
|
|
1463
|
+
console.error = toErr;
|
|
1464
|
+
console.warn = toErr;
|
|
1465
|
+
console.info = toErr;
|
|
1466
|
+
const write = (obj) => process.stdout.write(JSON.stringify(obj) + `
|
|
1467
|
+
`);
|
|
1468
|
+
const log = toErr;
|
|
1469
|
+
let buffer = "";
|
|
1470
|
+
process.stdin.setEncoding("utf8");
|
|
1471
|
+
for await (const chunk of process.stdin) {
|
|
1472
|
+
buffer += chunk;
|
|
1473
|
+
let nl;
|
|
1474
|
+
while ((nl = buffer.indexOf(`
|
|
1475
|
+
`)) >= 0) {
|
|
1476
|
+
const line = buffer.slice(0, nl).trim();
|
|
1477
|
+
buffer = buffer.slice(nl + 1);
|
|
1478
|
+
if (!line)
|
|
1479
|
+
continue;
|
|
1480
|
+
let msg;
|
|
1481
|
+
try {
|
|
1482
|
+
msg = JSON.parse(line);
|
|
1483
|
+
} catch {
|
|
1484
|
+
log("[mcp] dropping malformed frame");
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
const id = msg.id;
|
|
1488
|
+
const method = msg.method;
|
|
1489
|
+
log("[mcp] ->", method || "?", id ?? "");
|
|
1490
|
+
if (!method) {
|
|
1491
|
+
if (id !== undefined)
|
|
1492
|
+
write({ jsonrpc: "2.0", id, error: { code: -32600, message: "missing method" } });
|
|
1493
|
+
continue;
|
|
1494
|
+
}
|
|
1495
|
+
switch (method) {
|
|
1496
|
+
case "initialize":
|
|
1497
|
+
write({
|
|
1498
|
+
jsonrpc: "2.0",
|
|
1499
|
+
id,
|
|
1500
|
+
result: {
|
|
1501
|
+
protocolVersion: msg.params?.protocolVersion || "2024-11-05",
|
|
1502
|
+
capabilities: { tools: {} },
|
|
1503
|
+
serverInfo: { name: "devmap", version: VERSION }
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
break;
|
|
1507
|
+
case "notifications/initialized":
|
|
1508
|
+
case "initialized":
|
|
1509
|
+
case "notifications/cancelled":
|
|
1510
|
+
break;
|
|
1511
|
+
case "ping":
|
|
1512
|
+
write({ jsonrpc: "2.0", id, result: {} });
|
|
1513
|
+
break;
|
|
1514
|
+
case "tools/list":
|
|
1515
|
+
write({ jsonrpc: "2.0", id, result: { tools: TOOLS } });
|
|
1516
|
+
break;
|
|
1517
|
+
case "tools/call":
|
|
1518
|
+
try {
|
|
1519
|
+
const r = await callTool(msg.params?.name, msg.params?.arguments || {});
|
|
1520
|
+
write({ jsonrpc: "2.0", id, result: r });
|
|
1521
|
+
} catch (e) {
|
|
1522
|
+
write({
|
|
1523
|
+
jsonrpc: "2.0",
|
|
1524
|
+
id,
|
|
1525
|
+
result: { content: [{ type: "text", text: "error: " + (e?.message || String(e)) }], isError: true }
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
break;
|
|
1529
|
+
default:
|
|
1530
|
+
if (id !== undefined)
|
|
1531
|
+
write({ jsonrpc: "2.0", id, error: { code: -32601, message: `method not found: ${method}` } });
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
log("[mcp] stdin closed, shutting down");
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1174
1538
|
// src/diff.ts
|
|
1175
1539
|
var CAT_KEYS = [
|
|
1176
1540
|
["runtimes", "runtime"],
|
|
@@ -1264,9 +1628,9 @@ register({
|
|
|
1264
1628
|
});
|
|
1265
1629
|
|
|
1266
1630
|
// src/detector/runtimes.ts
|
|
1267
|
-
import { homedir } from "node:os";
|
|
1268
|
-
import { join as
|
|
1269
|
-
import { existsSync as
|
|
1631
|
+
import { homedir as homedir2 } from "node:os";
|
|
1632
|
+
import { join as join3 } from "node:path";
|
|
1633
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3 } from "node:fs";
|
|
1270
1634
|
|
|
1271
1635
|
// src/detector/util.ts
|
|
1272
1636
|
var CONCURRENCY = 12;
|
|
@@ -1336,27 +1700,27 @@ async function detectSimpleAsync(list, onProgress) {
|
|
|
1336
1700
|
}
|
|
1337
1701
|
|
|
1338
1702
|
// src/detector/runtimes.ts
|
|
1339
|
-
var HOME =
|
|
1703
|
+
var HOME = homedir2();
|
|
1340
1704
|
async function condaPythonPaths() {
|
|
1341
1705
|
const found = [];
|
|
1342
1706
|
const bases = new Set;
|
|
1343
1707
|
const ci = await runAsync("conda", ["info", "--base"], 15000);
|
|
1344
1708
|
if (ci)
|
|
1345
1709
|
bases.add(ci.trim());
|
|
1346
|
-
bases.add(
|
|
1347
|
-
bases.add(
|
|
1710
|
+
bases.add(join3(HOME, "miniconda3"));
|
|
1711
|
+
bases.add(join3(HOME, "anaconda3"));
|
|
1348
1712
|
bases.add("E:\\Users\\Administrator\\miniconda3");
|
|
1349
1713
|
for (const base of bases) {
|
|
1350
|
-
if (!base || !
|
|
1714
|
+
if (!base || !existsSync3(base))
|
|
1351
1715
|
continue;
|
|
1352
|
-
const basePy = IS_WIN ?
|
|
1353
|
-
if (
|
|
1716
|
+
const basePy = IS_WIN ? join3(base, "python.exe") : join3(base, "bin", "python");
|
|
1717
|
+
if (existsSync3(basePy))
|
|
1354
1718
|
found.push({ path: basePy, env: "base" });
|
|
1355
|
-
const envsDir =
|
|
1356
|
-
if (
|
|
1357
|
-
for (const e of
|
|
1358
|
-
const py = IS_WIN ?
|
|
1359
|
-
if (
|
|
1719
|
+
const envsDir = join3(base, "envs");
|
|
1720
|
+
if (existsSync3(envsDir)) {
|
|
1721
|
+
for (const e of readdirSync3(envsDir)) {
|
|
1722
|
+
const py = IS_WIN ? join3(envsDir, e, "python.exe") : join3(envsDir, e, "bin", "python");
|
|
1723
|
+
if (existsSync3(py))
|
|
1360
1724
|
found.push({ path: py, env: e });
|
|
1361
1725
|
}
|
|
1362
1726
|
}
|
|
@@ -1369,14 +1733,14 @@ async function scanCondaPythons() {
|
|
|
1369
1733
|
return paths.map((p, i) => ({ path: p.path, onPath: false, version: versions[i], env: p.env }));
|
|
1370
1734
|
}
|
|
1371
1735
|
async function scanNvmNodes() {
|
|
1372
|
-
const cands = [
|
|
1736
|
+
const cands = [join3(HOME, "AppData", "Roaming", "nvm"), join3(HOME, ".nvm", "versions", "node")];
|
|
1373
1737
|
const found = [];
|
|
1374
1738
|
for (const dir of cands) {
|
|
1375
|
-
if (!
|
|
1739
|
+
if (!existsSync3(dir))
|
|
1376
1740
|
continue;
|
|
1377
|
-
for (const v of
|
|
1378
|
-
const bin = IS_WIN ?
|
|
1379
|
-
if (
|
|
1741
|
+
for (const v of readdirSync3(dir)) {
|
|
1742
|
+
const bin = IS_WIN ? join3(dir, v, "node.exe") : join3(dir, v, "bin", "node");
|
|
1743
|
+
if (existsSync3(bin))
|
|
1380
1744
|
found.push({ path: bin, env: v });
|
|
1381
1745
|
}
|
|
1382
1746
|
}
|
|
@@ -1384,7 +1748,7 @@ async function scanNvmNodes() {
|
|
|
1384
1748
|
return found.map((f, i) => ({ path: f.path, onPath: false, version: versions[i], env: f.env }));
|
|
1385
1749
|
}
|
|
1386
1750
|
async function scanKnownBinary(cands, flags) {
|
|
1387
|
-
const hits = cands.filter((c) =>
|
|
1751
|
+
const hits = cands.filter((c) => existsSync3(c));
|
|
1388
1752
|
const versions = await pool(hits, 12, (c) => versionOfAsync(c, flags));
|
|
1389
1753
|
return hits.map((c, i) => ({ path: c, onPath: false, version: versions[i] }));
|
|
1390
1754
|
}
|
|
@@ -1399,7 +1763,7 @@ register({
|
|
|
1399
1763
|
detectRuntimeAsync("rustc", "rustc", ["--version"]),
|
|
1400
1764
|
detectRuntimeAsync("java", "java", ["-version"]),
|
|
1401
1765
|
detectRuntimeAsync("ruby", "ruby", ["--version"]),
|
|
1402
|
-
detectRuntimeAsync("bun", "bun", ["--version"], () => scanKnownBinary([
|
|
1766
|
+
detectRuntimeAsync("bun", "bun", ["--version"], () => scanKnownBinary([join3(HOME, ".bun", "bin", "bun"), join3(HOME, ".bun", "bin", "bun.exe")], ["--version"]))
|
|
1403
1767
|
]);
|
|
1404
1768
|
const groups = results.filter((g) => g !== null);
|
|
1405
1769
|
return { data: groups, checks: [] };
|
|
@@ -1407,7 +1771,7 @@ register({
|
|
|
1407
1771
|
});
|
|
1408
1772
|
|
|
1409
1773
|
// src/detector/pkgmgrs.ts
|
|
1410
|
-
var
|
|
1774
|
+
var TOOLS2 = [
|
|
1411
1775
|
"npm",
|
|
1412
1776
|
"pip",
|
|
1413
1777
|
"pip3",
|
|
@@ -1428,7 +1792,7 @@ var TOOLS = [
|
|
|
1428
1792
|
register({
|
|
1429
1793
|
name: "packageManagers",
|
|
1430
1794
|
async scan(ctx) {
|
|
1431
|
-
const data = await detectSimpleAsync(
|
|
1795
|
+
const data = await detectSimpleAsync(TOOLS2, (done, total) => {
|
|
1432
1796
|
ctx.progress?.sub(`packageManagers · ${done}/${total}`);
|
|
1433
1797
|
});
|
|
1434
1798
|
return { data, checks: [] };
|
|
@@ -1447,7 +1811,7 @@ register({
|
|
|
1447
1811
|
});
|
|
1448
1812
|
|
|
1449
1813
|
// src/detector/vcpkg.ts
|
|
1450
|
-
import { join as
|
|
1814
|
+
import { join as join4 } from "node:path";
|
|
1451
1815
|
var CANDIDATES = [
|
|
1452
1816
|
"C:\\vcpkg\\vcpkg.exe",
|
|
1453
1817
|
"C:\\src\\vcpkg\\vcpkg.exe",
|
|
@@ -1474,7 +1838,7 @@ async function findVcpkg() {
|
|
|
1474
1838
|
if (fileExists(c))
|
|
1475
1839
|
add(c, false);
|
|
1476
1840
|
if (process.env.VCPKG_ROOT) {
|
|
1477
|
-
const p =
|
|
1841
|
+
const p = join4(process.env.VCPKG_ROOT, IS_WIN ? "vcpkg.exe" : "vcpkg");
|
|
1478
1842
|
if (fileExists(p))
|
|
1479
1843
|
add(p, false);
|
|
1480
1844
|
}
|
|
@@ -1630,11 +1994,11 @@ register({
|
|
|
1630
1994
|
});
|
|
1631
1995
|
|
|
1632
1996
|
// src/detector/condaenv.ts
|
|
1633
|
-
import { join as
|
|
1634
|
-
import { existsSync as
|
|
1997
|
+
import { join as join5 } from "node:path";
|
|
1998
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
1635
1999
|
function pythonOf(envPath) {
|
|
1636
|
-
const py = IS_WIN ?
|
|
1637
|
-
return
|
|
2000
|
+
const py = IS_WIN ? join5(envPath, "python.exe") : join5(envPath, "bin", "python");
|
|
2001
|
+
return existsSync4(py) ? py : null;
|
|
1638
2002
|
}
|
|
1639
2003
|
register({
|
|
1640
2004
|
name: "condaenv",
|
|
@@ -1814,8 +2178,8 @@ register({
|
|
|
1814
2178
|
});
|
|
1815
2179
|
|
|
1816
2180
|
// src/detector/vsshells.ts
|
|
1817
|
-
import { readdirSync as
|
|
1818
|
-
import { join as
|
|
2181
|
+
import { readdirSync as readdirSync4, existsSync as existsSync5, statSync as statSync3 } from "node:fs";
|
|
2182
|
+
import { join as join6 } from "node:path";
|
|
1819
2183
|
var VS_BASES = [
|
|
1820
2184
|
"C:\\Program Files\\Microsoft Visual Studio",
|
|
1821
2185
|
"C:\\Program Files (x86)\\Microsoft Visual Studio"
|
|
@@ -1828,20 +2192,20 @@ var EDITIONS = ["Community", "Professional", "Enterprise", "BuildTools"];
|
|
|
1828
2192
|
function vsRoots() {
|
|
1829
2193
|
const roots = [];
|
|
1830
2194
|
for (const b of VS_BASES) {
|
|
1831
|
-
if (!
|
|
2195
|
+
if (!existsSync5(b))
|
|
1832
2196
|
continue;
|
|
1833
|
-
for (const v of
|
|
1834
|
-
const verDir =
|
|
1835
|
-
if (!
|
|
2197
|
+
for (const v of readdirSync4(b)) {
|
|
2198
|
+
const verDir = join6(b, v);
|
|
2199
|
+
if (!existsSync5(verDir) || !statSync3(verDir).isDirectory())
|
|
1836
2200
|
continue;
|
|
1837
2201
|
const hasEdition = EDITIONS.some((e) => {
|
|
1838
|
-
const ed =
|
|
1839
|
-
return
|
|
2202
|
+
const ed = join6(verDir, e);
|
|
2203
|
+
return existsSync5(ed) && statSync3(ed).isDirectory();
|
|
1840
2204
|
});
|
|
1841
2205
|
if (hasEdition) {
|
|
1842
2206
|
for (const e of EDITIONS) {
|
|
1843
|
-
const ed =
|
|
1844
|
-
if (
|
|
2207
|
+
const ed = join6(verDir, e);
|
|
2208
|
+
if (existsSync5(ed) && statSync3(ed).isDirectory())
|
|
1845
2209
|
roots.push(ed);
|
|
1846
2210
|
}
|
|
1847
2211
|
} else {
|
|
@@ -1854,37 +2218,37 @@ function vsRoots() {
|
|
|
1854
2218
|
function msvcVersionsDesc(msvc) {
|
|
1855
2219
|
let vers = [];
|
|
1856
2220
|
try {
|
|
1857
|
-
vers =
|
|
2221
|
+
vers = readdirSync4(msvc);
|
|
1858
2222
|
} catch {
|
|
1859
2223
|
return [];
|
|
1860
2224
|
}
|
|
1861
2225
|
return vers.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
|
|
1862
2226
|
}
|
|
1863
2227
|
function vsCl(root, arch) {
|
|
1864
|
-
const msvc =
|
|
1865
|
-
if (!
|
|
2228
|
+
const msvc = join6(root, "VC", "Tools", "MSVC");
|
|
2229
|
+
if (!existsSync5(msvc))
|
|
1866
2230
|
return null;
|
|
1867
2231
|
for (const v of msvcVersionsDesc(msvc)) {
|
|
1868
|
-
const p =
|
|
1869
|
-
if (
|
|
2232
|
+
const p = join6(msvc, v, "bin", `Host${arch}`, arch, "cl.exe");
|
|
2233
|
+
if (existsSync5(p))
|
|
1870
2234
|
return p;
|
|
1871
2235
|
}
|
|
1872
2236
|
return null;
|
|
1873
2237
|
}
|
|
1874
2238
|
function vsMsbuild(root) {
|
|
1875
|
-
const base =
|
|
1876
|
-
if (!
|
|
2239
|
+
const base = join6(root, "MSBuild");
|
|
2240
|
+
if (!existsSync5(base))
|
|
1877
2241
|
return null;
|
|
1878
|
-
const cands = [
|
|
2242
|
+
const cands = [join6(base, "Current", "Bin", "MSBuild.exe")];
|
|
1879
2243
|
try {
|
|
1880
|
-
for (const v of
|
|
1881
|
-
cands.push(
|
|
2244
|
+
for (const v of readdirSync4(base))
|
|
2245
|
+
cands.push(join6(base, v, "Bin", "MSBuild.exe"));
|
|
1882
2246
|
} catch {}
|
|
1883
|
-
return cands.find((c) =>
|
|
2247
|
+
return cands.find((c) => existsSync5(c)) || null;
|
|
1884
2248
|
}
|
|
1885
2249
|
function vsCMake(root) {
|
|
1886
|
-
const p =
|
|
1887
|
-
return
|
|
2250
|
+
const p = join6(root, "Common7", "IDE", "CommonExtensions", "Microsoft", "CMake", "CMake", "bin", "cmake.exe");
|
|
2251
|
+
return existsSync5(p) ? p : null;
|
|
1888
2252
|
}
|
|
1889
2253
|
function vsDotnet() {
|
|
1890
2254
|
return whichInDirs("dotnet", pathDirs())[0] || null;
|
|
@@ -1907,19 +2271,19 @@ function scanToolchain() {
|
|
|
1907
2271
|
}
|
|
1908
2272
|
function shortcutNames() {
|
|
1909
2273
|
const names = [];
|
|
1910
|
-
const stack = [...START_MENU.filter((d) =>
|
|
2274
|
+
const stack = [...START_MENU.filter((d) => existsSync5(d))];
|
|
1911
2275
|
while (stack.length) {
|
|
1912
2276
|
const dir = stack.pop();
|
|
1913
2277
|
let entries = [];
|
|
1914
2278
|
try {
|
|
1915
|
-
entries =
|
|
2279
|
+
entries = readdirSync4(dir);
|
|
1916
2280
|
} catch {
|
|
1917
2281
|
continue;
|
|
1918
2282
|
}
|
|
1919
2283
|
for (const e of entries) {
|
|
1920
|
-
const full =
|
|
2284
|
+
const full = join6(dir, e);
|
|
1921
2285
|
try {
|
|
1922
|
-
if (
|
|
2286
|
+
if (statSync3(full).isDirectory())
|
|
1923
2287
|
stack.push(full);
|
|
1924
2288
|
else if (e.toLowerCase().endsWith(".lnk") && /developer|native tools|vs 20/i.test(e))
|
|
1925
2289
|
names.push(e.replace(/\.lnk$/i, ""));
|
|
@@ -1937,8 +2301,8 @@ function inferKindArch(name) {
|
|
|
1937
2301
|
function entryRef(kind) {
|
|
1938
2302
|
const fn = kind === "powershell" ? "Launch-VsDevShell.ps1" : "VsDevCmd.bat";
|
|
1939
2303
|
for (const r of vsRoots()) {
|
|
1940
|
-
const p =
|
|
1941
|
-
if (
|
|
2304
|
+
const p = join6(r, "Common7", "Tools", fn);
|
|
2305
|
+
if (existsSync5(p))
|
|
1942
2306
|
return p;
|
|
1943
2307
|
}
|
|
1944
2308
|
return null;
|
|
@@ -1980,16 +2344,16 @@ register({
|
|
|
1980
2344
|
});
|
|
1981
2345
|
|
|
1982
2346
|
// src/detector/projects.ts
|
|
1983
|
-
import { readdirSync as
|
|
1984
|
-
import { join as
|
|
2347
|
+
import { readdirSync as readdirSync5, existsSync as existsSync6, statSync as statSync4 } from "node:fs";
|
|
2348
|
+
import { join as join7, basename as basename3, dirname as dirname2 } from "node:path";
|
|
1985
2349
|
function isVenv(dir) {
|
|
1986
|
-
if (!
|
|
2350
|
+
if (!existsSync6(join7(dir, "pyvenv.cfg")))
|
|
1987
2351
|
return false;
|
|
1988
2352
|
const child = IS_WIN_PYVENV(dir);
|
|
1989
2353
|
return child;
|
|
1990
2354
|
}
|
|
1991
2355
|
function IS_WIN_PYVENV(dir) {
|
|
1992
|
-
return
|
|
2356
|
+
return existsSync6(join7(dir, "Scripts", "activate")) || existsSync6(join7(dir, "Scripts", "activate.bat")) || existsSync6(join7(dir, "bin", "activate"));
|
|
1993
2357
|
}
|
|
1994
2358
|
function walk(root, maxDepth, out, seen) {
|
|
1995
2359
|
const stack = [[root, 0]];
|
|
@@ -1997,14 +2361,14 @@ function walk(root, maxDepth, out, seen) {
|
|
|
1997
2361
|
const [dir, depth] = stack.pop();
|
|
1998
2362
|
let entries = [];
|
|
1999
2363
|
try {
|
|
2000
|
-
entries =
|
|
2364
|
+
entries = readdirSync5(dir);
|
|
2001
2365
|
} catch {
|
|
2002
2366
|
continue;
|
|
2003
2367
|
}
|
|
2004
2368
|
for (const e of entries) {
|
|
2005
2369
|
if (e === "node_modules" || e === ".git" || e === "target" || e === "dist" || e === ".next" || e === "out") {
|
|
2006
2370
|
if (e === "node_modules") {
|
|
2007
|
-
const p =
|
|
2371
|
+
const p = join7(dir, e);
|
|
2008
2372
|
if (!seen.has(p.toLowerCase())) {
|
|
2009
2373
|
seen.add(p.toLowerCase());
|
|
2010
2374
|
out.push({ project: basename3(dir), type: "node_modules", path: p });
|
|
@@ -2012,10 +2376,10 @@ function walk(root, maxDepth, out, seen) {
|
|
|
2012
2376
|
}
|
|
2013
2377
|
continue;
|
|
2014
2378
|
}
|
|
2015
|
-
const full =
|
|
2379
|
+
const full = join7(dir, e);
|
|
2016
2380
|
let st;
|
|
2017
2381
|
try {
|
|
2018
|
-
st =
|
|
2382
|
+
st = statSync4(full);
|
|
2019
2383
|
} catch {
|
|
2020
2384
|
continue;
|
|
2021
2385
|
}
|
|
@@ -2040,7 +2404,7 @@ register({
|
|
|
2040
2404
|
const envs = [];
|
|
2041
2405
|
const seen = new Set;
|
|
2042
2406
|
for (const r of roots) {
|
|
2043
|
-
if (
|
|
2407
|
+
if (existsSync6(r))
|
|
2044
2408
|
walk(r, 4, envs, seen);
|
|
2045
2409
|
}
|
|
2046
2410
|
const checks = [];
|
|
@@ -2054,9 +2418,9 @@ register({
|
|
|
2054
2418
|
});
|
|
2055
2419
|
|
|
2056
2420
|
// src/cli.ts
|
|
2057
|
-
import { writeFileSync, mkdirSync, readFileSync, existsSync as
|
|
2058
|
-
import { homedir as
|
|
2059
|
-
import { join as
|
|
2421
|
+
import { writeFileSync, mkdirSync, readFileSync as readFileSync2, existsSync as existsSync7 } from "node:fs";
|
|
2422
|
+
import { homedir as homedir3 } from "node:os";
|
|
2423
|
+
import { join as join8 } from "node:path";
|
|
2060
2424
|
import { execSync } from "node:child_process";
|
|
2061
2425
|
if (process.platform === "win32") {
|
|
2062
2426
|
try {
|
|
@@ -2069,8 +2433,8 @@ if (process.platform === "win32") {
|
|
|
2069
2433
|
const oerr = console.error.bind(console);
|
|
2070
2434
|
console.error = (...a) => oerr(...a.map((x) => typeof x === "string" ? clean(x) : x));
|
|
2071
2435
|
}
|
|
2072
|
-
var
|
|
2073
|
-
var DEFAULT_MANIFEST =
|
|
2436
|
+
var ALL_SHELLS2 = ["current", "powershell", "gitbash", "wsl"];
|
|
2437
|
+
var DEFAULT_MANIFEST = join8(homedir3(), ".devmap", "devmap.json");
|
|
2074
2438
|
function emitText(text, withBom = false) {
|
|
2075
2439
|
const prefix = withBom && !process.stdout.isTTY ? "\uFEFF" : "";
|
|
2076
2440
|
process.stdout.write(prefix + text + `
|
|
@@ -2105,7 +2469,7 @@ function toShells(v) {
|
|
|
2105
2469
|
if (typeof v !== "string")
|
|
2106
2470
|
return ["current"];
|
|
2107
2471
|
const list = v.split(",").map((s) => s.trim());
|
|
2108
|
-
return list.includes("all") ?
|
|
2472
|
+
return list.includes("all") ? ALL_SHELLS2 : list.length ? list : ["current"];
|
|
2109
2473
|
}
|
|
2110
2474
|
function toSet(v) {
|
|
2111
2475
|
if (typeof v !== "string")
|
|
@@ -2119,10 +2483,10 @@ function toRoots(v) {
|
|
|
2119
2483
|
return arr.length ? arr : undefined;
|
|
2120
2484
|
}
|
|
2121
2485
|
function readManifestFile(path) {
|
|
2122
|
-
if (!
|
|
2486
|
+
if (!existsSync7(path))
|
|
2123
2487
|
throw new Error(`manifest file not found: ${path}`);
|
|
2124
2488
|
try {
|
|
2125
|
-
const raw =
|
|
2489
|
+
const raw = readFileSync2(path, "utf8").replace(/^\uFEFF/, "");
|
|
2126
2490
|
return JSON.parse(raw);
|
|
2127
2491
|
} catch (e) {
|
|
2128
2492
|
throw new Error(`manifest parse failed (${path}): ${e.message}`);
|
|
@@ -2131,7 +2495,7 @@ function readManifestFile(path) {
|
|
|
2131
2495
|
async function resolveManifest(explicitPath, shells, disabled, label, roots) {
|
|
2132
2496
|
if (explicitPath)
|
|
2133
2497
|
return { manifest: readManifestFile(explicitPath), source: explicitPath };
|
|
2134
|
-
if (
|
|
2498
|
+
if (existsSync7(DEFAULT_MANIFEST))
|
|
2135
2499
|
return { manifest: readManifestFile(DEFAULT_MANIFEST), source: DEFAULT_MANIFEST };
|
|
2136
2500
|
const manifest = await runScan(shells, disabled, { projectRoots: roots });
|
|
2137
2501
|
return { manifest, source: label + "(live scan)" };
|
|
@@ -2146,7 +2510,8 @@ async function cmdScan(opts, pos) {
|
|
|
2146
2510
|
checkOutdated: opts.outdated === true,
|
|
2147
2511
|
wslDeep: opts["wsl-deep"] === true,
|
|
2148
2512
|
auditDeps: opts["audit-deps"] === true,
|
|
2149
|
-
healthNetwork: opts["health-network"] === true
|
|
2513
|
+
healthNetwork: opts["health-network"] === true,
|
|
2514
|
+
scanSecrets: opts["scan-secrets"] === true
|
|
2150
2515
|
});
|
|
2151
2516
|
if (opts.json) {
|
|
2152
2517
|
emitText(JSON.stringify(manifest, null, 2), opts.bom === true);
|
|
@@ -2156,7 +2521,7 @@ async function cmdScan(opts, pos) {
|
|
|
2156
2521
|
renderPrettyFromManifest(manifest);
|
|
2157
2522
|
if (opts["no-manifest"] !== true) {
|
|
2158
2523
|
try {
|
|
2159
|
-
mkdirSync(
|
|
2524
|
+
mkdirSync(join8(DEFAULT_MANIFEST, ".."), { recursive: true });
|
|
2160
2525
|
writeFileSync(DEFAULT_MANIFEST, JSON.stringify(manifest, null, 2));
|
|
2161
2526
|
console.log(` ${"\x1B[2m"}manifest → ${DEFAULT_MANIFEST}${"\x1B[0m"}`);
|
|
2162
2527
|
} catch {}
|
|
@@ -2248,7 +2613,7 @@ async function cmdDevdiff(opts, pos) {
|
|
|
2248
2613
|
const before = await resolveManifest(opts.manifest, shells, disabled, "devdiff-before", toRoots(opts["scan-root"]));
|
|
2249
2614
|
A = before.manifest;
|
|
2250
2615
|
labelA = before.source;
|
|
2251
|
-
B = await runScan(shells, disabled, { projectRoots: toRoots(opts["scan-root"]) });
|
|
2616
|
+
B = await runScan(shells, disabled, { projectRoots: toRoots(opts["scan-root"]), scanSecrets: opts["scan-secrets"] === true });
|
|
2252
2617
|
labelB = "current live scan";
|
|
2253
2618
|
}
|
|
2254
2619
|
const diff = diffManifests(A, B);
|
|
@@ -2515,9 +2880,11 @@ scan options:
|
|
|
2515
2880
|
--wsl-deep probe inside stopped WSL distros (default probes only running ones to avoid waking distros)
|
|
2516
2881
|
--ci CI gate: clean → exit 0; any warn → exit 2; any error → exit 1
|
|
2517
2882
|
--audit-deps dependency CVE audit via OSV-Scanner/pip-audit/govulncheck/Trivy (opt-in, uses network)
|
|
2883
|
+
--scan-secrets scan a curated allowlist of HOME/project credential files for hardcoded secrets (opt-in, offline)
|
|
2518
2884
|
--health-network run network health probes too (npm doctor / rustup check; off by default to stay offline-fast)
|
|
2519
2885
|
--quiet suppress scan progress feedback (for scripts/CI)
|
|
2520
2886
|
--bom prepend UTF-8 BOM for viewing in Notepad etc. (only on redirect/pipe)
|
|
2887
|
+
--mcp run as an MCP server (stdio JSON-RPC) for AI IDE agents (Cursor/Cline/Claude Desktop)
|
|
2521
2888
|
--version / --help
|
|
2522
2889
|
|
|
2523
2890
|
Phase 2 environment layer (covered by this scan):
|
|
@@ -2555,6 +2922,10 @@ async function main() {
|
|
|
2555
2922
|
console.log(VERSION);
|
|
2556
2923
|
process.exit(0);
|
|
2557
2924
|
}
|
|
2925
|
+
if (opts.mcp) {
|
|
2926
|
+
await startMcpServer();
|
|
2927
|
+
process.exit(0);
|
|
2928
|
+
}
|
|
2558
2929
|
const sub = pos[0] || "scan";
|
|
2559
2930
|
let code = 0;
|
|
2560
2931
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devmap
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9
|
|
4
4
|
Summary: Map your machine's dev environment and surface issues (PATH conflicts, ghost binaries, multi-version collisions). fastfetch-for-dev-tools. This PyPI package ships the real devmap CLI (a bundled JavaScript app launched via Node.js or Bun), so `pip install devmap && devmap` works standalone.
|
|
5
5
|
Author: devmap contributors
|
|
6
6
|
License: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devmap"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.9"
|
|
8
8
|
description = "Map your machine's dev environment and surface issues (PATH conflicts, ghost binaries, multi-version collisions). fastfetch-for-dev-tools. This PyPI package ships the real devmap CLI (a bundled JavaScript app launched via Node.js or Bun), so `pip install devmap && devmap` works standalone."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|