agenthud 0.6.1 → 0.6.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/dist/index.js +321 -312
- package/dist/templates/config.yaml +2 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import React3 from "react";
|
|
5
5
|
import { render } from "ink";
|
|
6
|
-
import { existsSync } from "fs";
|
|
6
|
+
import { existsSync as existsSync8 } from "fs";
|
|
7
7
|
|
|
8
8
|
// src/ui/App.tsx
|
|
9
9
|
import React2, { useState as useState4, useEffect as useEffect4, useCallback as useCallback4, useMemo as useMemo3, useRef as useRef3 } from "react";
|
|
@@ -1346,18 +1346,17 @@ function useHotkeys({
|
|
|
1346
1346
|
}
|
|
1347
1347
|
|
|
1348
1348
|
// src/data/git.ts
|
|
1349
|
-
import { execSync
|
|
1349
|
+
import { execSync, exec } from "child_process";
|
|
1350
1350
|
import { promisify } from "util";
|
|
1351
|
-
var execAsync = promisify(
|
|
1351
|
+
var execAsync = promisify(exec);
|
|
1352
1352
|
function cleanOutput(str) {
|
|
1353
1353
|
let result = str.replace(/^\uFEFF/, "");
|
|
1354
1354
|
result = result.replace(/^"|"$/g, "");
|
|
1355
1355
|
return result.trim();
|
|
1356
1356
|
}
|
|
1357
|
-
var execFn = (command, options2) => nodeExecSync(command, options2);
|
|
1358
1357
|
function getUncommittedCount() {
|
|
1359
1358
|
try {
|
|
1360
|
-
const result =
|
|
1359
|
+
const result = execSync("git status --porcelain", {
|
|
1361
1360
|
encoding: "utf-8"
|
|
1362
1361
|
});
|
|
1363
1362
|
const lines = result.trim().split("\n").filter(Boolean);
|
|
@@ -1408,21 +1407,21 @@ function getGitData(config) {
|
|
|
1408
1407
|
};
|
|
1409
1408
|
let branch = null;
|
|
1410
1409
|
try {
|
|
1411
|
-
const result =
|
|
1410
|
+
const result = execSync(commands.branch, { encoding: "utf-8" });
|
|
1412
1411
|
branch = result.trim();
|
|
1413
1412
|
} catch {
|
|
1414
1413
|
branch = null;
|
|
1415
1414
|
}
|
|
1416
1415
|
let commits = [];
|
|
1417
1416
|
try {
|
|
1418
|
-
const result =
|
|
1417
|
+
const result = execSync(commands.commits, { encoding: "utf-8" });
|
|
1419
1418
|
commits = parseCommitsOutput(result);
|
|
1420
1419
|
} catch {
|
|
1421
1420
|
commits = [];
|
|
1422
1421
|
}
|
|
1423
1422
|
let stats = { added: 0, deleted: 0, files: 0 };
|
|
1424
1423
|
try {
|
|
1425
|
-
const result =
|
|
1424
|
+
const result = execSync(commands.stats, { encoding: "utf-8" });
|
|
1426
1425
|
stats = parseStatsOutput(result);
|
|
1427
1426
|
} catch {
|
|
1428
1427
|
stats = { added: 0, deleted: 0, files: 0 };
|
|
@@ -1462,29 +1461,249 @@ async function getGitDataAsync(config) {
|
|
|
1462
1461
|
}
|
|
1463
1462
|
|
|
1464
1463
|
// src/data/tests.ts
|
|
1465
|
-
import { readFileSync as
|
|
1466
|
-
import { execSync as
|
|
1464
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1465
|
+
import { execSync as execSync3 } from "child_process";
|
|
1467
1466
|
import { join } from "path";
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1467
|
+
|
|
1468
|
+
// src/runner/command.ts
|
|
1469
|
+
import { execSync as execSync2 } from "child_process";
|
|
1470
|
+
import {
|
|
1471
|
+
existsSync as existsSync2,
|
|
1472
|
+
unlinkSync,
|
|
1473
|
+
readFileSync as readFileSync2
|
|
1474
|
+
} from "fs";
|
|
1475
|
+
|
|
1476
|
+
// src/data/detectTestFramework.ts
|
|
1477
|
+
import {
|
|
1478
|
+
existsSync,
|
|
1479
|
+
readFileSync
|
|
1480
|
+
} from "fs";
|
|
1481
|
+
var TEST_RESULTS_FILE = ".agenthud/test-results.xml";
|
|
1482
|
+
var FRAMEWORK_COMMANDS = {
|
|
1483
|
+
vitest: `npx vitest run --reporter=junit --outputFile=${TEST_RESULTS_FILE}`,
|
|
1484
|
+
jest: `JEST_JUNIT_OUTPUT_FILE=${TEST_RESULTS_FILE} npx jest --reporters=jest-junit`,
|
|
1485
|
+
mocha: `npx mocha --reporter mocha-junit-reporter --reporter-options mochaFile=${TEST_RESULTS_FILE}`,
|
|
1486
|
+
pytest: `uv run pytest --junitxml=${TEST_RESULTS_FILE}`
|
|
1473
1487
|
};
|
|
1474
|
-
var
|
|
1475
|
-
|
|
1488
|
+
var JS_FRAMEWORKS = ["vitest", "jest", "mocha"];
|
|
1489
|
+
function detectTestFramework() {
|
|
1490
|
+
const jsFramework = detectJsFramework();
|
|
1491
|
+
if (jsFramework) {
|
|
1492
|
+
return jsFramework;
|
|
1493
|
+
}
|
|
1494
|
+
const pythonFramework = detectPythonFramework();
|
|
1495
|
+
if (pythonFramework) {
|
|
1496
|
+
return pythonFramework;
|
|
1497
|
+
}
|
|
1498
|
+
return null;
|
|
1499
|
+
}
|
|
1500
|
+
function detectJsFramework() {
|
|
1501
|
+
if (!existsSync("package.json")) {
|
|
1502
|
+
return null;
|
|
1503
|
+
}
|
|
1504
|
+
let packageJson;
|
|
1505
|
+
try {
|
|
1506
|
+
const content = readFileSync("package.json", "utf-8");
|
|
1507
|
+
packageJson = JSON.parse(content);
|
|
1508
|
+
} catch {
|
|
1509
|
+
return null;
|
|
1510
|
+
}
|
|
1511
|
+
const allDeps = {
|
|
1512
|
+
...packageJson.dependencies,
|
|
1513
|
+
...packageJson.devDependencies
|
|
1514
|
+
};
|
|
1515
|
+
for (const framework of JS_FRAMEWORKS) {
|
|
1516
|
+
if (allDeps[framework]) {
|
|
1517
|
+
return {
|
|
1518
|
+
framework,
|
|
1519
|
+
command: FRAMEWORK_COMMANDS[framework]
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
return null;
|
|
1524
|
+
}
|
|
1525
|
+
function detectPythonFramework() {
|
|
1526
|
+
const pytestIndicators = ["pytest.ini", "conftest.py"];
|
|
1527
|
+
for (const file of pytestIndicators) {
|
|
1528
|
+
if (existsSync(file)) {
|
|
1529
|
+
return {
|
|
1530
|
+
framework: "pytest",
|
|
1531
|
+
command: FRAMEWORK_COMMANDS.pytest
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
if (existsSync("pyproject.toml")) {
|
|
1536
|
+
try {
|
|
1537
|
+
const content = readFileSync("pyproject.toml", "utf-8");
|
|
1538
|
+
if (content.includes("[tool.pytest") || content.includes("[tool.pytest.ini_options]")) {
|
|
1539
|
+
return {
|
|
1540
|
+
framework: "pytest",
|
|
1541
|
+
command: FRAMEWORK_COMMANDS.pytest
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
} catch {
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
const requirementsFiles = ["requirements.txt", "requirements-dev.txt"];
|
|
1548
|
+
for (const file of requirementsFiles) {
|
|
1549
|
+
if (existsSync(file)) {
|
|
1550
|
+
try {
|
|
1551
|
+
const content = readFileSync(file, "utf-8");
|
|
1552
|
+
if (content.includes("pytest")) {
|
|
1553
|
+
return {
|
|
1554
|
+
framework: "pytest",
|
|
1555
|
+
command: FRAMEWORK_COMMANDS.pytest
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
} catch {
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
return null;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// src/runner/command.ts
|
|
1566
|
+
function parseJUnitXml(xml) {
|
|
1567
|
+
try {
|
|
1568
|
+
if (!xml.includes("<testsuite") && !xml.includes("<testsuites")) {
|
|
1569
|
+
return null;
|
|
1570
|
+
}
|
|
1571
|
+
let totalTests = 0;
|
|
1572
|
+
let totalErrors = 0;
|
|
1573
|
+
let totalFailures = 0;
|
|
1574
|
+
let totalSkipped = 0;
|
|
1575
|
+
const failures = [];
|
|
1576
|
+
const testsuiteMatches = xml.match(/<testsuite\b[^>]*(?:\/>|>[\s\S]*?<\/testsuite>)/g) || [];
|
|
1577
|
+
const testsuites = testsuiteMatches.length > 0 ? testsuiteMatches : [xml];
|
|
1578
|
+
for (const suite of testsuites) {
|
|
1579
|
+
const suiteTag = suite.match(/<testsuite[^>]*>/)?.[0] || "";
|
|
1580
|
+
const testsMatch = suiteTag.match(/tests="(\d+)"/);
|
|
1581
|
+
const errorsMatch = suiteTag.match(/errors="(\d+)"/);
|
|
1582
|
+
const failuresMatch = suiteTag.match(/failures="(\d+)"/);
|
|
1583
|
+
const skippedMatch = suiteTag.match(/skipped="(\d+)"/);
|
|
1584
|
+
totalTests += testsMatch ? parseInt(testsMatch[1], 10) : 0;
|
|
1585
|
+
totalErrors += errorsMatch ? parseInt(errorsMatch[1], 10) : 0;
|
|
1586
|
+
totalFailures += failuresMatch ? parseInt(failuresMatch[1], 10) : 0;
|
|
1587
|
+
totalSkipped += skippedMatch ? parseInt(skippedMatch[1], 10) : 0;
|
|
1588
|
+
const testcaseRegex = /<testcase[^>]*classname="([^"]*)"[^>]*name="([^"]*)"[^/>]*(?:\/>|>[\s\S]*?<\/testcase>)/g;
|
|
1589
|
+
let testcaseMatch;
|
|
1590
|
+
while ((testcaseMatch = testcaseRegex.exec(suite)) !== null) {
|
|
1591
|
+
const testcaseContent = testcaseMatch[0];
|
|
1592
|
+
const classname = testcaseMatch[1];
|
|
1593
|
+
const name = testcaseMatch[2];
|
|
1594
|
+
if (testcaseContent.includes("<failure") || testcaseContent.includes("<error")) {
|
|
1595
|
+
failures.push({
|
|
1596
|
+
file: classname,
|
|
1597
|
+
name
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
if (totalTests === 0 && testsuiteMatches.length === 0) {
|
|
1603
|
+
return null;
|
|
1604
|
+
}
|
|
1605
|
+
const failed = totalFailures + totalErrors;
|
|
1606
|
+
const passed = totalTests - failed - totalSkipped;
|
|
1607
|
+
return {
|
|
1608
|
+
passed,
|
|
1609
|
+
failed,
|
|
1610
|
+
skipped: totalSkipped,
|
|
1611
|
+
failures
|
|
1612
|
+
};
|
|
1613
|
+
} catch {
|
|
1614
|
+
return null;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
function getHeadHash() {
|
|
1618
|
+
try {
|
|
1619
|
+
return execSync2("git rev-parse --short HEAD", {
|
|
1620
|
+
encoding: "utf-8",
|
|
1621
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1622
|
+
}).trim();
|
|
1623
|
+
} catch {
|
|
1624
|
+
return "unknown";
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
function runTestCommand(command, source = TEST_RESULTS_FILE) {
|
|
1628
|
+
try {
|
|
1629
|
+
if (existsSync2(source)) {
|
|
1630
|
+
unlinkSync(source);
|
|
1631
|
+
}
|
|
1632
|
+
} catch {
|
|
1633
|
+
}
|
|
1634
|
+
try {
|
|
1635
|
+
execSync2(command, {
|
|
1636
|
+
encoding: "utf-8",
|
|
1637
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1638
|
+
});
|
|
1639
|
+
} catch {
|
|
1640
|
+
}
|
|
1641
|
+
if (!existsSync2(source)) {
|
|
1642
|
+
return {
|
|
1643
|
+
results: null,
|
|
1644
|
+
isOutdated: false,
|
|
1645
|
+
commitsBehind: 0,
|
|
1646
|
+
error: "Test command failed to produce output file"
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
let content;
|
|
1650
|
+
try {
|
|
1651
|
+
content = readFileSync2(source, "utf-8");
|
|
1652
|
+
} catch (error) {
|
|
1653
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1654
|
+
return {
|
|
1655
|
+
results: null,
|
|
1656
|
+
isOutdated: false,
|
|
1657
|
+
commitsBehind: 0,
|
|
1658
|
+
error: `Failed to read result file: ${message}`
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
const parsed = parseJUnitXml(content);
|
|
1662
|
+
if (!parsed) {
|
|
1663
|
+
return {
|
|
1664
|
+
results: null,
|
|
1665
|
+
isOutdated: false,
|
|
1666
|
+
commitsBehind: 0,
|
|
1667
|
+
error: "Failed to parse test results XML"
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
const hash = getHeadHash();
|
|
1671
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1672
|
+
const results = {
|
|
1673
|
+
hash,
|
|
1674
|
+
timestamp,
|
|
1675
|
+
passed: parsed.passed,
|
|
1676
|
+
failed: parsed.failed,
|
|
1677
|
+
skipped: parsed.skipped,
|
|
1678
|
+
failures: parsed.failures
|
|
1679
|
+
};
|
|
1680
|
+
return {
|
|
1681
|
+
results,
|
|
1682
|
+
isOutdated: false,
|
|
1683
|
+
commitsBehind: 0
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
// src/data/tests.ts
|
|
1688
|
+
var AGENT_DIR = ".agenthud";
|
|
1689
|
+
var TEST_RESULTS_FILE2 = "test-results.json";
|
|
1690
|
+
function getHeadHash2() {
|
|
1691
|
+
return execSync3("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
|
|
1692
|
+
}
|
|
1693
|
+
function getCommitCount(fromHash) {
|
|
1694
|
+
const result = execSync3(`git rev-list ${fromHash}..HEAD --count`, {
|
|
1476
1695
|
encoding: "utf-8"
|
|
1477
1696
|
}).trim();
|
|
1478
1697
|
return parseInt(result, 10) || 0;
|
|
1479
|
-
}
|
|
1698
|
+
}
|
|
1480
1699
|
function getTestData(dir = process.cwd()) {
|
|
1481
|
-
const testResultsPath = join(dir, AGENT_DIR,
|
|
1700
|
+
const testResultsPath = join(dir, AGENT_DIR, TEST_RESULTS_FILE2);
|
|
1482
1701
|
let results = null;
|
|
1483
1702
|
let isOutdated = false;
|
|
1484
1703
|
let commitsBehind = 0;
|
|
1485
1704
|
let error;
|
|
1486
1705
|
try {
|
|
1487
|
-
const content =
|
|
1706
|
+
const content = readFileSync3(testResultsPath, "utf-8");
|
|
1488
1707
|
results = JSON.parse(content);
|
|
1489
1708
|
} catch (e) {
|
|
1490
1709
|
if (e instanceof SyntaxError) {
|
|
@@ -1495,10 +1714,10 @@ function getTestData(dir = process.cwd()) {
|
|
|
1495
1714
|
return { results: null, isOutdated: false, commitsBehind: 0, error };
|
|
1496
1715
|
}
|
|
1497
1716
|
try {
|
|
1498
|
-
const currentHash =
|
|
1717
|
+
const currentHash = getHeadHash2();
|
|
1499
1718
|
if (results.hash !== currentHash) {
|
|
1500
1719
|
isOutdated = true;
|
|
1501
|
-
commitsBehind =
|
|
1720
|
+
commitsBehind = getCommitCount(results.hash);
|
|
1502
1721
|
}
|
|
1503
1722
|
} catch {
|
|
1504
1723
|
isOutdated = false;
|
|
@@ -1508,16 +1727,12 @@ function getTestData(dir = process.cwd()) {
|
|
|
1508
1727
|
}
|
|
1509
1728
|
|
|
1510
1729
|
// src/data/project.ts
|
|
1511
|
-
import { execSync as
|
|
1730
|
+
import { execSync as execSync4 } from "child_process";
|
|
1512
1731
|
import {
|
|
1513
|
-
existsSync as
|
|
1514
|
-
readFileSync as
|
|
1515
|
-
readdirSync as nodeReaddirSync
|
|
1732
|
+
existsSync as existsSync3,
|
|
1733
|
+
readFileSync as readFileSync4
|
|
1516
1734
|
} from "fs";
|
|
1517
1735
|
import { basename } from "path";
|
|
1518
|
-
var fileExistsFn = nodeExistsSync;
|
|
1519
|
-
var readFileFn2 = (path) => nodeReadFileSync2(path, "utf-8");
|
|
1520
|
-
var execFn2 = (cmd, opts) => nodeExecSync3(cmd, opts);
|
|
1521
1736
|
var LANGUAGE_INDICATORS = [
|
|
1522
1737
|
{ file: "tsconfig.json", language: "TypeScript" },
|
|
1523
1738
|
{ file: "package.json", language: "JavaScript" },
|
|
@@ -1587,7 +1802,7 @@ var SOURCE_DIRS = ["src", "lib", "app"];
|
|
|
1587
1802
|
var EXCLUDE_DIRS = ["node_modules", "dist", "build", ".git", "__pycache__", "venv", ".venv", "target"];
|
|
1588
1803
|
function detectLanguage() {
|
|
1589
1804
|
for (const { file, language } of LANGUAGE_INDICATORS) {
|
|
1590
|
-
if (
|
|
1805
|
+
if (existsSync3(file)) {
|
|
1591
1806
|
return language;
|
|
1592
1807
|
}
|
|
1593
1808
|
}
|
|
@@ -1716,29 +1931,29 @@ function parseSetupPy(content) {
|
|
|
1716
1931
|
}
|
|
1717
1932
|
function getFolderName() {
|
|
1718
1933
|
try {
|
|
1719
|
-
return
|
|
1934
|
+
return execSync4('basename "$PWD"', { encoding: "utf-8" }).trim();
|
|
1720
1935
|
} catch {
|
|
1721
1936
|
return basename(process.cwd());
|
|
1722
1937
|
}
|
|
1723
1938
|
}
|
|
1724
1939
|
function getProjectInfo() {
|
|
1725
|
-
if (
|
|
1940
|
+
if (existsSync3("package.json")) {
|
|
1726
1941
|
try {
|
|
1727
|
-
const content =
|
|
1942
|
+
const content = readFileSync4("package.json", "utf-8");
|
|
1728
1943
|
return parsePackageJson(content);
|
|
1729
1944
|
} catch {
|
|
1730
1945
|
}
|
|
1731
1946
|
}
|
|
1732
|
-
if (
|
|
1947
|
+
if (existsSync3("pyproject.toml")) {
|
|
1733
1948
|
try {
|
|
1734
|
-
const content =
|
|
1949
|
+
const content = readFileSync4("pyproject.toml", "utf-8");
|
|
1735
1950
|
return parsePyprojectToml(content);
|
|
1736
1951
|
} catch {
|
|
1737
1952
|
}
|
|
1738
1953
|
}
|
|
1739
|
-
if (
|
|
1954
|
+
if (existsSync3("setup.py")) {
|
|
1740
1955
|
try {
|
|
1741
|
-
const content =
|
|
1956
|
+
const content = readFileSync4("setup.py", "utf-8");
|
|
1742
1957
|
return parseSetupPy(content);
|
|
1743
1958
|
} catch {
|
|
1744
1959
|
}
|
|
@@ -1769,7 +1984,7 @@ function detectStack(deps) {
|
|
|
1769
1984
|
}
|
|
1770
1985
|
function findSourceDir() {
|
|
1771
1986
|
for (const dir of SOURCE_DIRS) {
|
|
1772
|
-
if (
|
|
1987
|
+
if (existsSync3(dir)) {
|
|
1773
1988
|
return dir;
|
|
1774
1989
|
}
|
|
1775
1990
|
}
|
|
@@ -1788,7 +2003,7 @@ function countFiles(language) {
|
|
|
1788
2003
|
const excludes = EXCLUDE_DIRS.map((d) => `-path "*/${d}/*"`).join(" -o ");
|
|
1789
2004
|
const namePatterns = config.patterns.map((p) => `-name "${p}"`).join(" -o ");
|
|
1790
2005
|
const cmd = `find ${sourceDir} \\( ${excludes} \\) -prune -o -type f \\( ${namePatterns} \\) -print | wc -l`;
|
|
1791
|
-
const result =
|
|
2006
|
+
const result = execSync4(cmd, { encoding: "utf-8" });
|
|
1792
2007
|
const count = parseInt(result.trim(), 10) || 0;
|
|
1793
2008
|
return { count, extension: config.ext };
|
|
1794
2009
|
} catch {
|
|
@@ -1808,7 +2023,7 @@ function countLines(language) {
|
|
|
1808
2023
|
const excludes = EXCLUDE_DIRS.map((d) => `-path "*/${d}/*"`).join(" -o ");
|
|
1809
2024
|
const namePatterns = config.patterns.map((p) => `-name "${p}"`).join(" -o ");
|
|
1810
2025
|
const cmd = `find ${sourceDir} \\( ${excludes} \\) -prune -o -type f \\( ${namePatterns} \\) -print0 | xargs -0 wc -l 2>/dev/null | tail -1 | awk '{print $1}'`;
|
|
1811
|
-
const result =
|
|
2026
|
+
const result = execSync4(cmd, { encoding: "utf-8" });
|
|
1812
2027
|
return parseInt(result.trim(), 10) || 0;
|
|
1813
2028
|
} catch {
|
|
1814
2029
|
return 0;
|
|
@@ -1851,10 +2066,10 @@ function getProjectData() {
|
|
|
1851
2066
|
|
|
1852
2067
|
// src/data/claude.ts
|
|
1853
2068
|
import {
|
|
1854
|
-
existsSync as
|
|
1855
|
-
readFileSync as
|
|
1856
|
-
readdirSync as
|
|
1857
|
-
statSync
|
|
2069
|
+
existsSync as existsSync4,
|
|
2070
|
+
readFileSync as readFileSync5,
|
|
2071
|
+
readdirSync as readdirSync2,
|
|
2072
|
+
statSync
|
|
1858
2073
|
} from "fs";
|
|
1859
2074
|
import { homedir } from "os";
|
|
1860
2075
|
import { join as join2, basename as basename2 } from "path";
|
|
@@ -1881,12 +2096,6 @@ var ICONS = {
|
|
|
1881
2096
|
};
|
|
1882
2097
|
|
|
1883
2098
|
// src/data/claude.ts
|
|
1884
|
-
var fs = {
|
|
1885
|
-
existsSync: nodeExistsSync2,
|
|
1886
|
-
readFileSync: (path) => nodeReadFileSync3(path, "utf-8"),
|
|
1887
|
-
readdirSync: (path) => nodeReaddirSync2(path),
|
|
1888
|
-
statSync: nodeStatSync
|
|
1889
|
-
};
|
|
1890
2099
|
var MAX_LINES_TO_SCAN = 200;
|
|
1891
2100
|
var DEFAULT_MAX_ACTIVITIES = 10;
|
|
1892
2101
|
function getClaudeSessionPath(projectPath) {
|
|
@@ -1894,10 +2103,10 @@ function getClaudeSessionPath(projectPath) {
|
|
|
1894
2103
|
return join2(homedir(), ".claude", "projects", encoded);
|
|
1895
2104
|
}
|
|
1896
2105
|
function findActiveSession(sessionDir, sessionTimeout) {
|
|
1897
|
-
if (!
|
|
2106
|
+
if (!existsSync4(sessionDir)) {
|
|
1898
2107
|
return null;
|
|
1899
2108
|
}
|
|
1900
|
-
const files =
|
|
2109
|
+
const files = readdirSync2(sessionDir);
|
|
1901
2110
|
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
1902
2111
|
if (jsonlFiles.length === 0) {
|
|
1903
2112
|
return null;
|
|
@@ -1907,7 +2116,7 @@ function findActiveSession(sessionDir, sessionTimeout) {
|
|
|
1907
2116
|
let latestSize = 0;
|
|
1908
2117
|
for (const file of jsonlFiles) {
|
|
1909
2118
|
const filePath = join2(sessionDir, file);
|
|
1910
|
-
const stat =
|
|
2119
|
+
const stat = statSync(filePath);
|
|
1911
2120
|
if (stat.mtimeMs > latestMtime || stat.mtimeMs === latestMtime && stat.size > latestSize) {
|
|
1912
2121
|
latestMtime = stat.mtimeMs;
|
|
1913
2122
|
latestSize = stat.size;
|
|
@@ -1947,12 +2156,12 @@ function parseSessionState(sessionFile, maxActivities = DEFAULT_MAX_ACTIVITIES)
|
|
|
1947
2156
|
sessionStartTime: null,
|
|
1948
2157
|
todos: null
|
|
1949
2158
|
};
|
|
1950
|
-
if (!
|
|
2159
|
+
if (!existsSync4(sessionFile)) {
|
|
1951
2160
|
return defaultState;
|
|
1952
2161
|
}
|
|
1953
2162
|
let content;
|
|
1954
2163
|
try {
|
|
1955
|
-
content =
|
|
2164
|
+
content = readFileSync5(sessionFile, "utf-8");
|
|
1956
2165
|
} catch {
|
|
1957
2166
|
return defaultState;
|
|
1958
2167
|
}
|
|
@@ -2090,13 +2299,13 @@ function parseSessionState(sessionFile, maxActivities = DEFAULT_MAX_ACTIVITIES)
|
|
|
2090
2299
|
}
|
|
2091
2300
|
}
|
|
2092
2301
|
const subagentsDir = join2(sessionFile.replace(/\.jsonl$/, ""), "subagents");
|
|
2093
|
-
if (
|
|
2302
|
+
if (existsSync4(subagentsDir)) {
|
|
2094
2303
|
try {
|
|
2095
|
-
const subagentFiles =
|
|
2304
|
+
const subagentFiles = readdirSync2(subagentsDir).filter((f) => f.endsWith(".jsonl"));
|
|
2096
2305
|
for (const file of subagentFiles) {
|
|
2097
2306
|
const filePath = join2(subagentsDir, file);
|
|
2098
2307
|
try {
|
|
2099
|
-
const subContent =
|
|
2308
|
+
const subContent = readFileSync5(filePath, "utf-8");
|
|
2100
2309
|
const subLines = subContent.trim().split("\n").filter(Boolean);
|
|
2101
2310
|
for (const line of subLines) {
|
|
2102
2311
|
try {
|
|
@@ -2133,7 +2342,7 @@ function getClaudeData(projectPath, maxActivities, sessionTimeout = DEFAULT_SESS
|
|
|
2133
2342
|
};
|
|
2134
2343
|
try {
|
|
2135
2344
|
const sessionDir = getClaudeSessionPath(projectPath);
|
|
2136
|
-
const hasSession =
|
|
2345
|
+
const hasSession = existsSync4(sessionDir);
|
|
2137
2346
|
const sessionFile = findActiveSession(sessionDir, sessionTimeout);
|
|
2138
2347
|
if (!sessionFile) {
|
|
2139
2348
|
return {
|
|
@@ -2161,26 +2370,20 @@ function getClaudeData(projectPath, maxActivities, sessionTimeout = DEFAULT_SESS
|
|
|
2161
2370
|
|
|
2162
2371
|
// src/data/otherSessions.ts
|
|
2163
2372
|
import {
|
|
2164
|
-
existsSync as
|
|
2165
|
-
readFileSync as
|
|
2166
|
-
readdirSync as
|
|
2167
|
-
statSync as
|
|
2373
|
+
existsSync as existsSync5,
|
|
2374
|
+
readFileSync as readFileSync6,
|
|
2375
|
+
readdirSync as readdirSync3,
|
|
2376
|
+
statSync as statSync2
|
|
2168
2377
|
} from "fs";
|
|
2169
2378
|
import { homedir as homedir2 } from "os";
|
|
2170
2379
|
import { join as join3, basename as basename3, sep } from "path";
|
|
2171
|
-
var fs2 = {
|
|
2172
|
-
existsSync: nodeExistsSync3,
|
|
2173
|
-
readFileSync: (path) => nodeReadFileSync4(path, "utf-8"),
|
|
2174
|
-
readdirSync: (path) => nodeReaddirSync3(path),
|
|
2175
|
-
statSync: nodeStatSync2
|
|
2176
|
-
};
|
|
2177
2380
|
var MAX_LINES_TO_SCAN2 = 100;
|
|
2178
2381
|
function getProjectsDir() {
|
|
2179
2382
|
return join3(homedir2(), ".claude", "projects");
|
|
2180
2383
|
}
|
|
2181
2384
|
function decodeProjectPath(encoded) {
|
|
2182
2385
|
const naiveDecoded = encoded.replace(/-/g, sep);
|
|
2183
|
-
if (
|
|
2386
|
+
if (existsSync5(naiveDecoded)) {
|
|
2184
2387
|
return naiveDecoded;
|
|
2185
2388
|
}
|
|
2186
2389
|
const segments = encoded.split("-").filter(Boolean);
|
|
@@ -2195,9 +2398,9 @@ function decodeProjectPath(encoded) {
|
|
|
2195
2398
|
const segment = segments.slice(i, j).join("-");
|
|
2196
2399
|
const testPath = currentPath ? join3(currentPath, segment) : sep + segment;
|
|
2197
2400
|
try {
|
|
2198
|
-
if (
|
|
2199
|
-
const stat =
|
|
2200
|
-
if (stat.isDirectory
|
|
2401
|
+
if (existsSync5(testPath)) {
|
|
2402
|
+
const stat = statSync2(testPath);
|
|
2403
|
+
if (stat.isDirectory()) {
|
|
2201
2404
|
currentPath = testPath;
|
|
2202
2405
|
i = j;
|
|
2203
2406
|
found = true;
|
|
@@ -2216,16 +2419,16 @@ function decodeProjectPath(encoded) {
|
|
|
2216
2419
|
}
|
|
2217
2420
|
function getAllProjects() {
|
|
2218
2421
|
const projectsDir = getProjectsDir();
|
|
2219
|
-
if (!
|
|
2422
|
+
if (!existsSync5(projectsDir)) {
|
|
2220
2423
|
return [];
|
|
2221
2424
|
}
|
|
2222
|
-
const entries =
|
|
2425
|
+
const entries = readdirSync3(projectsDir);
|
|
2223
2426
|
const projects = [];
|
|
2224
2427
|
for (const entry of entries) {
|
|
2225
2428
|
const fullPath = join3(projectsDir, entry);
|
|
2226
2429
|
try {
|
|
2227
|
-
const stat =
|
|
2228
|
-
if (stat.isDirectory
|
|
2430
|
+
const stat = statSync2(fullPath);
|
|
2431
|
+
if (stat.isDirectory()) {
|
|
2229
2432
|
projects.push({
|
|
2230
2433
|
encodedPath: entry,
|
|
2231
2434
|
decodedPath: decodeProjectPath(entry)
|
|
@@ -2237,12 +2440,12 @@ function getAllProjects() {
|
|
|
2237
2440
|
return projects;
|
|
2238
2441
|
}
|
|
2239
2442
|
function parseLastAssistantMessage(sessionFile) {
|
|
2240
|
-
if (!
|
|
2443
|
+
if (!existsSync5(sessionFile)) {
|
|
2241
2444
|
return null;
|
|
2242
2445
|
}
|
|
2243
2446
|
let content;
|
|
2244
2447
|
try {
|
|
2245
|
-
content =
|
|
2448
|
+
content = readFileSync6(sessionFile, "utf-8");
|
|
2246
2449
|
} catch {
|
|
2247
2450
|
return null;
|
|
2248
2451
|
}
|
|
@@ -2290,12 +2493,12 @@ function formatRelativeTime2(date) {
|
|
|
2290
2493
|
return `${days}d ago`;
|
|
2291
2494
|
}
|
|
2292
2495
|
function findMostRecentSession(projectDir) {
|
|
2293
|
-
if (!
|
|
2496
|
+
if (!existsSync5(projectDir)) {
|
|
2294
2497
|
return null;
|
|
2295
2498
|
}
|
|
2296
2499
|
let files;
|
|
2297
2500
|
try {
|
|
2298
|
-
files =
|
|
2501
|
+
files = readdirSync3(projectDir);
|
|
2299
2502
|
} catch {
|
|
2300
2503
|
return null;
|
|
2301
2504
|
}
|
|
@@ -2308,7 +2511,7 @@ function findMostRecentSession(projectDir) {
|
|
|
2308
2511
|
for (const file of jsonlFiles) {
|
|
2309
2512
|
const filePath = join3(projectDir, file);
|
|
2310
2513
|
try {
|
|
2311
|
-
const stat =
|
|
2514
|
+
const stat = statSync2(filePath);
|
|
2312
2515
|
if (stat.mtimeMs && stat.mtimeMs > latestMtime) {
|
|
2313
2516
|
latestMtime = stat.mtimeMs;
|
|
2314
2517
|
latestFile = filePath;
|
|
@@ -2331,7 +2534,7 @@ function getOtherSessionsData(currentProjectPath, options2 = {}) {
|
|
|
2331
2534
|
recentSession: null,
|
|
2332
2535
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2333
2536
|
};
|
|
2334
|
-
if (!
|
|
2537
|
+
if (!existsSync5(projectsDir)) {
|
|
2335
2538
|
return defaultResult;
|
|
2336
2539
|
}
|
|
2337
2540
|
const allProjects = getAllProjects();
|
|
@@ -2388,12 +2591,10 @@ function getOtherSessionsData(currentProjectPath, options2 = {}) {
|
|
|
2388
2591
|
}
|
|
2389
2592
|
|
|
2390
2593
|
// src/data/custom.ts
|
|
2391
|
-
import { execSync as
|
|
2392
|
-
import { readFileSync as
|
|
2594
|
+
import { execSync as execSync5, exec as exec2 } from "child_process";
|
|
2595
|
+
import { readFileSync as readFileSync7, promises as fsPromises } from "fs";
|
|
2393
2596
|
import { promisify as promisify2 } from "util";
|
|
2394
|
-
var execAsync2 = promisify2(
|
|
2395
|
-
var execFn3 = (cmd, options2) => nodeExecSync4(cmd, options2);
|
|
2396
|
-
var readFileFn3 = (path) => nodeReadFileSync5(path, "utf-8");
|
|
2597
|
+
var execAsync2 = promisify2(exec2);
|
|
2397
2598
|
function capitalizeFirst(str) {
|
|
2398
2599
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
2399
2600
|
}
|
|
@@ -2404,7 +2605,7 @@ function getCustomPanelData(name, panelConfig) {
|
|
|
2404
2605
|
};
|
|
2405
2606
|
if (panelConfig.command) {
|
|
2406
2607
|
try {
|
|
2407
|
-
const output =
|
|
2608
|
+
const output = execSync5(panelConfig.command, { encoding: "utf-8" }).trim();
|
|
2408
2609
|
try {
|
|
2409
2610
|
const parsed = JSON.parse(output);
|
|
2410
2611
|
return {
|
|
@@ -2438,7 +2639,7 @@ function getCustomPanelData(name, panelConfig) {
|
|
|
2438
2639
|
}
|
|
2439
2640
|
if (panelConfig.source) {
|
|
2440
2641
|
try {
|
|
2441
|
-
const content =
|
|
2642
|
+
const content = readFileSync7(panelConfig.source, "utf-8");
|
|
2442
2643
|
const parsed = JSON.parse(content);
|
|
2443
2644
|
return {
|
|
2444
2645
|
data: {
|
|
@@ -2549,98 +2750,12 @@ async function getCustomPanelDataAsync(name, panelConfig) {
|
|
|
2549
2750
|
};
|
|
2550
2751
|
}
|
|
2551
2752
|
|
|
2552
|
-
// src/runner/command.ts
|
|
2553
|
-
import { execSync as nodeExecSync5 } from "child_process";
|
|
2554
|
-
var execFn4 = (command, options2) => nodeExecSync5(command, options2);
|
|
2555
|
-
function parseVitestOutput(output) {
|
|
2556
|
-
try {
|
|
2557
|
-
const data = JSON.parse(output);
|
|
2558
|
-
if (typeof data.numPassedTests !== "number" || typeof data.numFailedTests !== "number") {
|
|
2559
|
-
return null;
|
|
2560
|
-
}
|
|
2561
|
-
const failures = [];
|
|
2562
|
-
for (const testResult of data.testResults || []) {
|
|
2563
|
-
for (const assertion of testResult.assertionResults || []) {
|
|
2564
|
-
if (assertion.status === "failed") {
|
|
2565
|
-
failures.push({
|
|
2566
|
-
file: testResult.name,
|
|
2567
|
-
name: assertion.title
|
|
2568
|
-
});
|
|
2569
|
-
}
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
return {
|
|
2573
|
-
passed: data.numPassedTests,
|
|
2574
|
-
failed: data.numFailedTests,
|
|
2575
|
-
skipped: data.numPendingTests || 0,
|
|
2576
|
-
failures
|
|
2577
|
-
};
|
|
2578
|
-
} catch {
|
|
2579
|
-
return null;
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
|
-
function getHeadHash() {
|
|
2583
|
-
try {
|
|
2584
|
-
return execFn4("git rev-parse --short HEAD", {
|
|
2585
|
-
encoding: "utf-8",
|
|
2586
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
2587
|
-
}).trim();
|
|
2588
|
-
} catch {
|
|
2589
|
-
return "unknown";
|
|
2590
|
-
}
|
|
2591
|
-
}
|
|
2592
|
-
function runTestCommand(command) {
|
|
2593
|
-
let output;
|
|
2594
|
-
try {
|
|
2595
|
-
output = execFn4(command, {
|
|
2596
|
-
encoding: "utf-8",
|
|
2597
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
2598
|
-
});
|
|
2599
|
-
} catch (error) {
|
|
2600
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2601
|
-
return {
|
|
2602
|
-
results: null,
|
|
2603
|
-
isOutdated: false,
|
|
2604
|
-
commitsBehind: 0,
|
|
2605
|
-
error: message
|
|
2606
|
-
};
|
|
2607
|
-
}
|
|
2608
|
-
const parsed = parseVitestOutput(output);
|
|
2609
|
-
if (!parsed) {
|
|
2610
|
-
return {
|
|
2611
|
-
results: null,
|
|
2612
|
-
isOutdated: false,
|
|
2613
|
-
commitsBehind: 0,
|
|
2614
|
-
error: "Failed to parse test output"
|
|
2615
|
-
};
|
|
2616
|
-
}
|
|
2617
|
-
const hash = getHeadHash();
|
|
2618
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2619
|
-
const results = {
|
|
2620
|
-
hash,
|
|
2621
|
-
timestamp,
|
|
2622
|
-
passed: parsed.passed,
|
|
2623
|
-
failed: parsed.failed,
|
|
2624
|
-
skipped: parsed.skipped,
|
|
2625
|
-
failures: parsed.failures
|
|
2626
|
-
};
|
|
2627
|
-
return {
|
|
2628
|
-
results,
|
|
2629
|
-
isOutdated: false,
|
|
2630
|
-
commitsBehind: 0
|
|
2631
|
-
};
|
|
2632
|
-
}
|
|
2633
|
-
|
|
2634
2753
|
// src/config/parser.ts
|
|
2635
2754
|
import {
|
|
2636
|
-
existsSync as
|
|
2637
|
-
readFileSync as
|
|
2755
|
+
existsSync as existsSync6,
|
|
2756
|
+
readFileSync as readFileSync8
|
|
2638
2757
|
} from "fs";
|
|
2639
2758
|
import { parse as parseYaml } from "yaml";
|
|
2640
|
-
var fs3 = {
|
|
2641
|
-
existsSync: nodeExistsSync4,
|
|
2642
|
-
readFileSync: (path) => nodeReadFileSync6(path, "utf-8")
|
|
2643
|
-
};
|
|
2644
2759
|
var DEFAULT_WIDTH = DEFAULT_PANEL_WIDTH;
|
|
2645
2760
|
var MIN_WIDTH = MIN_TERMINAL_WIDTH;
|
|
2646
2761
|
var MAX_WIDTH = MAX_TERMINAL_WIDTH;
|
|
@@ -2718,12 +2833,12 @@ function parseBasePanelConfig(panelConfig, targetConfig, panelName, warnings) {
|
|
|
2718
2833
|
function parseConfig() {
|
|
2719
2834
|
const warnings = [];
|
|
2720
2835
|
const defaultConfig = getDefaultConfig();
|
|
2721
|
-
if (!
|
|
2836
|
+
if (!existsSync6(CONFIG_PATH)) {
|
|
2722
2837
|
return { config: defaultConfig, warnings };
|
|
2723
2838
|
}
|
|
2724
2839
|
let rawConfig;
|
|
2725
2840
|
try {
|
|
2726
|
-
const content =
|
|
2841
|
+
const content = readFileSync8(CONFIG_PATH, "utf-8");
|
|
2727
2842
|
rawConfig = parseYaml(content);
|
|
2728
2843
|
} catch (error) {
|
|
2729
2844
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2838,7 +2953,7 @@ function parseConfig() {
|
|
|
2838
2953
|
}
|
|
2839
2954
|
|
|
2840
2955
|
// src/cli.ts
|
|
2841
|
-
import { readFileSync } from "fs";
|
|
2956
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
2842
2957
|
import { dirname, join as join4 } from "path";
|
|
2843
2958
|
import { fileURLToPath } from "url";
|
|
2844
2959
|
function getHelp() {
|
|
@@ -2857,13 +2972,12 @@ Options:
|
|
|
2857
2972
|
function getVersion() {
|
|
2858
2973
|
const __dirname3 = dirname(fileURLToPath(import.meta.url));
|
|
2859
2974
|
const packageJson = JSON.parse(
|
|
2860
|
-
|
|
2975
|
+
readFileSync9(join4(__dirname3, "..", "package.json"), "utf-8")
|
|
2861
2976
|
);
|
|
2862
2977
|
return packageJson.version;
|
|
2863
2978
|
}
|
|
2864
|
-
var clearFn = () => console.clear();
|
|
2865
2979
|
function clearScreen() {
|
|
2866
|
-
|
|
2980
|
+
console.clear();
|
|
2867
2981
|
}
|
|
2868
2982
|
function parseArgs(args) {
|
|
2869
2983
|
const hasOnce = args.includes("--once");
|
|
@@ -2985,7 +3099,7 @@ function DashboardApp({ mode }) {
|
|
|
2985
3099
|
}, [config.panels.tests.command]);
|
|
2986
3100
|
const initialTestData = useMemo3(() => getTestDataFromConfig(), [getTestDataFromConfig]);
|
|
2987
3101
|
const [testsDisabled, setTestsDisabled] = useState4(
|
|
2988
|
-
!!(initialTestData.error && config.panels.tests.command)
|
|
3102
|
+
!!(initialTestData.error && !config.panels.tests.command)
|
|
2989
3103
|
);
|
|
2990
3104
|
const [testData, setTestData] = useState4(initialTestData);
|
|
2991
3105
|
const [customPanelData, setCustomPanelData] = useState4(() => {
|
|
@@ -3272,124 +3386,23 @@ function App({ mode, agentDirExists: agentDirExists2 = true }) {
|
|
|
3272
3386
|
|
|
3273
3387
|
// src/commands/init.ts
|
|
3274
3388
|
import {
|
|
3275
|
-
existsSync as
|
|
3276
|
-
mkdirSync
|
|
3277
|
-
writeFileSync
|
|
3278
|
-
readFileSync as
|
|
3279
|
-
appendFileSync
|
|
3389
|
+
existsSync as existsSync7,
|
|
3390
|
+
mkdirSync,
|
|
3391
|
+
writeFileSync,
|
|
3392
|
+
readFileSync as readFileSync10,
|
|
3393
|
+
appendFileSync
|
|
3280
3394
|
} from "fs";
|
|
3281
3395
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3282
3396
|
import { dirname as dirname2, join as join5 } from "path";
|
|
3283
3397
|
import { homedir as homedir3 } from "os";
|
|
3284
|
-
|
|
3285
|
-
// src/data/detectTestFramework.ts
|
|
3286
|
-
import {
|
|
3287
|
-
existsSync as nodeExistsSync5,
|
|
3288
|
-
readFileSync as nodeReadFileSync7
|
|
3289
|
-
} from "fs";
|
|
3290
|
-
var fs4 = {
|
|
3291
|
-
existsSync: nodeExistsSync5,
|
|
3292
|
-
readFileSync: (path) => nodeReadFileSync7(path, "utf-8")
|
|
3293
|
-
};
|
|
3294
|
-
var FRAMEWORK_COMMANDS = {
|
|
3295
|
-
vitest: "npx vitest run --reporter=json",
|
|
3296
|
-
jest: "npx jest --json",
|
|
3297
|
-
mocha: "npx mocha --reporter=json",
|
|
3298
|
-
pytest: "pytest --json-report --json-report-file=.agenthud/test-results.json"
|
|
3299
|
-
};
|
|
3300
|
-
var JS_FRAMEWORKS = ["vitest", "jest", "mocha"];
|
|
3301
|
-
function detectTestFramework() {
|
|
3302
|
-
const jsFramework = detectJsFramework();
|
|
3303
|
-
if (jsFramework) {
|
|
3304
|
-
return jsFramework;
|
|
3305
|
-
}
|
|
3306
|
-
const pythonFramework = detectPythonFramework();
|
|
3307
|
-
if (pythonFramework) {
|
|
3308
|
-
return pythonFramework;
|
|
3309
|
-
}
|
|
3310
|
-
return null;
|
|
3311
|
-
}
|
|
3312
|
-
function detectJsFramework() {
|
|
3313
|
-
if (!fs4.existsSync("package.json")) {
|
|
3314
|
-
return null;
|
|
3315
|
-
}
|
|
3316
|
-
let packageJson;
|
|
3317
|
-
try {
|
|
3318
|
-
const content = fs4.readFileSync("package.json");
|
|
3319
|
-
packageJson = JSON.parse(content);
|
|
3320
|
-
} catch {
|
|
3321
|
-
return null;
|
|
3322
|
-
}
|
|
3323
|
-
const allDeps = {
|
|
3324
|
-
...packageJson.dependencies,
|
|
3325
|
-
...packageJson.devDependencies
|
|
3326
|
-
};
|
|
3327
|
-
for (const framework of JS_FRAMEWORKS) {
|
|
3328
|
-
if (allDeps[framework]) {
|
|
3329
|
-
return {
|
|
3330
|
-
framework,
|
|
3331
|
-
command: FRAMEWORK_COMMANDS[framework]
|
|
3332
|
-
};
|
|
3333
|
-
}
|
|
3334
|
-
}
|
|
3335
|
-
return null;
|
|
3336
|
-
}
|
|
3337
|
-
function detectPythonFramework() {
|
|
3338
|
-
const pytestIndicators = ["pytest.ini", "conftest.py"];
|
|
3339
|
-
for (const file of pytestIndicators) {
|
|
3340
|
-
if (fs4.existsSync(file)) {
|
|
3341
|
-
return {
|
|
3342
|
-
framework: "pytest",
|
|
3343
|
-
command: FRAMEWORK_COMMANDS.pytest
|
|
3344
|
-
};
|
|
3345
|
-
}
|
|
3346
|
-
}
|
|
3347
|
-
if (fs4.existsSync("pyproject.toml")) {
|
|
3348
|
-
try {
|
|
3349
|
-
const content = fs4.readFileSync("pyproject.toml");
|
|
3350
|
-
if (content.includes("[tool.pytest") || content.includes("[tool.pytest.ini_options]")) {
|
|
3351
|
-
return {
|
|
3352
|
-
framework: "pytest",
|
|
3353
|
-
command: FRAMEWORK_COMMANDS.pytest
|
|
3354
|
-
};
|
|
3355
|
-
}
|
|
3356
|
-
} catch {
|
|
3357
|
-
}
|
|
3358
|
-
}
|
|
3359
|
-
const requirementsFiles = ["requirements.txt", "requirements-dev.txt"];
|
|
3360
|
-
for (const file of requirementsFiles) {
|
|
3361
|
-
if (fs4.existsSync(file)) {
|
|
3362
|
-
try {
|
|
3363
|
-
const content = fs4.readFileSync(file);
|
|
3364
|
-
if (content.includes("pytest")) {
|
|
3365
|
-
return {
|
|
3366
|
-
framework: "pytest",
|
|
3367
|
-
command: FRAMEWORK_COMMANDS.pytest
|
|
3368
|
-
};
|
|
3369
|
-
}
|
|
3370
|
-
} catch {
|
|
3371
|
-
}
|
|
3372
|
-
}
|
|
3373
|
-
}
|
|
3374
|
-
return null;
|
|
3375
|
-
}
|
|
3376
|
-
|
|
3377
|
-
// src/commands/init.ts
|
|
3378
|
-
var fs5 = {
|
|
3379
|
-
existsSync: nodeExistsSync6,
|
|
3380
|
-
mkdirSync: nodeMkdirSync,
|
|
3381
|
-
writeFileSync: nodeWriteFileSync,
|
|
3382
|
-
readFileSync: (path) => nodeReadFileSync8(path, "utf-8"),
|
|
3383
|
-
appendFileSync: nodeAppendFileSync
|
|
3384
|
-
};
|
|
3385
3398
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
3386
3399
|
var __dirname2 = dirname2(__filename2);
|
|
3387
3400
|
function getDefaultConfig2() {
|
|
3388
3401
|
let templatePath = join5(__dirname2, "templates", "config.yaml");
|
|
3389
|
-
if (!
|
|
3402
|
+
if (!existsSync7(templatePath)) {
|
|
3390
3403
|
templatePath = join5(__dirname2, "..", "templates", "config.yaml");
|
|
3391
3404
|
}
|
|
3392
|
-
return
|
|
3405
|
+
return readFileSync10(templatePath, "utf-8");
|
|
3393
3406
|
}
|
|
3394
3407
|
function getClaudeSessionPath2(projectPath) {
|
|
3395
3408
|
const encoded = projectPath.replace(/[/\\]/g, "-");
|
|
@@ -3401,14 +3414,14 @@ function runInit(cwd = process.cwd()) {
|
|
|
3401
3414
|
skipped: [],
|
|
3402
3415
|
warnings: []
|
|
3403
3416
|
};
|
|
3404
|
-
if (!
|
|
3405
|
-
|
|
3417
|
+
if (!existsSync7(".agenthud")) {
|
|
3418
|
+
mkdirSync(".agenthud", { recursive: true });
|
|
3406
3419
|
result.created.push(".agenthud/");
|
|
3407
3420
|
} else {
|
|
3408
3421
|
result.skipped.push(".agenthud/");
|
|
3409
3422
|
}
|
|
3410
|
-
if (!
|
|
3411
|
-
|
|
3423
|
+
if (!existsSync7(".agenthud/tests")) {
|
|
3424
|
+
mkdirSync(".agenthud/tests", { recursive: true });
|
|
3412
3425
|
result.created.push(".agenthud/tests/");
|
|
3413
3426
|
} else {
|
|
3414
3427
|
result.skipped.push(".agenthud/tests/");
|
|
@@ -3417,7 +3430,7 @@ function runInit(cwd = process.cwd()) {
|
|
|
3417
3430
|
if (testFramework) {
|
|
3418
3431
|
result.detectedTestFramework = testFramework.framework;
|
|
3419
3432
|
}
|
|
3420
|
-
if (!
|
|
3433
|
+
if (!existsSync7(".agenthud/config.yaml")) {
|
|
3421
3434
|
let configContent = getDefaultConfig2();
|
|
3422
3435
|
if (testFramework) {
|
|
3423
3436
|
configContent = configContent.replace(
|
|
@@ -3430,28 +3443,28 @@ function runInit(cwd = process.cwd()) {
|
|
|
3430
3443
|
"# command: (auto-detect failed - configure manually)"
|
|
3431
3444
|
);
|
|
3432
3445
|
}
|
|
3433
|
-
|
|
3446
|
+
writeFileSync(".agenthud/config.yaml", configContent);
|
|
3434
3447
|
result.created.push(".agenthud/config.yaml");
|
|
3435
3448
|
} else {
|
|
3436
3449
|
result.skipped.push(".agenthud/config.yaml");
|
|
3437
3450
|
}
|
|
3438
|
-
if (!
|
|
3439
|
-
|
|
3451
|
+
if (!existsSync7(".gitignore")) {
|
|
3452
|
+
writeFileSync(".gitignore", ".agenthud/\n");
|
|
3440
3453
|
result.created.push(".gitignore");
|
|
3441
3454
|
} else {
|
|
3442
|
-
const content =
|
|
3455
|
+
const content = readFileSync10(".gitignore", "utf-8");
|
|
3443
3456
|
if (!content.includes(".agenthud/")) {
|
|
3444
|
-
|
|
3457
|
+
appendFileSync(".gitignore", "\n.agenthud/\n");
|
|
3445
3458
|
result.created.push(".gitignore");
|
|
3446
3459
|
} else {
|
|
3447
3460
|
result.skipped.push(".gitignore");
|
|
3448
3461
|
}
|
|
3449
3462
|
}
|
|
3450
|
-
if (!
|
|
3463
|
+
if (!existsSync7(".git")) {
|
|
3451
3464
|
result.warnings.push("Not a git repository - Git panel will show limited info");
|
|
3452
3465
|
}
|
|
3453
3466
|
const claudeSessionPath = getClaudeSessionPath2(cwd);
|
|
3454
|
-
if (!
|
|
3467
|
+
if (!existsSync7(claudeSessionPath)) {
|
|
3455
3468
|
result.warnings.push("No Claude session found - start Claude to see activity");
|
|
3456
3469
|
}
|
|
3457
3470
|
return result;
|
|
@@ -3460,14 +3473,10 @@ function runInit(cwd = process.cwd()) {
|
|
|
3460
3473
|
// src/utils/performance.ts
|
|
3461
3474
|
import { performance } from "perf_hooks";
|
|
3462
3475
|
var DEFAULT_CLEANUP_INTERVAL = 6e4;
|
|
3463
|
-
var perfFunctions = {
|
|
3464
|
-
clearMarks: () => performance.clearMarks(),
|
|
3465
|
-
clearMeasures: () => performance.clearMeasures()
|
|
3466
|
-
};
|
|
3467
3476
|
var cleanupInterval = null;
|
|
3468
3477
|
function clearPerformanceEntries() {
|
|
3469
|
-
|
|
3470
|
-
|
|
3478
|
+
performance.clearMarks();
|
|
3479
|
+
performance.clearMeasures();
|
|
3471
3480
|
}
|
|
3472
3481
|
function startPerformanceCleanup(intervalMs = DEFAULT_CLEANUP_INTERVAL) {
|
|
3473
3482
|
if (cleanupInterval !== null) {
|
|
@@ -3513,7 +3522,7 @@ if (options.command === "init") {
|
|
|
3513
3522
|
console.log(" Run: npx agenthud\n");
|
|
3514
3523
|
process.exit(0);
|
|
3515
3524
|
}
|
|
3516
|
-
var agentDirExists =
|
|
3525
|
+
var agentDirExists = existsSync8(".agenthud");
|
|
3517
3526
|
if (options.mode === "watch") {
|
|
3518
3527
|
clearScreen();
|
|
3519
3528
|
}
|
|
@@ -21,7 +21,8 @@ panels:
|
|
|
21
21
|
enabled: true
|
|
22
22
|
interval: manual
|
|
23
23
|
# command is auto-detected from: vitest, jest, mocha, pytest
|
|
24
|
-
|
|
24
|
+
# all frameworks output JUnit XML to .agenthud/test-results.xml
|
|
25
|
+
command: npx vitest run --reporter=junit --outputFile=.agenthud/test-results.xml
|
|
25
26
|
|
|
26
27
|
project:
|
|
27
28
|
enabled: true
|
package/package.json
CHANGED