bosun 0.41.2 → 0.41.3
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/.env.example +1 -1
- package/agent/agent-prompt-catalog.mjs +971 -0
- package/agent/agent-prompts.mjs +2 -970
- package/agent/agent-supervisor.mjs +6 -3
- package/agent/autofix-git.mjs +33 -0
- package/agent/autofix-prompts.mjs +151 -0
- package/agent/autofix.mjs +11 -175
- package/agent/bosun-skills.mjs +3 -2
- package/bosun.config.example.json +17 -0
- package/bosun.schema.json +87 -188
- package/cli.mjs +34 -1
- package/config/config-doctor.mjs +5 -250
- package/config/config-file-names.mjs +5 -0
- package/config/config.mjs +89 -493
- package/config/executor-config.mjs +493 -0
- package/config/repo-root.mjs +1 -2
- package/config/workspace-health.mjs +242 -0
- package/git/git-safety.mjs +15 -0
- package/github/github-oauth-portal.mjs +46 -0
- package/infra/library-manager-utils.mjs +22 -0
- package/infra/library-manager-well-known-sources.mjs +578 -0
- package/infra/library-manager.mjs +512 -1030
- package/infra/monitor.mjs +28 -9
- package/infra/session-tracker.mjs +10 -7
- package/kanban/kanban-adapter.mjs +17 -1
- package/lib/codebase-audit-manifests.mjs +117 -0
- package/lib/codebase-audit.mjs +18 -115
- package/package.json +18 -3
- package/server/ui-server.mjs +1194 -79
- package/shell/codex-config-file.mjs +178 -0
- package/shell/codex-config.mjs +538 -575
- package/task/task-cli.mjs +54 -3
- package/task/task-executor.mjs +143 -13
- package/task/task-store.mjs +409 -1
- package/telegram/telegram-bot.mjs +127 -0
- package/tools/apply-pr-suggestions.mjs +401 -0
- package/tools/syntax-check.mjs +21 -9
- package/ui/app.js +3 -14
- package/ui/components/kanban-board.js +227 -4
- package/ui/components/session-list.js +85 -5
- package/ui/demo-defaults.js +334 -80
- package/ui/demo.html +155 -0
- package/ui/modules/session-api.js +96 -0
- package/ui/modules/settings-schema.js +1 -2
- package/ui/modules/state.js +21 -3
- package/ui/setup.html +4 -5
- package/ui/styles/components.css +58 -4
- package/ui/tabs/agents.js +12 -15
- package/ui/tabs/control.js +1 -0
- package/ui/tabs/library.js +484 -22
- package/ui/tabs/manual-flows.js +105 -29
- package/ui/tabs/tasks.js +785 -140
- package/ui/tabs/telemetry.js +129 -11
- package/ui/tabs/workflow-canvas-utils.mjs +130 -0
- package/ui/tabs/workflows.js +293 -23
- package/voice/voice-tool-definitions.mjs +757 -0
- package/voice/voice-tools.mjs +34 -778
- package/workflow/manual-flow-audit.mjs +165 -0
- package/workflow/manual-flows.mjs +164 -259
- package/workflow/workflow-engine.mjs +147 -58
- package/workflow/workflow-nodes/definitions.mjs +1207 -0
- package/workflow/workflow-nodes/transforms.mjs +612 -0
- package/workflow/workflow-nodes.mjs +304 -52
- package/workflow/workflow-templates.mjs +313 -191
- package/workflow-templates/_helpers.mjs +154 -0
- package/workflow-templates/agents.mjs +61 -4
- package/workflow-templates/code-quality.mjs +7 -7
- package/workflow-templates/github.mjs +20 -10
- package/workflow-templates/task-batch.mjs +20 -9
- package/workflow-templates/task-lifecycle.mjs +31 -6
- package/workspace/worktree-manager.mjs +277 -3
|
@@ -20,6 +20,14 @@ import { resolve, basename, relative, extname, sep } from "node:path";
|
|
|
20
20
|
import { execSync, spawnSync } from "node:child_process";
|
|
21
21
|
import { homedir } from "node:os";
|
|
22
22
|
import { getAgentToolConfig, getEffectiveTools } from "../agent/agent-tool-config.mjs";
|
|
23
|
+
import { nowISO, toStringArray, uniqueStrings } from "./library-manager-utils.mjs";
|
|
24
|
+
import {
|
|
25
|
+
WELL_KNOWN_AGENT_SOURCES,
|
|
26
|
+
computeWellKnownSourceTrust,
|
|
27
|
+
listWellKnownAgentSources,
|
|
28
|
+
clearWellKnownAgentSourceProbeCache,
|
|
29
|
+
probeWellKnownAgentSources,
|
|
30
|
+
} from "./library-manager-well-known-sources.mjs";
|
|
23
31
|
|
|
24
32
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
25
33
|
|
|
@@ -35,7 +43,6 @@ export const SKILL_ENTRY_INDEX = "skills.json";
|
|
|
35
43
|
|
|
36
44
|
const agentProfileIndexCache = new Map();
|
|
37
45
|
const skillEntryIndexCache = new Map();
|
|
38
|
-
const wellKnownSourceProbeCache = new Map();
|
|
39
46
|
const repoContextCache = new Map();
|
|
40
47
|
|
|
41
48
|
const REPO_CONTEXT_TTL_MS = 120_000;
|
|
@@ -365,10 +372,6 @@ function slugify(name) {
|
|
|
365
372
|
.replace(/^-|-$/g, "");
|
|
366
373
|
}
|
|
367
374
|
|
|
368
|
-
function nowISO() {
|
|
369
|
-
return new Date().toISOString();
|
|
370
|
-
}
|
|
371
|
-
|
|
372
375
|
function isSafeGitRefName(value) {
|
|
373
376
|
const ref = String(value || "").trim();
|
|
374
377
|
if (!ref) return false;
|
|
@@ -395,25 +398,6 @@ function isSafeGitRepositorySource(value) {
|
|
|
395
398
|
}
|
|
396
399
|
}
|
|
397
400
|
|
|
398
|
-
function toStringArray(input) {
|
|
399
|
-
if (!Array.isArray(input)) return [];
|
|
400
|
-
return input.map((item) => String(item || '').trim()).filter(Boolean);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function uniqueStrings(values = []) {
|
|
404
|
-
const out = [];
|
|
405
|
-
const seen = new Set();
|
|
406
|
-
for (const raw of values) {
|
|
407
|
-
const value = String(raw || '').trim();
|
|
408
|
-
if (!value) continue;
|
|
409
|
-
const key = value.toLowerCase();
|
|
410
|
-
if (seen.has(key)) continue;
|
|
411
|
-
seen.add(key);
|
|
412
|
-
out.push(value);
|
|
413
|
-
}
|
|
414
|
-
return out;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
401
|
function keywordTokens(value, { minLength = 3 } = {}) {
|
|
418
402
|
return uniqueStrings(
|
|
419
403
|
String(value || '')
|
|
@@ -1393,8 +1377,23 @@ export function matchAgentProfiles(rootDir, criteria = {}, opts = {}) {
|
|
|
1393
1377
|
...(fileSignals ? [...fileSignals.fileDomains, ...fileSignals.fileLanguages] : []),
|
|
1394
1378
|
].map((d) => d.toLowerCase()));
|
|
1395
1379
|
|
|
1396
|
-
// Max theoretical score: 10 + 6 + 6 + 3 + 8 + 6 + 4 =
|
|
1397
|
-
const MAX_THEORETICAL_SCORE =
|
|
1380
|
+
// Max theoretical score: 10 + 6 + 6 + 3 + 8 + 6 + 4 + 8 + 5 = 56
|
|
1381
|
+
const MAX_THEORETICAL_SCORE = 56;
|
|
1382
|
+
|
|
1383
|
+
// Task-type detection (used by Signal 9)
|
|
1384
|
+
const TASK_TYPE_PATTERNS = {
|
|
1385
|
+
tdd: /\b(tdd|test.driven|write.*tests?|spec)\b/i,
|
|
1386
|
+
test: /\b(test|testing|coverage|jest|vitest|pytest|spec)\b/i,
|
|
1387
|
+
review: /\b(review|code.review|pr.review|audit|inspect)\b/i,
|
|
1388
|
+
docs: /\b(doc|documentation|readme|changelog|api.doc)\b/i,
|
|
1389
|
+
implementation: /\b(implement|build|create|develop|feat|feature|add)\b/i,
|
|
1390
|
+
fix: /\b(fix|bug|patch|hotfix|repair|resolve)\b/i,
|
|
1391
|
+
refactor: /\b(refactor|cleanup|reorganize|restructure|simplify)\b/i,
|
|
1392
|
+
devops: /\b(ci|cd|deploy|pipeline|docker|k8s|infra|terraform)\b/i,
|
|
1393
|
+
};
|
|
1394
|
+
const detectedTaskTypes = Object.entries(TASK_TYPE_PATTERNS)
|
|
1395
|
+
.filter(([, re]) => re.test(textBlob))
|
|
1396
|
+
.map(([type]) => type);
|
|
1398
1397
|
|
|
1399
1398
|
const candidates = [];
|
|
1400
1399
|
for (const entry of profiles) {
|
|
@@ -1406,74 +1405,120 @@ export function matchAgentProfiles(rootDir, criteria = {}, opts = {}) {
|
|
|
1406
1405
|
|
|
1407
1406
|
let score = 0;
|
|
1408
1407
|
const reasons = [];
|
|
1408
|
+
const breakdown = {};
|
|
1409
1409
|
|
|
1410
1410
|
// ── Signal 1: titlePattern regex match → +10 (precompiled) ──
|
|
1411
1411
|
const patterns = toStringArray(profile.titlePatterns);
|
|
1412
|
+
let titlePatternScore = 0;
|
|
1412
1413
|
for (const pattern of patterns) {
|
|
1413
1414
|
const re = getCompiledRegex(pattern);
|
|
1414
1415
|
if (re && re.test(textBlob)) {
|
|
1415
|
-
|
|
1416
|
+
titlePatternScore = 10;
|
|
1416
1417
|
reasons.push(`pattern:${pattern}`);
|
|
1417
1418
|
break;
|
|
1418
1419
|
}
|
|
1419
1420
|
}
|
|
1421
|
+
score += titlePatternScore;
|
|
1422
|
+
breakdown.titlePattern = titlePatternScore;
|
|
1420
1423
|
|
|
1421
1424
|
// ── Signal 2: conventional-commit scope match → +6 ──
|
|
1422
1425
|
const scopes = toStringArray(profile.scopes).map((s) => s.toLowerCase());
|
|
1426
|
+
let scopeScore = 0;
|
|
1423
1427
|
if (taskScope && scopes.includes(taskScope)) {
|
|
1424
|
-
|
|
1428
|
+
scopeScore = 6;
|
|
1425
1429
|
reasons.push(`scope:${taskScope}`);
|
|
1426
1430
|
}
|
|
1431
|
+
score += scopeScore;
|
|
1432
|
+
breakdown.scope = scopeScore;
|
|
1427
1433
|
|
|
1428
1434
|
// ── Signal 3: tag overlap → up to +6 ──
|
|
1429
1435
|
const profileTags = uniqueStrings([...(entry.tags || []), ...toStringArray(profile.tags)]).map((v) => v.toLowerCase());
|
|
1430
1436
|
const tagHits = criteriaTags.filter((tag) => profileTags.includes(tag));
|
|
1437
|
+
let tagScore = 0;
|
|
1431
1438
|
if (tagHits.length > 0) {
|
|
1432
|
-
|
|
1433
|
-
score += tagScore;
|
|
1439
|
+
tagScore = Math.min(6, tagHits.length * 2);
|
|
1434
1440
|
reasons.push(`tags:${tagHits.slice(0, 4).join(",")}`);
|
|
1435
1441
|
}
|
|
1442
|
+
score += tagScore;
|
|
1443
|
+
breakdown.tags = tagScore;
|
|
1436
1444
|
|
|
1437
1445
|
// ── Signal 4: voice-type hint → +3 ──
|
|
1446
|
+
let voiceScore = 0;
|
|
1438
1447
|
if (profileType === "voice") {
|
|
1439
1448
|
const voiceHint = /\bvoice\b|\bcall\b|\brealtime\b/.test(textBlobLower);
|
|
1440
1449
|
if (voiceHint) {
|
|
1441
|
-
|
|
1450
|
+
voiceScore = 3;
|
|
1442
1451
|
reasons.push("voice-hint");
|
|
1443
1452
|
}
|
|
1444
1453
|
}
|
|
1454
|
+
score += voiceScore;
|
|
1455
|
+
breakdown.voice = voiceScore;
|
|
1445
1456
|
|
|
1446
1457
|
// ── Signal 5: changed-file path match → up to +8 ──
|
|
1447
1458
|
const scopeHitsFromPaths = scopes.filter((scope) =>
|
|
1448
1459
|
changedHints.includes(scope) || changedFiles.some((f) => String(f).toLowerCase().includes(`/${scope}/`) || String(f).toLowerCase().includes(`\\${scope}\\`)),
|
|
1449
1460
|
);
|
|
1461
|
+
let pathScore = 0;
|
|
1450
1462
|
if (scopeHitsFromPaths.length > 0) {
|
|
1451
|
-
|
|
1452
|
-
score += fileScore;
|
|
1463
|
+
pathScore = Math.min(8, scopeHitsFromPaths.length * 2);
|
|
1453
1464
|
reasons.push(`paths:${scopeHitsFromPaths.slice(0, 4).join(",")}`);
|
|
1454
1465
|
}
|
|
1466
|
+
score += pathScore;
|
|
1467
|
+
breakdown.paths = pathScore;
|
|
1455
1468
|
|
|
1456
1469
|
// ── Signal 6: repo-context domain match → up to +6 ──
|
|
1470
|
+
let domainScore = 0;
|
|
1457
1471
|
if (contextDomains.size > 0) {
|
|
1458
1472
|
const profileAllTags = new Set([...profileTags, ...scopes]);
|
|
1459
1473
|
const domainHits = [...contextDomains].filter((d) => profileAllTags.has(d));
|
|
1460
1474
|
if (domainHits.length > 0) {
|
|
1461
|
-
|
|
1462
|
-
score += domainScore;
|
|
1475
|
+
domainScore = Math.min(6, domainHits.length * 2);
|
|
1463
1476
|
reasons.push(`repo-ctx:${domainHits.slice(0, 3).join(",")}`);
|
|
1464
1477
|
}
|
|
1465
1478
|
}
|
|
1479
|
+
score += domainScore;
|
|
1480
|
+
breakdown.repoCtx = domainScore;
|
|
1466
1481
|
|
|
1467
1482
|
// ── Signal 7: file-type domain match → up to +4 ──
|
|
1483
|
+
let fileTypeScore = 0;
|
|
1468
1484
|
if (fileSignals && fileSignals.fileDomains.length > 0) {
|
|
1469
1485
|
const profileAllTags = new Set([...profileTags, ...scopes]);
|
|
1470
1486
|
const fileHits = fileSignals.fileDomains.filter((d) => profileAllTags.has(d));
|
|
1471
1487
|
if (fileHits.length > 0) {
|
|
1472
|
-
|
|
1473
|
-
score += fileTypeScore;
|
|
1488
|
+
fileTypeScore = Math.min(4, fileHits.length);
|
|
1474
1489
|
reasons.push(`file-type:${fileHits.slice(0, 3).join(",")}`);
|
|
1475
1490
|
}
|
|
1476
1491
|
}
|
|
1492
|
+
score += fileTypeScore;
|
|
1493
|
+
breakdown.fileType = fileTypeScore;
|
|
1494
|
+
|
|
1495
|
+
// ── Signal 8: description keyword match → up to +8 ──
|
|
1496
|
+
const profileDesc = String(profile.description || "").toLowerCase();
|
|
1497
|
+
let descScore = 0;
|
|
1498
|
+
if (profileDesc.length > 10) {
|
|
1499
|
+
const descTokens = keywordTokens(profileDesc, { minLength: 4 });
|
|
1500
|
+
const titleTokens = keywordTokens(textBlob, { minLength: 4 });
|
|
1501
|
+
const descHits = descTokens.filter((t) => titleTokens.includes(t));
|
|
1502
|
+
if (descHits.length > 0) {
|
|
1503
|
+
descScore = Math.min(8, descHits.length * 2);
|
|
1504
|
+
reasons.push(`desc:${descHits.slice(0, 4).join(",")}`);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
score += descScore;
|
|
1508
|
+
breakdown.descMatch = descScore;
|
|
1509
|
+
|
|
1510
|
+
// ── Signal 9: task-type hint → +5 ──
|
|
1511
|
+
let taskTypeScore = 0;
|
|
1512
|
+
if (detectedTaskTypes.length > 0) {
|
|
1513
|
+
const profileAllTags = new Set([...profileTags, ...scopes]);
|
|
1514
|
+
const taskTypeHits = detectedTaskTypes.filter((t) => profileAllTags.has(t));
|
|
1515
|
+
if (taskTypeHits.length > 0) {
|
|
1516
|
+
taskTypeScore = 5;
|
|
1517
|
+
reasons.push(`task-type:${taskTypeHits.join(",")}`);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
score += taskTypeScore;
|
|
1521
|
+
breakdown.taskType = taskTypeScore;
|
|
1477
1522
|
|
|
1478
1523
|
if (score <= 0) continue;
|
|
1479
1524
|
|
|
@@ -1484,6 +1529,7 @@ export function matchAgentProfiles(rootDir, criteria = {}, opts = {}) {
|
|
|
1484
1529
|
score,
|
|
1485
1530
|
confidence,
|
|
1486
1531
|
reasons,
|
|
1532
|
+
breakdown,
|
|
1487
1533
|
matchedScope: taskScope,
|
|
1488
1534
|
});
|
|
1489
1535
|
}
|
|
@@ -1519,6 +1565,7 @@ export function matchAgentProfiles(rootDir, criteria = {}, opts = {}) {
|
|
|
1519
1565
|
description,
|
|
1520
1566
|
requestedAgentType,
|
|
1521
1567
|
taskScope,
|
|
1568
|
+
detectedTaskTypes,
|
|
1522
1569
|
changedFilesCount: changedFiles.length,
|
|
1523
1570
|
repoContext: repoCtx || null,
|
|
1524
1571
|
fileSignals: fileSignals || null,
|
|
@@ -1538,581 +1585,13 @@ export function matchAgentProfile(rootDir, taskTitle) {
|
|
|
1538
1585
|
return result.best || null;
|
|
1539
1586
|
}
|
|
1540
1587
|
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
repoUrl: "https://github.com/microsoft/skills.git",
|
|
1549
|
-
defaultBranch: "main",
|
|
1550
|
-
description: "Microsoft-maintained backend, frontend, planner, infrastructure, and scaffolder agents with hundreds of Azure SDK skills.",
|
|
1551
|
-
owner: "microsoft",
|
|
1552
|
-
trustTier: "official",
|
|
1553
|
-
importCoverage: "high",
|
|
1554
|
-
estimatedPlugins: 180,
|
|
1555
|
-
focuses: ["backend", "frontend", "planner", "infra", "scaffolding", "azure"],
|
|
1556
|
-
},
|
|
1557
|
-
{
|
|
1558
|
-
id: "microsoft-hve-core",
|
|
1559
|
-
name: "Microsoft HVE Core",
|
|
1560
|
-
repoUrl: "https://github.com/microsoft/hve-core.git",
|
|
1561
|
-
defaultBranch: "main",
|
|
1562
|
-
description: "Core HVE agent library with domain and plugin agent templates and experimental skills.",
|
|
1563
|
-
owner: "microsoft",
|
|
1564
|
-
trustTier: "official",
|
|
1565
|
-
importCoverage: "high",
|
|
1566
|
-
estimatedPlugins: 60,
|
|
1567
|
-
focuses: ["core", "plugins", "platform"],
|
|
1568
|
-
},
|
|
1569
|
-
{
|
|
1570
|
-
id: "microsoft-vscode",
|
|
1571
|
-
name: "Microsoft VS Code",
|
|
1572
|
-
repoUrl: "https://github.com/microsoft/vscode.git",
|
|
1573
|
-
defaultBranch: "main",
|
|
1574
|
-
description: "VS Code editor skills for hygiene, testing, and extension development workflows.",
|
|
1575
|
-
owner: "microsoft",
|
|
1576
|
-
trustTier: "official",
|
|
1577
|
-
importCoverage: "medium",
|
|
1578
|
-
estimatedPlugins: 15,
|
|
1579
|
-
focuses: ["vscode", "editor", "extensions", "testing"],
|
|
1580
|
-
},
|
|
1581
|
-
{
|
|
1582
|
-
id: "microsoft-powertoys",
|
|
1583
|
-
name: "Microsoft PowerToys",
|
|
1584
|
-
repoUrl: "https://github.com/microsoft/PowerToys.git",
|
|
1585
|
-
defaultBranch: "main",
|
|
1586
|
-
description: "PowerToys development skills for Windows utility and plugin engineering.",
|
|
1587
|
-
owner: "microsoft",
|
|
1588
|
-
trustTier: "official",
|
|
1589
|
-
importCoverage: "medium",
|
|
1590
|
-
estimatedPlugins: 10,
|
|
1591
|
-
focuses: ["windows", "utilities", "c-sharp", "plugins"],
|
|
1592
|
-
},
|
|
1593
|
-
{
|
|
1594
|
-
id: "microsoft-typespec",
|
|
1595
|
-
name: "Microsoft TypeSpec",
|
|
1596
|
-
repoUrl: "https://github.com/microsoft/typespec.git",
|
|
1597
|
-
defaultBranch: "main",
|
|
1598
|
-
description: "TypeSpec API definition language skills for code generation and API design workflows.",
|
|
1599
|
-
owner: "microsoft",
|
|
1600
|
-
trustTier: "official",
|
|
1601
|
-
importCoverage: "medium",
|
|
1602
|
-
estimatedPlugins: 20,
|
|
1603
|
-
focuses: ["api", "code-generation", "typescript", "openapi"],
|
|
1604
|
-
},
|
|
1605
|
-
{
|
|
1606
|
-
id: "microsoft-copilot-for-azure",
|
|
1607
|
-
name: "GitHub Copilot for Azure",
|
|
1608
|
-
repoUrl: "https://github.com/microsoft/GitHub-Copilot-for-Azure.git",
|
|
1609
|
-
defaultBranch: "main",
|
|
1610
|
-
description: "Azure-focused Copilot skills for cloud infrastructure, deployment, and resource management.",
|
|
1611
|
-
owner: "microsoft",
|
|
1612
|
-
trustTier: "official",
|
|
1613
|
-
importCoverage: "high",
|
|
1614
|
-
estimatedPlugins: 45,
|
|
1615
|
-
focuses: ["azure", "cloud", "infrastructure", "deployment"],
|
|
1616
|
-
},
|
|
1617
|
-
{
|
|
1618
|
-
id: "microsoft-vscode-python-environments",
|
|
1619
|
-
name: "Microsoft VS Code Python Environments",
|
|
1620
|
-
repoUrl: "https://github.com/microsoft/vscode-python-environments.git",
|
|
1621
|
-
defaultBranch: "main",
|
|
1622
|
-
description: "Maintainer, reviewer, and documentation agents for a production VS Code extension.",
|
|
1623
|
-
owner: "microsoft",
|
|
1624
|
-
trustTier: "official",
|
|
1625
|
-
importCoverage: "medium",
|
|
1626
|
-
estimatedPlugins: 8,
|
|
1627
|
-
focuses: ["vscode", "python", "extension", "maintainer"],
|
|
1628
|
-
},
|
|
1629
|
-
{
|
|
1630
|
-
id: "microsoft-vscode-docs",
|
|
1631
|
-
name: "Microsoft VS Code Documentation",
|
|
1632
|
-
repoUrl: "https://github.com/microsoft/vscode-docs.git",
|
|
1633
|
-
defaultBranch: "main",
|
|
1634
|
-
description: "Skills for VS Code documentation authoring, editing, and review workflows.",
|
|
1635
|
-
owner: "microsoft",
|
|
1636
|
-
trustTier: "official",
|
|
1637
|
-
importCoverage: "medium",
|
|
1638
|
-
estimatedPlugins: 12,
|
|
1639
|
-
focuses: ["documentation", "vscode", "markdown", "authoring"],
|
|
1640
|
-
},
|
|
1641
|
-
{
|
|
1642
|
-
id: "microsoft-windowsappsdk",
|
|
1643
|
-
name: "Microsoft Windows App SDK",
|
|
1644
|
-
repoUrl: "https://github.com/microsoft/WindowsAppSDK.git",
|
|
1645
|
-
defaultBranch: "main",
|
|
1646
|
-
description: "Windows App SDK skills for WinUI and Windows platform development.",
|
|
1647
|
-
owner: "microsoft",
|
|
1648
|
-
trustTier: "official",
|
|
1649
|
-
importCoverage: "medium",
|
|
1650
|
-
estimatedPlugins: 8,
|
|
1651
|
-
focuses: ["windows", "winui", "sdk", "desktop"],
|
|
1652
|
-
},
|
|
1653
|
-
{
|
|
1654
|
-
id: "microsoft-vscode-java-pack",
|
|
1655
|
-
name: "Microsoft VS Code Java Pack",
|
|
1656
|
-
repoUrl: "https://github.com/microsoft/vscode-java-pack.git",
|
|
1657
|
-
defaultBranch: "main",
|
|
1658
|
-
description: "Java development skills for VS Code including debugging, testing, and project management.",
|
|
1659
|
-
owner: "microsoft",
|
|
1660
|
-
trustTier: "official",
|
|
1661
|
-
importCoverage: "medium",
|
|
1662
|
-
estimatedPlugins: 10,
|
|
1663
|
-
focuses: ["java", "vscode", "debugging", "testing"],
|
|
1664
|
-
},
|
|
1665
|
-
{
|
|
1666
|
-
id: "microsoft-duroxide",
|
|
1667
|
-
name: "Microsoft Duroxide",
|
|
1668
|
-
repoUrl: "https://github.com/microsoft/duroxide.git",
|
|
1669
|
-
defaultBranch: "main",
|
|
1670
|
-
description: "Durable Functions in Rust — skills for building resilient serverless workflows.",
|
|
1671
|
-
owner: "microsoft",
|
|
1672
|
-
trustTier: "official",
|
|
1673
|
-
importCoverage: "medium",
|
|
1674
|
-
estimatedPlugins: 6,
|
|
1675
|
-
focuses: ["rust", "serverless", "durable-functions", "workflows"],
|
|
1676
|
-
},
|
|
1677
|
-
{
|
|
1678
|
-
id: "microsoft-ebpf-for-windows",
|
|
1679
|
-
name: "Microsoft eBPF for Windows",
|
|
1680
|
-
repoUrl: "https://github.com/microsoft/ebpf-for-windows.git",
|
|
1681
|
-
defaultBranch: "main",
|
|
1682
|
-
description: "eBPF development skills for Windows kernel and networking instrumentation.",
|
|
1683
|
-
owner: "microsoft",
|
|
1684
|
-
trustTier: "official",
|
|
1685
|
-
importCoverage: "medium",
|
|
1686
|
-
estimatedPlugins: 6,
|
|
1687
|
-
focuses: ["ebpf", "windows", "kernel", "networking"],
|
|
1688
|
-
},
|
|
1689
|
-
// ── GitHub — Official ─────────────────────────────────────────────────────
|
|
1690
|
-
{
|
|
1691
|
-
id: "github-copilot-sdk",
|
|
1692
|
-
name: "GitHub Copilot SDK",
|
|
1693
|
-
repoUrl: "https://github.com/github/copilot-sdk.git",
|
|
1694
|
-
defaultBranch: "main",
|
|
1695
|
-
description: "Official GitHub workflow-authoring and docs-maintenance agents for Copilot SDK projects.",
|
|
1696
|
-
owner: "github",
|
|
1697
|
-
trustTier: "official",
|
|
1698
|
-
importCoverage: "medium",
|
|
1699
|
-
estimatedPlugins: 10,
|
|
1700
|
-
focuses: ["copilot", "workflow", "docs"],
|
|
1701
|
-
},
|
|
1702
|
-
{
|
|
1703
|
-
id: "github-desktop",
|
|
1704
|
-
name: "GitHub Desktop",
|
|
1705
|
-
repoUrl: "https://github.com/desktop/desktop.git",
|
|
1706
|
-
defaultBranch: "development",
|
|
1707
|
-
description: "GitHub Desktop app agent profiles for Electron, TypeScript, and Git workflow development.",
|
|
1708
|
-
owner: "desktop",
|
|
1709
|
-
trustTier: "official",
|
|
1710
|
-
importCoverage: "medium",
|
|
1711
|
-
estimatedPlugins: 10,
|
|
1712
|
-
focuses: ["electron", "typescript", "git", "desktop"],
|
|
1713
|
-
},
|
|
1714
|
-
// ── Azure — Official ──────────────────────────────────────────────────────
|
|
1715
|
-
{
|
|
1716
|
-
id: "azure-sdk-for-js",
|
|
1717
|
-
name: "Azure SDK for JavaScript",
|
|
1718
|
-
repoUrl: "https://github.com/Azure/azure-sdk-for-js.git",
|
|
1719
|
-
defaultBranch: "main",
|
|
1720
|
-
description: "Azure JavaScript SDK repo with agentic workflow authoring guidance and prompts.",
|
|
1721
|
-
owner: "azure",
|
|
1722
|
-
trustTier: "official",
|
|
1723
|
-
importCoverage: "medium",
|
|
1724
|
-
estimatedPlugins: 15,
|
|
1725
|
-
focuses: ["azure", "javascript", "sdk", "workflow"],
|
|
1726
|
-
},
|
|
1727
|
-
// ── Community — Verified ──────────────────────────────────────────────────
|
|
1728
|
-
{
|
|
1729
|
-
id: "mastra-ai-mastra",
|
|
1730
|
-
name: "Mastra AI Framework",
|
|
1731
|
-
repoUrl: "https://github.com/mastra-ai/mastra.git",
|
|
1732
|
-
defaultBranch: "main",
|
|
1733
|
-
description: "AI agent framework with extensive prompt templates for issue tracking, code review, and workflow automation.",
|
|
1734
|
-
owner: "mastra-ai",
|
|
1735
|
-
trustTier: "community",
|
|
1736
|
-
importCoverage: "high",
|
|
1737
|
-
estimatedPlugins: 40,
|
|
1738
|
-
focuses: ["ai", "agents", "prompts", "automation"],
|
|
1739
|
-
},
|
|
1740
|
-
{
|
|
1741
|
-
id: "z3prover-z3",
|
|
1742
|
-
name: "Z3 Theorem Prover",
|
|
1743
|
-
repoUrl: "https://github.com/Z3Prover/z3.git",
|
|
1744
|
-
defaultBranch: "master",
|
|
1745
|
-
description: "Z3 SMT solver agent profiles for formal verification and constraint solving workflows.",
|
|
1746
|
-
owner: "Z3Prover",
|
|
1747
|
-
trustTier: "community",
|
|
1748
|
-
importCoverage: "low",
|
|
1749
|
-
estimatedPlugins: 3,
|
|
1750
|
-
focuses: ["formal-verification", "smt", "solver", "c++"],
|
|
1751
|
-
},
|
|
1752
|
-
{
|
|
1753
|
-
id: "likec4-likec4",
|
|
1754
|
-
name: "LikeC4",
|
|
1755
|
-
repoUrl: "https://github.com/likec4/likec4.git",
|
|
1756
|
-
defaultBranch: "main",
|
|
1757
|
-
description: "Architecture-as-code tool with agents for diagram generation and architecture documentation.",
|
|
1758
|
-
owner: "likec4",
|
|
1759
|
-
trustTier: "community",
|
|
1760
|
-
importCoverage: "medium",
|
|
1761
|
-
estimatedPlugins: 12,
|
|
1762
|
-
focuses: ["architecture", "diagrams", "documentation", "c4"],
|
|
1763
|
-
},
|
|
1764
|
-
{
|
|
1765
|
-
id: "canonical-copilot-collections",
|
|
1766
|
-
name: "Canonical Copilot Collections",
|
|
1767
|
-
repoUrl: "https://github.com/canonical/copilot-collections.git",
|
|
1768
|
-
defaultBranch: "main",
|
|
1769
|
-
description: "Canonical's curated collection of Copilot agent definitions for Ubuntu and open-source development.",
|
|
1770
|
-
owner: "canonical",
|
|
1771
|
-
trustTier: "community",
|
|
1772
|
-
importCoverage: "high",
|
|
1773
|
-
estimatedPlugins: 50,
|
|
1774
|
-
focuses: ["ubuntu", "linux", "open-source", "devops"],
|
|
1775
|
-
},
|
|
1776
|
-
{
|
|
1777
|
-
id: "playwright-mcp-prompts",
|
|
1778
|
-
name: "Playwright MCP Prompts",
|
|
1779
|
-
repoUrl: "https://github.com/debs-obrien/playwright-mcp-prompts.git",
|
|
1780
|
-
defaultBranch: "main",
|
|
1781
|
-
description: "Prompt templates for Playwright end-to-end testing, page objects, and test generation.",
|
|
1782
|
-
owner: "debs-obrien",
|
|
1783
|
-
trustTier: "community",
|
|
1784
|
-
importCoverage: "high",
|
|
1785
|
-
estimatedPlugins: 25,
|
|
1786
|
-
focuses: ["playwright", "testing", "e2e", "automation"],
|
|
1787
|
-
},
|
|
1788
|
-
{
|
|
1789
|
-
id: "copilot-prompts-collection",
|
|
1790
|
-
name: "GitHub Copilot Prompts",
|
|
1791
|
-
repoUrl: "https://github.com/raffertyuy/github-copilot-prompts.git",
|
|
1792
|
-
defaultBranch: "main",
|
|
1793
|
-
description: "Curated collection of GitHub Copilot prompt files for code review, refactoring, and documentation.",
|
|
1794
|
-
owner: "raffertyuy",
|
|
1795
|
-
trustTier: "community",
|
|
1796
|
-
importCoverage: "high",
|
|
1797
|
-
estimatedPlugins: 30,
|
|
1798
|
-
focuses: ["prompts", "code-review", "refactoring", "docs"],
|
|
1799
|
-
},
|
|
1800
|
-
{
|
|
1801
|
-
id: "copilot-kit",
|
|
1802
|
-
name: "Copilot Kit",
|
|
1803
|
-
repoUrl: "https://github.com/TheSethRose/Copilot-Kit.git",
|
|
1804
|
-
defaultBranch: "main",
|
|
1805
|
-
description: "Comprehensive Copilot customization kit with agent profiles, skills, and prompt templates.",
|
|
1806
|
-
owner: "TheSethRose",
|
|
1807
|
-
trustTier: "community",
|
|
1808
|
-
importCoverage: "high",
|
|
1809
|
-
estimatedPlugins: 35,
|
|
1810
|
-
focuses: ["copilot", "agents", "skills", "prompts"],
|
|
1811
|
-
},
|
|
1812
|
-
{
|
|
1813
|
-
id: "dataplat-dbatools",
|
|
1814
|
-
name: "dbatools",
|
|
1815
|
-
repoUrl: "https://github.com/dataplat/dbatools.git",
|
|
1816
|
-
defaultBranch: "development",
|
|
1817
|
-
description: "SQL Server and database administration prompts for DBA workflows and automation.",
|
|
1818
|
-
owner: "dataplat",
|
|
1819
|
-
trustTier: "community",
|
|
1820
|
-
importCoverage: "medium",
|
|
1821
|
-
estimatedPlugins: 10,
|
|
1822
|
-
focuses: ["sql-server", "database", "powershell", "administration"],
|
|
1823
|
-
},
|
|
1824
|
-
{
|
|
1825
|
-
id: "finops-focus-spec",
|
|
1826
|
-
name: "FinOps FOCUS Spec",
|
|
1827
|
-
repoUrl: "https://github.com/FinOps-Open-Cost-and-Usage-Spec/FOCUS_Spec.git",
|
|
1828
|
-
defaultBranch: "working_draft",
|
|
1829
|
-
description: "FinOps specification prompts for cloud cost management and financial operations workflows.",
|
|
1830
|
-
owner: "FinOps-Open-Cost-and-Usage-Spec",
|
|
1831
|
-
trustTier: "community",
|
|
1832
|
-
importCoverage: "medium",
|
|
1833
|
-
estimatedPlugins: 8,
|
|
1834
|
-
focuses: ["finops", "cloud-costs", "specification", "governance"],
|
|
1835
|
-
},
|
|
1836
|
-
// ── MCP Tool Repositories ──────────────────────────────────────────────────
|
|
1837
|
-
{
|
|
1838
|
-
id: "modelcontextprotocol-servers",
|
|
1839
|
-
name: "MCP Official Servers",
|
|
1840
|
-
repoUrl: "https://github.com/modelcontextprotocol/servers.git",
|
|
1841
|
-
defaultBranch: "main",
|
|
1842
|
-
description: "Official Model Context Protocol reference servers — filesystem, GitHub, Git, Postgres, Slack, Google Maps, Puppeteer, and more.",
|
|
1843
|
-
owner: "modelcontextprotocol",
|
|
1844
|
-
trustTier: "official",
|
|
1845
|
-
importCoverage: "high",
|
|
1846
|
-
estimatedPlugins: 25,
|
|
1847
|
-
focuses: ["mcp", "tools", "filesystem", "database", "web", "search"],
|
|
1848
|
-
},
|
|
1849
|
-
{
|
|
1850
|
-
id: "github-mcp-server",
|
|
1851
|
-
name: "GitHub MCP Server",
|
|
1852
|
-
repoUrl: "https://github.com/github/github-mcp-server.git",
|
|
1853
|
-
defaultBranch: "main",
|
|
1854
|
-
description: "Official GitHub MCP server for repository management, issues, pull requests, and code search.",
|
|
1855
|
-
owner: "github",
|
|
1856
|
-
trustTier: "official",
|
|
1857
|
-
importCoverage: "medium",
|
|
1858
|
-
estimatedPlugins: 3,
|
|
1859
|
-
focuses: ["mcp", "github", "issues", "pull-requests", "code-search"],
|
|
1860
|
-
},
|
|
1861
|
-
{
|
|
1862
|
-
id: "punkpeye-awesome-mcp-servers",
|
|
1863
|
-
name: "Awesome MCP Servers",
|
|
1864
|
-
repoUrl: "https://github.com/punkpeye/awesome-mcp-servers.git",
|
|
1865
|
-
defaultBranch: "main",
|
|
1866
|
-
description: "Community-curated list of MCP server implementations — databases, dev tools, cloud platforms, AI services, and productivity tools.",
|
|
1867
|
-
owner: "punkpeye",
|
|
1868
|
-
trustTier: "community",
|
|
1869
|
-
importCoverage: "low",
|
|
1870
|
-
estimatedPlugins: 5,
|
|
1871
|
-
focuses: ["mcp", "tools", "community", "catalog"],
|
|
1872
|
-
},
|
|
1873
|
-
]);
|
|
1874
|
-
|
|
1875
|
-
function clampNumber(value, min, max) {
|
|
1876
|
-
const numeric = Number(value);
|
|
1877
|
-
if (!Number.isFinite(numeric)) return min;
|
|
1878
|
-
return Math.max(min, Math.min(max, numeric));
|
|
1879
|
-
}
|
|
1880
|
-
|
|
1881
|
-
function normalizeWellKnownSource(source = {}) {
|
|
1882
|
-
const repoUrl = String(source.repoUrl || "").trim();
|
|
1883
|
-
const github = repoUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/.]+)(?:\.git)?$/i);
|
|
1884
|
-
const owner = String(source.owner || (github?.[1] || "")).trim();
|
|
1885
|
-
const repo = String(source.repo || (github?.[2] || "")).trim();
|
|
1886
|
-
return {
|
|
1887
|
-
...source,
|
|
1888
|
-
owner: owner || null,
|
|
1889
|
-
repo: repo || null,
|
|
1890
|
-
provider: source.provider || (github ? "github" : null),
|
|
1891
|
-
importCoverage: String(source.importCoverage || "medium"),
|
|
1892
|
-
focuses: toStringArray(source.focuses),
|
|
1893
|
-
};
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
|
-
function compareWellKnownSources(a, b) {
|
|
1897
|
-
// Sort by estimated plugin count (most first), then trust score, then name
|
|
1898
|
-
const pluginDelta = Number(b?.estimatedPlugins || 0) - Number(a?.estimatedPlugins || 0);
|
|
1899
|
-
if (pluginDelta !== 0) return pluginDelta;
|
|
1900
|
-
const delta = Number(b?.trust?.score || 0) - Number(a?.trust?.score || 0);
|
|
1901
|
-
if (delta !== 0) return delta;
|
|
1902
|
-
return String(a?.name || "").localeCompare(String(b?.name || ""));
|
|
1903
|
-
}
|
|
1904
|
-
|
|
1905
|
-
export function computeWellKnownSourceTrust(source, probe = {}, options = {}) {
|
|
1906
|
-
const nowMs = Number(options?.nowMs || Date.now());
|
|
1907
|
-
const normalized = normalizeWellKnownSource(source);
|
|
1908
|
-
const reasons = [];
|
|
1909
|
-
let score = 20;
|
|
1910
|
-
|
|
1911
|
-
if (normalized.trustTier === "official") {
|
|
1912
|
-
score += 25;
|
|
1913
|
-
reasons.push("official-maintainer");
|
|
1914
|
-
}
|
|
1915
|
-
if (TRUSTED_GITHUB_OWNERS.has(String(normalized.owner || "").toLowerCase())) {
|
|
1916
|
-
score += 15;
|
|
1917
|
-
reasons.push("trusted-owner");
|
|
1918
|
-
}
|
|
1919
|
-
if (normalized.importCoverage === "high") {
|
|
1920
|
-
score += 12;
|
|
1921
|
-
reasons.push("high-import-coverage");
|
|
1922
|
-
} else if (normalized.importCoverage === "medium") {
|
|
1923
|
-
score += 6;
|
|
1924
|
-
reasons.push("import-coverage");
|
|
1925
|
-
}
|
|
1926
|
-
if (normalized.provider === "github") {
|
|
1927
|
-
score += 4;
|
|
1928
|
-
reasons.push("github-source");
|
|
1929
|
-
}
|
|
1930
|
-
|
|
1931
|
-
const stars = Number(probe?.stars || 0);
|
|
1932
|
-
if (stars >= 10000) {
|
|
1933
|
-
score += 10;
|
|
1934
|
-
reasons.push("popular-repo");
|
|
1935
|
-
} else if (stars >= 1000) {
|
|
1936
|
-
score += 6;
|
|
1937
|
-
reasons.push("established-repo");
|
|
1938
|
-
} else if (stars >= 100) {
|
|
1939
|
-
score += 3;
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
|
-
const daysSincePush = Number.isFinite(probe?.daysSincePush)
|
|
1943
|
-
? Number(probe.daysSincePush)
|
|
1944
|
-
: (probe?.pushedAt ? Math.max(0, (nowMs - Date.parse(probe.pushedAt)) / 86400000) : null);
|
|
1945
|
-
if (daysSincePush != null) {
|
|
1946
|
-
if (daysSincePush <= 45) {
|
|
1947
|
-
score += 10;
|
|
1948
|
-
reasons.push("recently-updated");
|
|
1949
|
-
} else if (daysSincePush <= 180) {
|
|
1950
|
-
score += 6;
|
|
1951
|
-
reasons.push("active-updates");
|
|
1952
|
-
} else if (daysSincePush <= 365) {
|
|
1953
|
-
score += 2;
|
|
1954
|
-
} else if (daysSincePush > 730) {
|
|
1955
|
-
score -= 16;
|
|
1956
|
-
reasons.push("stale-upstream");
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
|
|
1960
|
-
if (probe?.reachable === true) {
|
|
1961
|
-
score += 8;
|
|
1962
|
-
reasons.push("remote-reachable");
|
|
1963
|
-
} else if (probe?.reachable === false) {
|
|
1964
|
-
score -= 28;
|
|
1965
|
-
reasons.push("remote-unreachable");
|
|
1966
|
-
}
|
|
1967
|
-
|
|
1968
|
-
if (probe?.branchExists === true) {
|
|
1969
|
-
score += 6;
|
|
1970
|
-
reasons.push("branch-ok");
|
|
1971
|
-
} else if (probe?.branchExists === false) {
|
|
1972
|
-
score -= 22;
|
|
1973
|
-
reasons.push("branch-missing");
|
|
1974
|
-
}
|
|
1975
|
-
|
|
1976
|
-
if (probe?.archived === true) {
|
|
1977
|
-
score -= 45;
|
|
1978
|
-
reasons.push("archived");
|
|
1979
|
-
}
|
|
1980
|
-
if (probe?.disabled === true) {
|
|
1981
|
-
score -= 45;
|
|
1982
|
-
reasons.push("disabled");
|
|
1983
|
-
}
|
|
1984
|
-
|
|
1985
|
-
score = Math.round(clampNumber(score, 0, 100));
|
|
1986
|
-
// Hard-disable only for unreachable/archived/disabled repos — low score gets a warning but remains importable
|
|
1987
|
-
const hardBlocked = probe?.archived === true || probe?.disabled === true || probe?.reachable === false || probe?.branchExists === false;
|
|
1988
|
-
const enabled = !hardBlocked;
|
|
1989
|
-
const lowTrust = score < 55;
|
|
1990
|
-
const status = hardBlocked ? "disabled" : score >= 85 ? "healthy" : score >= 65 ? "warning" : score >= 55 ? "degraded" : "low-trust";
|
|
1991
|
-
|
|
1992
|
-
return {
|
|
1993
|
-
score,
|
|
1994
|
-
status,
|
|
1995
|
-
enabled,
|
|
1996
|
-
lowTrust,
|
|
1997
|
-
reasons: uniqueStrings(reasons),
|
|
1998
|
-
};
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
function buildWellKnownSourceResult(source, probe = null, options = {}) {
|
|
2002
|
-
const normalized = normalizeWellKnownSource(source);
|
|
2003
|
-
const trust = computeWellKnownSourceTrust(normalized, probe || {}, options);
|
|
2004
|
-
return {
|
|
2005
|
-
...normalized,
|
|
2006
|
-
trust,
|
|
2007
|
-
probe: probe ? { ...probe } : null,
|
|
2008
|
-
enabled: trust.enabled,
|
|
2009
|
-
status: trust.status,
|
|
2010
|
-
};
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
export function listWellKnownAgentSources() {
|
|
2014
|
-
return WELL_KNOWN_AGENT_SOURCES
|
|
2015
|
-
.map((source) => buildWellKnownSourceResult(source))
|
|
2016
|
-
.sort(compareWellKnownSources);
|
|
2017
|
-
}
|
|
2018
|
-
|
|
2019
|
-
export function clearWellKnownAgentSourceProbeCache() {
|
|
2020
|
-
wellKnownSourceProbeCache.clear();
|
|
2021
|
-
}
|
|
2022
|
-
|
|
2023
|
-
async function fetchGithubRepoProbe(source, options = {}) {
|
|
2024
|
-
const normalized = normalizeWellKnownSource(source);
|
|
2025
|
-
if (normalized.provider !== "github" || !normalized.owner || !normalized.repo) {
|
|
2026
|
-
return { checkedAt: nowISO(), reachable: false, branchExists: false, error: "Unsupported repository provider" };
|
|
2027
|
-
}
|
|
2028
|
-
|
|
2029
|
-
const fetchImpl = options.fetchImpl || globalThis.fetch;
|
|
2030
|
-
const spawnImpl = options.spawnImpl || spawnSync;
|
|
2031
|
-
const branch = String(normalized.defaultBranch || "main").trim() || "main";
|
|
2032
|
-
const headers = {
|
|
2033
|
-
"Accept": "application/vnd.github+json",
|
|
2034
|
-
"User-Agent": "bosun-library-manager",
|
|
2035
|
-
};
|
|
2036
|
-
if (process.env.GITHUB_TOKEN) headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
|
2037
|
-
|
|
2038
|
-
let repoMeta = null;
|
|
2039
|
-
let repoError = null;
|
|
2040
|
-
if (typeof fetchImpl === "function") {
|
|
2041
|
-
try {
|
|
2042
|
-
const response = await fetchImpl(`https://api.github.com/repos/${normalized.owner}/${normalized.repo}`, { headers });
|
|
2043
|
-
if (response?.ok) {
|
|
2044
|
-
repoMeta = await response.json();
|
|
2045
|
-
} else {
|
|
2046
|
-
repoError = `GitHub API returned ${Number(response?.status || 0) || "error"}`;
|
|
2047
|
-
}
|
|
2048
|
-
} catch (err) {
|
|
2049
|
-
repoError = err?.message || String(err);
|
|
2050
|
-
}
|
|
2051
|
-
} else {
|
|
2052
|
-
repoError = "fetch unavailable";
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
let reachable = false;
|
|
2056
|
-
let branchExists = false;
|
|
2057
|
-
let gitError = null;
|
|
2058
|
-
try {
|
|
2059
|
-
const remote = spawnImpl("git", ["ls-remote", "--exit-code", "--heads", normalized.repoUrl, branch], {
|
|
2060
|
-
encoding: "utf8",
|
|
2061
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2062
|
-
timeout: Number(options.timeoutMs || 15000),
|
|
2063
|
-
});
|
|
2064
|
-
const stdout = String(remote?.stdout || "").trim();
|
|
2065
|
-
reachable = Number(remote?.status) === 0 || stdout.length > 0;
|
|
2066
|
-
branchExists = reachable && stdout.length > 0;
|
|
2067
|
-
if (!reachable || !branchExists) {
|
|
2068
|
-
gitError = String(remote?.stderr || remote?.stdout || "git ls-remote failed").trim() || null;
|
|
2069
|
-
}
|
|
2070
|
-
} catch (err) {
|
|
2071
|
-
gitError = err?.message || String(err);
|
|
2072
|
-
}
|
|
2073
|
-
|
|
2074
|
-
return {
|
|
2075
|
-
checkedAt: nowISO(),
|
|
2076
|
-
reachable,
|
|
2077
|
-
branchExists,
|
|
2078
|
-
defaultBranch: String(repoMeta?.default_branch || branch || "main"),
|
|
2079
|
-
archived: repoMeta?.archived === true,
|
|
2080
|
-
disabled: repoMeta?.disabled === true,
|
|
2081
|
-
stars: Number(repoMeta?.stargazers_count || 0),
|
|
2082
|
-
forks: Number(repoMeta?.forks_count || 0),
|
|
2083
|
-
openIssues: Number(repoMeta?.open_issues_count || 0),
|
|
2084
|
-
pushedAt: repoMeta?.pushed_at || null,
|
|
2085
|
-
daysSincePush: repoMeta?.pushed_at ? Math.max(0, Math.round((Date.now() - Date.parse(repoMeta.pushed_at)) / 86400000)) : null,
|
|
2086
|
-
apiReachable: Boolean(repoMeta),
|
|
2087
|
-
importReady: reachable && branchExists && repoMeta?.archived !== true && repoMeta?.disabled !== true,
|
|
2088
|
-
error: gitError || repoError || null,
|
|
2089
|
-
};
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
|
-
export async function probeWellKnownAgentSources(options = {}) {
|
|
2093
|
-
const nowMs = Number(options?.nowMs || Date.now());
|
|
2094
|
-
const ttlMs = Math.max(1000, Number(options?.ttlMs || 30 * 60 * 1000));
|
|
2095
|
-
const sourceId = String(options?.sourceId || "").trim().toLowerCase();
|
|
2096
|
-
const refresh = options?.refresh === true;
|
|
2097
|
-
const sources = WELL_KNOWN_AGENT_SOURCES.filter((source) => !sourceId || source.id === sourceId);
|
|
2098
|
-
const results = [];
|
|
2099
|
-
|
|
2100
|
-
for (const source of sources) {
|
|
2101
|
-
const cacheKey = source.id;
|
|
2102
|
-
const cached = wellKnownSourceProbeCache.get(cacheKey) || null;
|
|
2103
|
-
if (!refresh && cached && (nowMs - Number(cached.cachedAt || 0)) < ttlMs) {
|
|
2104
|
-
results.push(buildWellKnownSourceResult(source, cached.probe, { nowMs }));
|
|
2105
|
-
continue;
|
|
2106
|
-
}
|
|
2107
|
-
|
|
2108
|
-
const probe = await fetchGithubRepoProbe(source, options);
|
|
2109
|
-
wellKnownSourceProbeCache.set(cacheKey, { cachedAt: nowMs, probe });
|
|
2110
|
-
results.push(buildWellKnownSourceResult(source, probe, { nowMs }));
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
return results.sort(compareWellKnownSources);
|
|
2114
|
-
}
|
|
2115
|
-
|
|
1588
|
+
export {
|
|
1589
|
+
WELL_KNOWN_AGENT_SOURCES,
|
|
1590
|
+
computeWellKnownSourceTrust,
|
|
1591
|
+
listWellKnownAgentSources,
|
|
1592
|
+
clearWellKnownAgentSourceProbeCache,
|
|
1593
|
+
probeWellKnownAgentSources,
|
|
1594
|
+
};
|
|
2116
1595
|
function parseSimpleFrontmatter(markdown = "") {
|
|
2117
1596
|
const text = String(markdown || "");
|
|
2118
1597
|
if (!text.startsWith("---\n")) return { attrs: {}, body: text };
|
|
@@ -2586,7 +2065,7 @@ export function syncAutoDiscoveredLibraryEntries(rootDir) {
|
|
|
2586
2065
|
};
|
|
2587
2066
|
}
|
|
2588
2067
|
|
|
2589
|
-
|
|
2068
|
+
function resolveRepositoryImportSource(options = {}) {
|
|
2590
2069
|
const sourceId = String(options?.sourceId || "").trim().toLowerCase();
|
|
2591
2070
|
const known = WELL_KNOWN_AGENT_SOURCES.find((source) => source.id === sourceId) || null;
|
|
2592
2071
|
const repoUrl = String(options?.repoUrl || known?.repoUrl || "").trim();
|
|
@@ -2599,16 +2078,13 @@ export function scanRepositoryForImport(options = {}) {
|
|
|
2599
2078
|
if (!isSafeGitRefName(branch)) {
|
|
2600
2079
|
throw new Error("Branch name contains invalid characters");
|
|
2601
2080
|
}
|
|
2602
|
-
const maxEntries = Math.max(
|
|
2603
|
-
1,
|
|
2604
|
-
Math.min(
|
|
2605
|
-
2000,
|
|
2606
|
-
Number.parseInt(String(options?.maxEntries ?? "500"), 10) || 500,
|
|
2607
|
-
),
|
|
2608
|
-
);
|
|
2609
2081
|
|
|
2082
|
+
return { sourceId, known, repoUrl, branch };
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
function createRepositoryImportCheckoutDir(prefix, repoUrl, branch) {
|
|
2610
2086
|
const cacheRoot = ensureDir(resolve(getBosunHomeDir(), ".cache", "imports"));
|
|
2611
|
-
const checkoutDir = resolve(cacheRoot,
|
|
2087
|
+
const checkoutDir = resolve(cacheRoot, `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`);
|
|
2612
2088
|
ensureDir(checkoutDir);
|
|
2613
2089
|
|
|
2614
2090
|
const clone = spawnSync("git", ["clone", "--depth", "1", "--branch", branch, "--", repoUrl, checkoutDir], {
|
|
@@ -2617,167 +2093,231 @@ export function scanRepositoryForImport(options = {}) {
|
|
|
2617
2093
|
timeout: 120_000,
|
|
2618
2094
|
});
|
|
2619
2095
|
const cloneStderr = String(clone.stderr || "").trim();
|
|
2620
|
-
// Treat "clone succeeded, but checkout failed" (long paths on Windows) as OK
|
|
2621
2096
|
const checkoutWarning = /clone succeeded.*checkout failed/i.test(cloneStderr);
|
|
2622
|
-
if (clone.status
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
}
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
}
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
}
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
}
|
|
2636
|
-
throw new Error(`Failed to clone repository: ${cloneStderr || "unknown error"}`);
|
|
2097
|
+
if (clone.status === 0 || checkoutWarning) return checkoutDir;
|
|
2098
|
+
|
|
2099
|
+
rmSync(checkoutDir, { recursive: true, force: true });
|
|
2100
|
+
if (/repository not found/i.test(cloneStderr)) {
|
|
2101
|
+
throw new Error(`Repository not found: ${repoUrl}`);
|
|
2102
|
+
}
|
|
2103
|
+
if (/could not read from remote/i.test(cloneStderr)) {
|
|
2104
|
+
throw new Error(`Cannot access repository (may be private or require authentication): ${repoUrl}`);
|
|
2105
|
+
}
|
|
2106
|
+
if (/not found in upstream/i.test(cloneStderr) || /remote branch.*not found/i.test(cloneStderr)) {
|
|
2107
|
+
throw new Error(`Branch "${branch}" not found in ${repoUrl}`);
|
|
2108
|
+
}
|
|
2109
|
+
if (clone.signal === "SIGTERM") {
|
|
2110
|
+
throw new Error(`Clone timed out — repository may be too large: ${repoUrl}`);
|
|
2637
2111
|
}
|
|
2112
|
+
throw new Error(`Failed to clone repository: ${cloneStderr || "unknown error"}`);
|
|
2113
|
+
}
|
|
2638
2114
|
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2115
|
+
function buildImportedMarkdownCandidate(candidate) {
|
|
2116
|
+
const { raw = "", attrs = {}, body = "", relPath = "", fileName = "", kind = null } = candidate || {};
|
|
2117
|
+
if (!kind) return null;
|
|
2118
|
+
|
|
2119
|
+
const fileStem = basename(fileName, ".md");
|
|
2120
|
+
const relSegments = relPath.split(/[\\/]/).filter(Boolean);
|
|
2121
|
+
const parentSegment = relSegments.length > 1 ? relSegments[relSegments.length - 2] : "";
|
|
2122
|
+
const fallbackNameBase = fileStem.toLowerCase() === "skill" && parentSegment ? parentSegment : fileStem;
|
|
2123
|
+
const fallbackName = fallbackNameBase.replace(/\.agent$/i, "").replace(/\.skill$/i, "").replace(/\.prompt$/i, "");
|
|
2124
|
+
const name = String(getFrontmatterValue(attrs, ["name", "title"]) || fallbackName.replace(/[-_.]+/g, " ")).trim();
|
|
2125
|
+
const description = normalizeImportedDescription(getFrontmatterValue(attrs, ["description", "summary"]), body);
|
|
2126
|
+
|
|
2127
|
+
return {
|
|
2128
|
+
...candidate,
|
|
2129
|
+
fileStem,
|
|
2130
|
+
name,
|
|
2131
|
+
description,
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
function sortImportedMarkdownCandidates(candidates = []) {
|
|
2136
|
+
const rank = { agent: 0, prompt: 1, skill: 2 };
|
|
2137
|
+
return candidates.sort((a, b) => {
|
|
2138
|
+
const aRank = Number(rank[a.kind] ?? 99);
|
|
2139
|
+
const bRank = Number(rank[b.kind] ?? 99);
|
|
2140
|
+
if (aRank !== bRank) return aRank - bRank;
|
|
2141
|
+
return String(a.relPath || "").localeCompare(String(b.relPath || ""));
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
function collectRepositoryImportMarkdownCandidates(checkoutDir, options = {}) {
|
|
2146
|
+
const maxEntries = Math.max(
|
|
2147
|
+
1,
|
|
2148
|
+
Math.min(2000, Number.parseInt(String(options?.maxEntries ?? "500"), 10) || 500),
|
|
2149
|
+
);
|
|
2150
|
+
|
|
2151
|
+
const files = walkFilesRecursive(checkoutDir);
|
|
2152
|
+
const candidates = sortImportedMarkdownCandidates(
|
|
2153
|
+
files
|
|
2642
2154
|
.filter((fullPath) => /\.md$/i.test(fullPath))
|
|
2643
2155
|
.map((fullPath) => {
|
|
2644
2156
|
const relPath = fullPath.slice(checkoutDir.length + 1).replace(/\\/g, "/");
|
|
2645
2157
|
const fileName = basename(fullPath);
|
|
2158
|
+
let raw = "";
|
|
2646
2159
|
let parsed = { attrs: {}, body: "" };
|
|
2647
2160
|
try {
|
|
2648
|
-
|
|
2161
|
+
raw = readFileSync(fullPath, "utf8");
|
|
2649
2162
|
parsed = parseSimpleFrontmatter(raw);
|
|
2650
|
-
} catch {
|
|
2163
|
+
} catch {
|
|
2164
|
+
return null;
|
|
2165
|
+
}
|
|
2651
2166
|
const kind = inferImportedEntryKind(relPath, fileName, parsed.attrs);
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
.filter(Boolean)
|
|
2663
|
-
.sort((a, b) => {
|
|
2664
|
-
const rank = { agent: 0, prompt: 1, skill: 2 };
|
|
2665
|
-
const aRank = Number(rank[a.kind] ?? 99);
|
|
2666
|
-
const bRank = Number(rank[b.kind] ?? 99);
|
|
2667
|
-
if (aRank !== bRank) return aRank - bRank;
|
|
2668
|
-
return String(a.relPath || "").localeCompare(String(b.relPath || ""));
|
|
2167
|
+
return buildImportedMarkdownCandidate({
|
|
2168
|
+
fullPath,
|
|
2169
|
+
relPath,
|
|
2170
|
+
fileName,
|
|
2171
|
+
raw,
|
|
2172
|
+
attrs: parsed.attrs,
|
|
2173
|
+
body: parsed.body,
|
|
2174
|
+
kind,
|
|
2175
|
+
selected: true,
|
|
2176
|
+
});
|
|
2669
2177
|
})
|
|
2670
|
-
.
|
|
2178
|
+
.filter(Boolean),
|
|
2179
|
+
).slice(0, maxEntries);
|
|
2671
2180
|
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
byType.mcp += 1;
|
|
2700
|
-
}
|
|
2701
|
-
} else {
|
|
2702
|
-
const discovered = parseMcpServersFromJson(raw, relPath);
|
|
2703
|
-
for (const mcp of discovered) {
|
|
2704
|
-
candidates.push({
|
|
2705
|
-
relPath: `${relPath}#${mcp.id}`,
|
|
2706
|
-
fileName: basename(configPath),
|
|
2707
|
-
kind: "mcp",
|
|
2708
|
-
name: mcp.name || mcp.id,
|
|
2709
|
-
description: `${mcp.transport === "stdio" ? "stdio" : "url"} MCP server${mcp.command ? ": " + mcp.command : ""}`,
|
|
2710
|
-
selected: true,
|
|
2711
|
-
});
|
|
2712
|
-
byType.mcp += 1;
|
|
2713
|
-
}
|
|
2714
|
-
}
|
|
2181
|
+
return { files, candidates };
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
function listRepositoryMcpConfigFiles(checkoutDir, { includeLegacy = false } = {}) {
|
|
2185
|
+
const configFiles = [
|
|
2186
|
+
{ path: resolve(checkoutDir, ".codex", "config.toml"), format: "toml" },
|
|
2187
|
+
{ path: resolve(checkoutDir, ".mcp.json"), format: "json" },
|
|
2188
|
+
{ path: resolve(checkoutDir, ".vscode", "mcp.json"), format: "json" },
|
|
2189
|
+
];
|
|
2190
|
+
|
|
2191
|
+
if (includeLegacy) {
|
|
2192
|
+
configFiles.splice(2, 0, { path: resolve(checkoutDir, "mcp.json"), format: "json" });
|
|
2193
|
+
configFiles.push({ path: resolve(checkoutDir, "claude_desktop_config.json"), format: "json" });
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
return configFiles;
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
function discoverRepositoryMcpConfigs(checkoutDir, options = {}) {
|
|
2200
|
+
const discovered = [];
|
|
2201
|
+
for (const { path: configPath, format } of listRepositoryMcpConfigFiles(checkoutDir, options)) {
|
|
2202
|
+
if (!existsSync(configPath)) continue;
|
|
2203
|
+
let raw = "";
|
|
2204
|
+
try {
|
|
2205
|
+
raw = readFileSync(configPath, "utf8");
|
|
2206
|
+
} catch {
|
|
2207
|
+
continue;
|
|
2715
2208
|
}
|
|
2209
|
+
const relPath = relative(checkoutDir, configPath).replace(/\\/g, "/");
|
|
2210
|
+
const entries = format === "toml"
|
|
2211
|
+
? parseMcpServersFromToml(raw, relPath)
|
|
2212
|
+
: parseMcpServersFromJson(raw, relPath);
|
|
2213
|
+
discovered.push(...entries.map((entry) => ({ ...entry, relPath, fileName: basename(configPath) })));
|
|
2214
|
+
}
|
|
2215
|
+
return discovered;
|
|
2216
|
+
}
|
|
2716
2217
|
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
const
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
kind: "mcp",
|
|
2771
|
-
name: nameMatch ? nameMatch[1] : cmd,
|
|
2772
|
-
description: descMatch ? descMatch[1] : `stdio MCP server: ${cmd}`,
|
|
2773
|
-
selected: true,
|
|
2774
|
-
});
|
|
2775
|
-
byType.mcp += 1;
|
|
2776
|
-
}
|
|
2218
|
+
function appendRepositoryMcpImportCandidates(candidates, files, checkoutDir, byType) {
|
|
2219
|
+
for (const mcp of discoverRepositoryMcpConfigs(checkoutDir, { includeLegacy: true })) {
|
|
2220
|
+
candidates.push({
|
|
2221
|
+
relPath: `${mcp.relPath}#${mcp.id}`,
|
|
2222
|
+
fileName: mcp.fileName,
|
|
2223
|
+
kind: "mcp",
|
|
2224
|
+
name: mcp.name || mcp.id,
|
|
2225
|
+
description: `${mcp.transport === "stdio" ? "stdio" : "url"} MCP server${mcp.command ? ": " + mcp.command : ""}`,
|
|
2226
|
+
selected: true,
|
|
2227
|
+
});
|
|
2228
|
+
byType.mcp += 1;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
const mcpSeenIds = new Set(candidates.filter((candidate) => candidate.kind === "mcp").map((candidate) => candidate.name));
|
|
2232
|
+
for (const fullPath of files) {
|
|
2233
|
+
const fileName = basename(fullPath);
|
|
2234
|
+
if (fileName !== "package.json" && fileName !== "pyproject.toml") continue;
|
|
2235
|
+
const relPath = relative(checkoutDir, fullPath).replace(/\\/g, "/");
|
|
2236
|
+
if (relPath === "package.json") continue;
|
|
2237
|
+
let raw = "";
|
|
2238
|
+
try {
|
|
2239
|
+
raw = readFileSync(fullPath, "utf8");
|
|
2240
|
+
} catch {
|
|
2241
|
+
continue;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
if (fileName === "package.json") {
|
|
2245
|
+
let pkg;
|
|
2246
|
+
try {
|
|
2247
|
+
pkg = JSON.parse(raw);
|
|
2248
|
+
} catch {
|
|
2249
|
+
continue;
|
|
2250
|
+
}
|
|
2251
|
+
if (!pkg || typeof pkg !== "object") continue;
|
|
2252
|
+
const bin = pkg.bin;
|
|
2253
|
+
if (!bin || typeof bin !== "object") continue;
|
|
2254
|
+
const mcpBins = Object.entries(bin).filter(([key]) => /^mcp[-_]?server/i.test(key) || /^mcp-/i.test(key));
|
|
2255
|
+
if (mcpBins.length === 0) continue;
|
|
2256
|
+
for (const [cmd] of mcpBins) {
|
|
2257
|
+
const id = slugifyIdentifier(cmd);
|
|
2258
|
+
if (mcpSeenIds.has(cmd) || mcpSeenIds.has(id)) continue;
|
|
2259
|
+
mcpSeenIds.add(cmd);
|
|
2260
|
+
const name = String(pkg.mcpName || pkg.name || cmd).trim();
|
|
2261
|
+
const description = String(pkg.description || "").trim();
|
|
2262
|
+
candidates.push({
|
|
2263
|
+
relPath: `${relPath}#${cmd}`,
|
|
2264
|
+
fileName,
|
|
2265
|
+
kind: "mcp",
|
|
2266
|
+
name: name.startsWith("@") ? cmd : name,
|
|
2267
|
+
description: description || `stdio MCP server: ${cmd}`,
|
|
2268
|
+
selected: true,
|
|
2269
|
+
});
|
|
2270
|
+
byType.mcp += 1;
|
|
2777
2271
|
}
|
|
2272
|
+
continue;
|
|
2778
2273
|
}
|
|
2779
2274
|
|
|
2780
|
-
|
|
2275
|
+
const scriptMatch = raw.match(/\[project\.scripts\]([\s\S]*?)(?:\n\[|\n$)/);
|
|
2276
|
+
if (!scriptMatch) continue;
|
|
2277
|
+
const scriptLines = scriptMatch[1].split(/\r?\n/);
|
|
2278
|
+
for (const line of scriptLines) {
|
|
2279
|
+
const kv = line.match(/^\s*(mcp[-_]?server[-_]?\S*)\s*=/i);
|
|
2280
|
+
if (!kv) continue;
|
|
2281
|
+
const cmd = kv[1].trim();
|
|
2282
|
+
if (mcpSeenIds.has(cmd)) continue;
|
|
2283
|
+
mcpSeenIds.add(cmd);
|
|
2284
|
+
const nameMatch = raw.match(/^\s*name\s*=\s*"([^"]+)"/m);
|
|
2285
|
+
const descMatch = raw.match(/^\s*description\s*=\s*"([^"]+)"/m);
|
|
2286
|
+
candidates.push({
|
|
2287
|
+
relPath: `${relPath}#${cmd}`,
|
|
2288
|
+
fileName,
|
|
2289
|
+
kind: "mcp",
|
|
2290
|
+
name: nameMatch ? nameMatch[1] : cmd,
|
|
2291
|
+
description: descMatch ? descMatch[1] : `stdio MCP server: ${cmd}`,
|
|
2292
|
+
selected: true,
|
|
2293
|
+
});
|
|
2294
|
+
byType.mcp += 1;
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
export function scanRepositoryForImport(options = {}) {
|
|
2300
|
+
const { sourceId, known, repoUrl, branch } = resolveRepositoryImportSource(options);
|
|
2301
|
+
const maxEntries = Math.max(
|
|
2302
|
+
1,
|
|
2303
|
+
Math.min(
|
|
2304
|
+
2000,
|
|
2305
|
+
Number.parseInt(String(options?.maxEntries ?? "500"), 10) || 500,
|
|
2306
|
+
),
|
|
2307
|
+
);
|
|
2308
|
+
|
|
2309
|
+
const checkoutDir = createRepositoryImportCheckoutDir("scan", repoUrl, branch);
|
|
2310
|
+
|
|
2311
|
+
try {
|
|
2312
|
+
const { files, candidates } = collectRepositoryImportMarkdownCandidates(checkoutDir, { maxEntries });
|
|
2313
|
+
|
|
2314
|
+
const byType = { agent: 0, prompt: 0, skill: 0, mcp: 0 };
|
|
2315
|
+
for (const candidate of candidates) {
|
|
2316
|
+
byType[candidate.kind] = (byType[candidate.kind] || 0) + 1;
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
appendRepositoryMcpImportCandidates(candidates, files, checkoutDir, byType);
|
|
2320
|
+
|
|
2781
2321
|
const rootDir = options?.rootDir || null;
|
|
2782
2322
|
let duplicateMap = {};
|
|
2783
2323
|
let intraDuplicateMap = {};
|
|
@@ -2787,13 +2327,12 @@ export function scanRepositoryForImport(options = {}) {
|
|
|
2787
2327
|
const extDups = detectImportDuplicates(candidates, existingEntries);
|
|
2788
2328
|
for (const [relPath, info] of extDups) {
|
|
2789
2329
|
duplicateMap[relPath] = info;
|
|
2790
|
-
// Auto-deselect exact duplicates
|
|
2791
2330
|
const cand = candidates.find((c) => c.relPath === relPath);
|
|
2792
2331
|
if (cand && info.similarity >= 0.95) cand.selected = false;
|
|
2793
2332
|
}
|
|
2794
|
-
} catch {
|
|
2333
|
+
} catch {}
|
|
2795
2334
|
}
|
|
2796
|
-
|
|
2335
|
+
|
|
2797
2336
|
const intraDups = detectIntraDuplicates(candidates);
|
|
2798
2337
|
for (const [relPath, peers] of intraDups) {
|
|
2799
2338
|
intraDuplicateMap[relPath] = peers;
|
|
@@ -2815,19 +2354,189 @@ export function scanRepositoryForImport(options = {}) {
|
|
|
2815
2354
|
}
|
|
2816
2355
|
}
|
|
2817
2356
|
|
|
2818
|
-
|
|
2819
|
-
const
|
|
2820
|
-
const
|
|
2821
|
-
const
|
|
2822
|
-
if (!repoUrl) throw new Error("Repository URL or source is required");
|
|
2357
|
+
function importRepositoryMarkdownCandidate(rootDir, candidate, context) {
|
|
2358
|
+
const { known, repoUrl, branch, sourceId, importAgents, importSkills, importPrompts, takenIds, imported, importedByType } = context;
|
|
2359
|
+
const { attrs, body, relPath, fileStem, kind, name, description, raw } = candidate;
|
|
2360
|
+
const keywords = keywordTokens(`${name} ${description} ${relPath}`, { minLength: 4 }).slice(0, 10);
|
|
2823
2361
|
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2362
|
+
if (kind === "prompt") {
|
|
2363
|
+
if (!importPrompts) return { needsAgentIndexRefresh: false, needsSkillIndexRefresh: false };
|
|
2364
|
+
const baseId = slugify(`${sourceId || "imported"}-${name}`) || slugify(fileStem) || `imported-prompt-${imported.length + 1}`;
|
|
2365
|
+
const id = ensureUniqueId(baseId, takenIds);
|
|
2366
|
+
const promptContent = String(body || raw || "").trim();
|
|
2367
|
+
if (!promptContent) return { needsAgentIndexRefresh: false, needsSkillIndexRefresh: false };
|
|
2368
|
+
upsertEntry(rootDir, {
|
|
2369
|
+
id,
|
|
2370
|
+
type: "prompt",
|
|
2371
|
+
name,
|
|
2372
|
+
description: description || `Imported prompt from ${known?.name || repoUrl}`,
|
|
2373
|
+
tags: uniqueStrings(["imported", "prompt", sourceId || "external", ...parseJsonishArray(getFrontmatterValue(attrs, ["tags"]))]),
|
|
2374
|
+
meta: {
|
|
2375
|
+
sourceId: sourceId || null,
|
|
2376
|
+
repoUrl,
|
|
2377
|
+
branch,
|
|
2378
|
+
relPath,
|
|
2379
|
+
},
|
|
2380
|
+
}, promptContent);
|
|
2381
|
+
imported.push({ id, name, relPath, type: "prompt", promptId: null });
|
|
2382
|
+
importedByType.prompt += 1;
|
|
2383
|
+
return { needsAgentIndexRefresh: false, needsSkillIndexRefresh: false };
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
if (kind === "skill") {
|
|
2387
|
+
if (!importSkills) return { needsAgentIndexRefresh: false, needsSkillIndexRefresh: false };
|
|
2388
|
+
const baseId = slugify(`${sourceId || "imported"}-${name}`) || slugify(fileStem) || `imported-skill-${imported.length + 1}`;
|
|
2389
|
+
const id = ensureUniqueId(baseId, takenIds);
|
|
2390
|
+
const skillContent = String(body || raw || "").trim();
|
|
2391
|
+
if (!skillContent) return { needsAgentIndexRefresh: false, needsSkillIndexRefresh: false };
|
|
2392
|
+
upsertEntry(rootDir, {
|
|
2393
|
+
id,
|
|
2394
|
+
type: "skill",
|
|
2395
|
+
name,
|
|
2396
|
+
description: description || "Imported skill",
|
|
2397
|
+
tags: uniqueStrings(["imported", "skill", sourceId || "external", ...parseJsonishArray(getFrontmatterValue(attrs, ["tags"]))]),
|
|
2398
|
+
meta: {
|
|
2399
|
+
sourceId: sourceId || null,
|
|
2400
|
+
repoUrl,
|
|
2401
|
+
branch,
|
|
2402
|
+
relPath,
|
|
2403
|
+
},
|
|
2404
|
+
}, skillContent, { skipIndexSync: true });
|
|
2405
|
+
imported.push({ id, name, relPath, type: "skill", promptId: null });
|
|
2406
|
+
importedByType.skill += 1;
|
|
2407
|
+
return { needsAgentIndexRefresh: false, needsSkillIndexRefresh: true };
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
if (kind !== "agent" || !importAgents) {
|
|
2411
|
+
return { needsAgentIndexRefresh: false, needsSkillIndexRefresh: false };
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
const baseId = slugify(`${sourceId || "imported"}-${name}`) || slugify(fileStem) || `imported-agent-${imported.length + 1}`;
|
|
2415
|
+
const id = ensureUniqueId(baseId, takenIds);
|
|
2416
|
+
const toolHints = parseJsonishArray(getFrontmatterValue(attrs, ["tools", "enabledTools"]));
|
|
2417
|
+
const profileSkillHints = parseJsonishArray(getFrontmatterValue(attrs, ["skills"]));
|
|
2418
|
+
const mcpHints = parseJsonishArray(getFrontmatterValue(attrs, ["enabledMcpServers", "mcpServers", "mcp"]));
|
|
2419
|
+
const titlePatternHints = parseJsonishArray(getFrontmatterValue(attrs, ["titlePatterns", "title_patterns", "patterns"]));
|
|
2420
|
+
const tags = uniqueStrings([
|
|
2421
|
+
"imported",
|
|
2422
|
+
sourceId || "external",
|
|
2423
|
+
...parseJsonishArray(getFrontmatterValue(attrs, ["tags"])),
|
|
2424
|
+
...keywords.slice(0, 4),
|
|
2425
|
+
]);
|
|
2426
|
+
const pathScopes = uniqueStrings(
|
|
2427
|
+
relPath
|
|
2428
|
+
.split(/[\/]/)
|
|
2429
|
+
.slice(0, -1)
|
|
2430
|
+
.map((segment) => slugify(segment))
|
|
2431
|
+
.filter((segment) => segment && segment !== "github" && segment !== "agents"),
|
|
2432
|
+
).slice(0, 6);
|
|
2433
|
+
const explicitScopes = parseJsonishArray(getFrontmatterValue(attrs, ["scopes", "scope"]));
|
|
2434
|
+
const scopes = uniqueStrings([...explicitScopes, ...pathScopes]).slice(0, 8);
|
|
2435
|
+
const titlePatterns = uniqueStrings([
|
|
2436
|
+
...titlePatternHints,
|
|
2437
|
+
...keywordTokens(name, { minLength: 4 }).slice(0, 4).map((token) => `\\b${token.replace(/[.*+?^${}()|[\\]\\]/g, "")}\\b`),
|
|
2438
|
+
]);
|
|
2439
|
+
const promptId = `${id}-prompt`;
|
|
2440
|
+
|
|
2441
|
+
if (importPrompts && body) {
|
|
2442
|
+
upsertEntry(rootDir, {
|
|
2443
|
+
id: promptId,
|
|
2444
|
+
type: "prompt",
|
|
2445
|
+
name: `${name} Prompt`,
|
|
2446
|
+
description: `Imported prompt from ${known?.name || repoUrl}`,
|
|
2447
|
+
tags: uniqueStrings(["imported", "agent-prompt", sourceId || "external"]),
|
|
2448
|
+
meta: {
|
|
2449
|
+
sourceId: sourceId || null,
|
|
2450
|
+
repoUrl,
|
|
2451
|
+
branch,
|
|
2452
|
+
relPath,
|
|
2453
|
+
},
|
|
2454
|
+
}, body);
|
|
2455
|
+
imported.push({ id: promptId, name: `${name} Prompt`, relPath, type: "prompt", promptId: null });
|
|
2456
|
+
importedByType.prompt += 1;
|
|
2827
2457
|
}
|
|
2828
|
-
|
|
2829
|
-
|
|
2458
|
+
|
|
2459
|
+
const explicitAgentType = String(getFrontmatterValue(attrs, ["agentType", "agent_type"]) || "").trim().toLowerCase();
|
|
2460
|
+
const profile = {
|
|
2461
|
+
id,
|
|
2462
|
+
name,
|
|
2463
|
+
description,
|
|
2464
|
+
titlePatterns: titlePatterns.length ? titlePatterns : ["\\btask\\b"],
|
|
2465
|
+
scopes,
|
|
2466
|
+
sdk: null,
|
|
2467
|
+
model: null,
|
|
2468
|
+
promptOverride: importPrompts && body ? promptId : null,
|
|
2469
|
+
skills: profileSkillHints,
|
|
2470
|
+
hookProfile: null,
|
|
2471
|
+
env: {},
|
|
2472
|
+
enabledTools: toolHints.length ? toolHints : null,
|
|
2473
|
+
enabledMcpServers: mcpHints,
|
|
2474
|
+
tags,
|
|
2475
|
+
agentType: explicitAgentType || (/voice|audio|realtime/i.test(`${name} ${description}`) ? "voice" : "task"),
|
|
2476
|
+
importMeta: {
|
|
2477
|
+
sourceId: sourceId || null,
|
|
2478
|
+
repoUrl,
|
|
2479
|
+
branch,
|
|
2480
|
+
relPath,
|
|
2481
|
+
},
|
|
2482
|
+
};
|
|
2483
|
+
|
|
2484
|
+
upsertEntry(rootDir, {
|
|
2485
|
+
id,
|
|
2486
|
+
type: "agent",
|
|
2487
|
+
name,
|
|
2488
|
+
description,
|
|
2489
|
+
tags: profile.tags,
|
|
2490
|
+
meta: {
|
|
2491
|
+
sourceId: sourceId || null,
|
|
2492
|
+
repoUrl,
|
|
2493
|
+
branch,
|
|
2494
|
+
relPath,
|
|
2495
|
+
},
|
|
2496
|
+
}, profile, { skipIndexSync: true });
|
|
2497
|
+
|
|
2498
|
+
imported.push({ id, name, relPath, type: "agent", promptId: importPrompts && body ? promptId : null });
|
|
2499
|
+
importedByType.agent += 1;
|
|
2500
|
+
return { needsAgentIndexRefresh: true, needsSkillIndexRefresh: false };
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
function importRepositoryMcpEntries(rootDir, checkoutDir, context) {
|
|
2504
|
+
const { sourceId, repoUrl, branch, takenIds, imported, importedByType } = context;
|
|
2505
|
+
for (const mcp of discoverRepositoryMcpConfigs(checkoutDir)) {
|
|
2506
|
+
const baseId = slugify(`${sourceId || "imported"}-${mcp.id}`) || slugify(mcp.id) || `imported-mcp-${imported.length + 1}`;
|
|
2507
|
+
const id = ensureUniqueId(baseId, takenIds);
|
|
2508
|
+
const content = {
|
|
2509
|
+
id,
|
|
2510
|
+
name: mcp.name,
|
|
2511
|
+
description: "Imported MCP server definition from " + mcp.relPath,
|
|
2512
|
+
transport: mcp.transport,
|
|
2513
|
+
command: mcp.transport === "stdio" ? mcp.command : undefined,
|
|
2514
|
+
args: mcp.transport === "stdio" ? mcp.args : undefined,
|
|
2515
|
+
url: mcp.transport === "url" ? mcp.url : undefined,
|
|
2516
|
+
env: Object.keys(mcp.env || {}).length ? mcp.env : undefined,
|
|
2517
|
+
source: "imported",
|
|
2518
|
+
tags: ["imported", "mcp", sourceId || "external"],
|
|
2519
|
+
};
|
|
2520
|
+
upsertEntry(rootDir, {
|
|
2521
|
+
id,
|
|
2522
|
+
type: "mcp",
|
|
2523
|
+
name: mcp.name,
|
|
2524
|
+
description: content.description,
|
|
2525
|
+
tags: uniqueStrings(["imported", "mcp", sourceId || "external"]),
|
|
2526
|
+
meta: {
|
|
2527
|
+
sourceId: sourceId || null,
|
|
2528
|
+
repoUrl,
|
|
2529
|
+
branch,
|
|
2530
|
+
relPath: mcp.relPath,
|
|
2531
|
+
},
|
|
2532
|
+
}, content);
|
|
2533
|
+
imported.push({ id, name: mcp.name, relPath: mcp.relPath, type: "mcp", promptId: null });
|
|
2534
|
+
importedByType.mcp += 1;
|
|
2830
2535
|
}
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
export function importAgentProfilesFromRepository(rootDir, options = {}) {
|
|
2539
|
+
const { sourceId, known, repoUrl, branch } = resolveRepositoryImportSource(options);
|
|
2831
2540
|
const importAgents = options?.importAgents !== false;
|
|
2832
2541
|
const importSkills = options?.importSkills !== false;
|
|
2833
2542
|
const importPrompts = options?.importPrompts !== false;
|
|
@@ -2842,62 +2551,8 @@ export function importAgentProfilesFromRepository(rootDir, options = {}) {
|
|
|
2842
2551
|
),
|
|
2843
2552
|
);
|
|
2844
2553
|
|
|
2845
|
-
const
|
|
2846
|
-
const
|
|
2847
|
-
ensureDir(checkoutDir);
|
|
2848
|
-
|
|
2849
|
-
const clone = spawnSync("git", ["clone", "--depth", "1", "--branch", branch, "--", repoUrl, checkoutDir], {
|
|
2850
|
-
encoding: "utf8",
|
|
2851
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2852
|
-
timeout: 120_000,
|
|
2853
|
-
});
|
|
2854
|
-
const cloneStderr = String(clone.stderr || "").trim();
|
|
2855
|
-
const checkoutWarning = /clone succeeded.*checkout failed/i.test(cloneStderr);
|
|
2856
|
-
if (clone.status !== 0 && !checkoutWarning) {
|
|
2857
|
-
rmSync(checkoutDir, { recursive: true, force: true });
|
|
2858
|
-
if (/repository not found/i.test(cloneStderr)) {
|
|
2859
|
-
throw new Error(`Repository not found: ${repoUrl}`);
|
|
2860
|
-
}
|
|
2861
|
-
if (/could not read from remote/i.test(cloneStderr)) {
|
|
2862
|
-
throw new Error(`Cannot access repository (may be private or require authentication): ${repoUrl}`);
|
|
2863
|
-
}
|
|
2864
|
-
if (/not found in upstream/i.test(cloneStderr) || /remote branch.*not found/i.test(cloneStderr)) {
|
|
2865
|
-
throw new Error(`Branch "${branch}" not found in ${repoUrl}`);
|
|
2866
|
-
}
|
|
2867
|
-
if (clone.signal === "SIGTERM") {
|
|
2868
|
-
throw new Error(`Clone timed out — repository may be too large: ${repoUrl}`);
|
|
2869
|
-
}
|
|
2870
|
-
throw new Error(`Failed to clone repository: ${cloneStderr || "unknown error"}`);
|
|
2871
|
-
}
|
|
2872
|
-
|
|
2873
|
-
const files = walkFilesRecursive(checkoutDir);
|
|
2874
|
-
const markdownCandidates = files
|
|
2875
|
-
.filter((fullPath) => /\.md$/i.test(fullPath))
|
|
2876
|
-
.map((fullPath) => {
|
|
2877
|
-
const relPath = fullPath.slice(checkoutDir.length + 1).replace(/\\/g, "/");
|
|
2878
|
-
const fileName = basename(fullPath);
|
|
2879
|
-
const raw = readFileSync(fullPath, "utf8");
|
|
2880
|
-
const parsed = parseSimpleFrontmatter(raw);
|
|
2881
|
-
return {
|
|
2882
|
-
fullPath,
|
|
2883
|
-
relPath,
|
|
2884
|
-
fileName,
|
|
2885
|
-
raw,
|
|
2886
|
-
attrs: parsed.attrs,
|
|
2887
|
-
body: parsed.body,
|
|
2888
|
-
kind: inferImportedEntryKind(relPath, fileName, parsed.attrs),
|
|
2889
|
-
};
|
|
2890
|
-
})
|
|
2891
|
-
.filter((entry) => Boolean(entry.kind))
|
|
2892
|
-
.sort((a, b) => {
|
|
2893
|
-
const rank = { agent: 0, prompt: 1, skill: 2 };
|
|
2894
|
-
const aRank = Number(rank[a.kind] ?? 99);
|
|
2895
|
-
const bRank = Number(rank[b.kind] ?? 99);
|
|
2896
|
-
if (aRank !== bRank) return aRank - bRank;
|
|
2897
|
-
return String(a.relPath || "").localeCompare(String(b.relPath || ""));
|
|
2898
|
-
});
|
|
2899
|
-
|
|
2900
|
-
const candidates = markdownCandidates.slice(0, maxProfiles);
|
|
2554
|
+
const checkoutDir = createRepositoryImportCheckoutDir("import", repoUrl, branch);
|
|
2555
|
+
const { candidates } = collectRepositoryImportMarkdownCandidates(checkoutDir, { maxEntries: maxProfiles });
|
|
2901
2556
|
|
|
2902
2557
|
const takenIds = new Set(
|
|
2903
2558
|
listEntries(rootDir).map((entry) => String(entry?.id || "").trim()).filter(Boolean),
|
|
@@ -2909,209 +2564,36 @@ export function importAgentProfilesFromRepository(rootDir, options = {}) {
|
|
|
2909
2564
|
|
|
2910
2565
|
try {
|
|
2911
2566
|
for (const candidate of candidates) {
|
|
2912
|
-
const {
|
|
2567
|
+
const { relPath } = candidate;
|
|
2913
2568
|
if (includeEntries && !includeEntries.has(relPath)) continue;
|
|
2914
|
-
const
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
if (!promptContent) continue;
|
|
2929
|
-
upsertEntry(rootDir, {
|
|
2930
|
-
id,
|
|
2931
|
-
type: "prompt",
|
|
2932
|
-
name,
|
|
2933
|
-
description: description || `Imported prompt from ${known?.name || repoUrl}`,
|
|
2934
|
-
tags: uniqueStrings(["imported", "prompt", sourceId || "external", ...parseJsonishArray(getFrontmatterValue(attrs, ["tags"]))]),
|
|
2935
|
-
meta: {
|
|
2936
|
-
sourceId: sourceId || null,
|
|
2937
|
-
repoUrl,
|
|
2938
|
-
branch,
|
|
2939
|
-
relPath,
|
|
2940
|
-
},
|
|
2941
|
-
}, promptContent);
|
|
2942
|
-
imported.push({ id, name, relPath, type: "prompt", promptId: null });
|
|
2943
|
-
importedByType.prompt += 1;
|
|
2944
|
-
continue;
|
|
2945
|
-
}
|
|
2946
|
-
|
|
2947
|
-
if (kind === "skill") {
|
|
2948
|
-
if (!importSkills) continue;
|
|
2949
|
-
const baseId = slugify(`${sourceId || "imported"}-${name}`) || slugify(fileStem) || `imported-skill-${imported.length + 1}`;
|
|
2950
|
-
const id = ensureUniqueId(baseId, takenIds);
|
|
2951
|
-
const skillContent = String(body || candidate.raw || "").trim();
|
|
2952
|
-
if (!skillContent) continue;
|
|
2953
|
-
upsertEntry(rootDir, {
|
|
2954
|
-
id,
|
|
2955
|
-
type: "skill",
|
|
2956
|
-
name,
|
|
2957
|
-
description: description || "Imported skill",
|
|
2958
|
-
tags: uniqueStrings(["imported", "skill", sourceId || "external", ...parseJsonishArray(getFrontmatterValue(attrs, ["tags"]))]),
|
|
2959
|
-
meta: {
|
|
2960
|
-
sourceId: sourceId || null,
|
|
2961
|
-
repoUrl,
|
|
2962
|
-
branch,
|
|
2963
|
-
relPath,
|
|
2964
|
-
},
|
|
2965
|
-
}, skillContent, { skipIndexSync: true });
|
|
2966
|
-
imported.push({ id, name, relPath, type: "skill", promptId: null });
|
|
2967
|
-
importedByType.skill += 1;
|
|
2968
|
-
needsSkillIndexRefresh = true;
|
|
2969
|
-
continue;
|
|
2970
|
-
}
|
|
2971
|
-
|
|
2972
|
-
if (kind !== "agent" || !importAgents) continue;
|
|
2973
|
-
|
|
2974
|
-
const baseId = slugify(`${sourceId || "imported"}-${name}`) || slugify(fileStem) || `imported-agent-${imported.length + 1}`;
|
|
2975
|
-
const id = ensureUniqueId(baseId, takenIds);
|
|
2976
|
-
const toolHints = parseJsonishArray(getFrontmatterValue(attrs, ["tools", "enabledTools"]));
|
|
2977
|
-
const profileSkillHints = parseJsonishArray(getFrontmatterValue(attrs, ["skills"]));
|
|
2978
|
-
const mcpHints = parseJsonishArray(getFrontmatterValue(attrs, ["enabledMcpServers", "mcpServers", "mcp"]));
|
|
2979
|
-
const titlePatternHints = parseJsonishArray(getFrontmatterValue(attrs, ["titlePatterns", "title_patterns", "patterns"]));
|
|
2980
|
-
const tags = uniqueStrings([
|
|
2981
|
-
"imported",
|
|
2982
|
-
sourceId || "external",
|
|
2983
|
-
...parseJsonishArray(getFrontmatterValue(attrs, ["tags"])),
|
|
2984
|
-
...keywords.slice(0, 4),
|
|
2985
|
-
]);
|
|
2986
|
-
const pathScopes = uniqueStrings(
|
|
2987
|
-
relPath
|
|
2988
|
-
.split(/[\\/]/)
|
|
2989
|
-
.slice(0, -1)
|
|
2990
|
-
.map((segment) => slugify(segment))
|
|
2991
|
-
.filter((segment) => segment && segment !== "github" && segment !== "agents"),
|
|
2992
|
-
).slice(0, 6);
|
|
2993
|
-
const explicitScopes = parseJsonishArray(getFrontmatterValue(attrs, ["scopes", "scope"]));
|
|
2994
|
-
const scopes = uniqueStrings([...explicitScopes, ...pathScopes]).slice(0, 8);
|
|
2995
|
-
const titlePatterns = uniqueStrings([
|
|
2996
|
-
...titlePatternHints,
|
|
2997
|
-
...keywordTokens(name, { minLength: 4 }).slice(0, 4).map((token) => `\\b${token.replace(/[.*+?^${}()|[\\]\\]/g, "")}\\b`),
|
|
2998
|
-
]);
|
|
2999
|
-
const promptId = `${id}-prompt`;
|
|
3000
|
-
|
|
3001
|
-
if (importPrompts && body) {
|
|
3002
|
-
upsertEntry(rootDir, {
|
|
3003
|
-
id: promptId,
|
|
3004
|
-
type: "prompt",
|
|
3005
|
-
name: `${name} Prompt`,
|
|
3006
|
-
description: `Imported prompt from ${known?.name || repoUrl}`,
|
|
3007
|
-
tags: uniqueStrings(["imported", "agent-prompt", sourceId || "external"]),
|
|
3008
|
-
meta: {
|
|
3009
|
-
sourceId: sourceId || null,
|
|
3010
|
-
repoUrl,
|
|
3011
|
-
branch,
|
|
3012
|
-
relPath,
|
|
3013
|
-
},
|
|
3014
|
-
}, body);
|
|
3015
|
-
imported.push({ id: promptId, name: `${name} Prompt`, relPath, type: "prompt", promptId: null });
|
|
3016
|
-
importedByType.prompt += 1;
|
|
3017
|
-
}
|
|
3018
|
-
|
|
3019
|
-
const explicitAgentType = String(getFrontmatterValue(attrs, ["agentType", "agent_type"]) || "").trim().toLowerCase();
|
|
3020
|
-
const profile = {
|
|
3021
|
-
id,
|
|
3022
|
-
name,
|
|
3023
|
-
description,
|
|
3024
|
-
titlePatterns: titlePatterns.length ? titlePatterns : ["\\btask\\b"],
|
|
3025
|
-
scopes,
|
|
3026
|
-
sdk: null,
|
|
3027
|
-
model: null,
|
|
3028
|
-
promptOverride: importPrompts && body ? promptId : null,
|
|
3029
|
-
skills: profileSkillHints,
|
|
3030
|
-
hookProfile: null,
|
|
3031
|
-
env: {},
|
|
3032
|
-
enabledTools: toolHints.length ? toolHints : null,
|
|
3033
|
-
enabledMcpServers: mcpHints,
|
|
3034
|
-
tags,
|
|
3035
|
-
agentType: explicitAgentType || (/voice|audio|realtime/i.test(`${name} ${description}`) ? "voice" : "task"),
|
|
3036
|
-
importMeta: {
|
|
3037
|
-
sourceId: sourceId || null,
|
|
3038
|
-
repoUrl,
|
|
3039
|
-
branch,
|
|
3040
|
-
relPath,
|
|
3041
|
-
},
|
|
3042
|
-
};
|
|
3043
|
-
|
|
3044
|
-
upsertEntry(rootDir, {
|
|
3045
|
-
id,
|
|
3046
|
-
type: "agent",
|
|
3047
|
-
name,
|
|
3048
|
-
description,
|
|
3049
|
-
tags: profile.tags,
|
|
3050
|
-
meta: {
|
|
3051
|
-
sourceId: sourceId || null,
|
|
3052
|
-
repoUrl,
|
|
3053
|
-
branch,
|
|
3054
|
-
relPath,
|
|
3055
|
-
},
|
|
3056
|
-
}, profile, { skipIndexSync: true });
|
|
3057
|
-
|
|
3058
|
-
imported.push({ id, name, relPath, type: "agent", promptId: importPrompts && body ? promptId : null });
|
|
3059
|
-
importedByType.agent += 1;
|
|
3060
|
-
needsAgentIndexRefresh = true;
|
|
2569
|
+
const result = importRepositoryMarkdownCandidate(rootDir, candidate, {
|
|
2570
|
+
known,
|
|
2571
|
+
repoUrl,
|
|
2572
|
+
branch,
|
|
2573
|
+
sourceId,
|
|
2574
|
+
importAgents,
|
|
2575
|
+
importSkills,
|
|
2576
|
+
importPrompts,
|
|
2577
|
+
takenIds,
|
|
2578
|
+
imported,
|
|
2579
|
+
importedByType,
|
|
2580
|
+
});
|
|
2581
|
+
needsAgentIndexRefresh = needsAgentIndexRefresh || result.needsAgentIndexRefresh;
|
|
2582
|
+
needsSkillIndexRefresh = needsSkillIndexRefresh || result.needsSkillIndexRefresh;
|
|
3061
2583
|
}
|
|
3062
2584
|
|
|
3063
2585
|
if (importTools) {
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
try {
|
|
3073
|
-
raw = readFileSync(configPath, "utf8");
|
|
3074
|
-
} catch {
|
|
3075
|
-
continue;
|
|
3076
|
-
}
|
|
3077
|
-
const relPath = relative(checkoutDir, configPath).replace(/\\/g, "/");
|
|
3078
|
-
const discovered = format === "toml"
|
|
3079
|
-
? parseMcpServersFromToml(raw, relPath)
|
|
3080
|
-
: parseMcpServersFromJson(raw, relPath);
|
|
3081
|
-
for (const mcp of discovered) {
|
|
3082
|
-
const baseId = slugify(`${sourceId || "imported"}-${mcp.id}`) || slugify(mcp.id) || `imported-mcp-${imported.length + 1}`;
|
|
3083
|
-
const id = ensureUniqueId(baseId, takenIds);
|
|
3084
|
-
const content = {
|
|
3085
|
-
id,
|
|
3086
|
-
name: mcp.name,
|
|
3087
|
-
description: "Imported MCP server definition from " + relPath,
|
|
3088
|
-
transport: mcp.transport,
|
|
3089
|
-
command: mcp.transport === "stdio" ? mcp.command : undefined,
|
|
3090
|
-
args: mcp.transport === "stdio" ? mcp.args : undefined,
|
|
3091
|
-
url: mcp.transport === "url" ? mcp.url : undefined,
|
|
3092
|
-
env: Object.keys(mcp.env || {}).length ? mcp.env : undefined,
|
|
3093
|
-
source: "imported",
|
|
3094
|
-
tags: ["imported", "mcp", sourceId || "external"],
|
|
3095
|
-
};
|
|
3096
|
-
upsertEntry(rootDir, {
|
|
3097
|
-
id,
|
|
3098
|
-
type: "mcp",
|
|
3099
|
-
name: mcp.name,
|
|
3100
|
-
description: content.description,
|
|
3101
|
-
tags: uniqueStrings(["imported", "mcp", sourceId || "external"]),
|
|
3102
|
-
meta: {
|
|
3103
|
-
sourceId: sourceId || null,
|
|
3104
|
-
repoUrl,
|
|
3105
|
-
branch,
|
|
3106
|
-
relPath,
|
|
3107
|
-
},
|
|
3108
|
-
}, content);
|
|
3109
|
-
imported.push({ id, name: mcp.name, relPath, type: "mcp", promptId: null });
|
|
3110
|
-
importedByType.mcp += 1;
|
|
3111
|
-
}
|
|
3112
|
-
}
|
|
2586
|
+
importRepositoryMcpEntries(rootDir, checkoutDir, {
|
|
2587
|
+
sourceId,
|
|
2588
|
+
repoUrl,
|
|
2589
|
+
branch,
|
|
2590
|
+
takenIds,
|
|
2591
|
+
imported,
|
|
2592
|
+
importedByType,
|
|
2593
|
+
});
|
|
3113
2594
|
}
|
|
3114
2595
|
} finally {
|
|
2596
|
+
|
|
3115
2597
|
rmSync(checkoutDir, { recursive: true, force: true });
|
|
3116
2598
|
}
|
|
3117
2599
|
|