copilot-agent 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +721 -97
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -747,9 +747,9 @@ function showActive(agentFilter) {
|
|
|
747
747
|
${BOLD}${"Agent".padEnd(9)} ${"PID".padEnd(8)} ${"Session".padEnd(40)} Command${RESET}`);
|
|
748
748
|
log("\u2500".repeat(118));
|
|
749
749
|
for (const p of procs) {
|
|
750
|
-
const
|
|
750
|
+
const agentLabel3 = p.agent === "claude" ? `${CYAN}claude${RESET} ` : `${GREEN}copilot${RESET}`;
|
|
751
751
|
log(
|
|
752
|
-
`${
|
|
752
|
+
`${agentLabel3.padEnd(9 + 9)} ${String(p.pid).padEnd(8)} ${(p.sessionId ?? "\u2014").padEnd(40)} ${truncate(p.command, 50)}`
|
|
753
753
|
);
|
|
754
754
|
}
|
|
755
755
|
log("");
|
|
@@ -769,12 +769,12 @@ ${BOLD}${"Agent".padEnd(9)} ${"Status".padEnd(10)} ${"Premium".padEnd(10)} ${"La
|
|
|
769
769
|
);
|
|
770
770
|
log("\u2500".repeat(130));
|
|
771
771
|
for (const s of sessions) {
|
|
772
|
-
const
|
|
772
|
+
const agentLabel3 = s.agent === "claude" ? `${CYAN}claude${RESET} ` : `${GREEN}copilot${RESET}`;
|
|
773
773
|
const status = s.complete ? `${GREEN}\u2714 done${RESET}` : `${YELLOW}\u23F8 stop${RESET}`;
|
|
774
774
|
const premium = s.agent === "claude" ? "\u2014" : String(s.premiumRequests);
|
|
775
775
|
const summary = truncate(s.summary || "\u2014", 33);
|
|
776
776
|
log(
|
|
777
|
-
`${
|
|
777
|
+
`${agentLabel3.padEnd(9 + 9)} ${status.padEnd(10 + 9)} ${premium.padEnd(10)} ${s.lastEvent.padEnd(22)} ${summary.padEnd(35)} ${DIM}${s.id.slice(0, 12)}\u2026${RESET}`
|
|
778
778
|
);
|
|
779
779
|
}
|
|
780
780
|
log(`
|
|
@@ -1658,19 +1658,343 @@ function renderReport(r) {
|
|
|
1658
1658
|
log("");
|
|
1659
1659
|
}
|
|
1660
1660
|
|
|
1661
|
+
// src/tui/widgets.ts
|
|
1662
|
+
function agentLabel(agent) {
|
|
1663
|
+
return agent === "claude" ? "{yellow-fg}claude{/}" : "{cyan-fg}copilot{/}";
|
|
1664
|
+
}
|
|
1665
|
+
function statusIcon(complete) {
|
|
1666
|
+
return complete ? "{green-fg}\u2714{/}" : "{yellow-fg}\u23F3{/}";
|
|
1667
|
+
}
|
|
1668
|
+
function fmtDuration(ms) {
|
|
1669
|
+
if (ms < 0) return "\u2014";
|
|
1670
|
+
const s = Math.floor(ms / 1e3);
|
|
1671
|
+
if (s < 60) return `${s}s`;
|
|
1672
|
+
const m = Math.floor(s / 60);
|
|
1673
|
+
if (m < 60) return `${m}m ${s % 60}s`;
|
|
1674
|
+
const h = Math.floor(m / 60);
|
|
1675
|
+
return `${h}h ${m % 60}m`;
|
|
1676
|
+
}
|
|
1677
|
+
function fmtTimeAgo(mtimeMs) {
|
|
1678
|
+
const diff = Date.now() - mtimeMs;
|
|
1679
|
+
if (diff < 6e4) return "just now";
|
|
1680
|
+
if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
|
|
1681
|
+
if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
|
|
1682
|
+
return `${Math.floor(diff / 864e5)}d ago`;
|
|
1683
|
+
}
|
|
1684
|
+
function fmtNumber(n) {
|
|
1685
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
1686
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
1687
|
+
return String(n);
|
|
1688
|
+
}
|
|
1689
|
+
function textBar(value, max, width) {
|
|
1690
|
+
if (max <= 0) return "\u2591".repeat(width);
|
|
1691
|
+
const filled = Math.round(value / max * width);
|
|
1692
|
+
return "\u2588".repeat(Math.min(filled, width)) + "\u2591".repeat(Math.max(width - filled, 0));
|
|
1693
|
+
}
|
|
1694
|
+
function sessionRow(s) {
|
|
1695
|
+
const icon = statusIcon(s.complete);
|
|
1696
|
+
const agent = agentLabel(s.agent);
|
|
1697
|
+
const name = (s.cwd?.split("/").pop() || s.id.slice(0, 8)).slice(0, 16).padEnd(16);
|
|
1698
|
+
const summary = (s.summary || "\u2014").slice(0, 30).padEnd(30);
|
|
1699
|
+
const time = fmtTimeAgo(s.mtime).padStart(8);
|
|
1700
|
+
const prem = s.premiumRequests > 0 ? `{yellow-fg}\u2B21${s.premiumRequests}{/}` : "";
|
|
1701
|
+
return ` ${icon} ${agent} ${name} ${summary} ${time} ${prem}`;
|
|
1702
|
+
}
|
|
1703
|
+
function toolBars(toolUsage, maxWidth = 20) {
|
|
1704
|
+
const entries = Object.entries(toolUsage).sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
1705
|
+
if (entries.length === 0) return [" {gray-fg}No tools used{/}"];
|
|
1706
|
+
const maxVal = entries[0][1];
|
|
1707
|
+
return entries.map(([name, count]) => {
|
|
1708
|
+
const label = name.slice(0, 12).padEnd(12);
|
|
1709
|
+
const bar2 = textBar(count, maxVal, maxWidth);
|
|
1710
|
+
return ` ${label} {cyan-fg}${bar2}{/} ${count}`;
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
function processRow(p) {
|
|
1714
|
+
const pid = String(p.pid).padEnd(7);
|
|
1715
|
+
const agent = agentLabel(p.agent);
|
|
1716
|
+
const sid = (p.sessionId || "\u2014").slice(0, 20).padEnd(20);
|
|
1717
|
+
const cwd = (p.cwd || "\u2014").replace(/^\/Users\/\w+/, "~").slice(0, 30).padEnd(30);
|
|
1718
|
+
return ` ${pid} ${agent} ${sid} ${cwd} {green-fg}running{/}`;
|
|
1719
|
+
}
|
|
1720
|
+
function detailContent(r) {
|
|
1721
|
+
const lines = [];
|
|
1722
|
+
const project = r.cwd?.split("/").pop() || "unknown";
|
|
1723
|
+
lines.push(`{bold}${project}{/} ${agentLabel(r.agent)}`);
|
|
1724
|
+
lines.push(`{gray-fg}${r.id}{/}`);
|
|
1725
|
+
lines.push("");
|
|
1726
|
+
lines.push("{bold}\u2500\u2500\u2500 Stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500{/}");
|
|
1727
|
+
lines.push(` Duration ${fmtDuration(r.durationMs)}`);
|
|
1728
|
+
lines.push(` Messages ${r.userMessages}`);
|
|
1729
|
+
lines.push(` Turns ${r.assistantTurns}`);
|
|
1730
|
+
lines.push(` Tokens ${fmtNumber(r.outputTokens)}`);
|
|
1731
|
+
lines.push(` Premium {yellow-fg}${r.premiumRequests}{/}`);
|
|
1732
|
+
lines.push(` Status ${r.complete ? "{green-fg}complete{/}" : "{yellow-fg}incomplete{/}"}`);
|
|
1733
|
+
lines.push("");
|
|
1734
|
+
if (Object.keys(r.toolUsage).length > 0) {
|
|
1735
|
+
lines.push("{bold}\u2500\u2500\u2500 Tools \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500{/}");
|
|
1736
|
+
lines.push(...toolBars(r.toolUsage, 16));
|
|
1737
|
+
lines.push("");
|
|
1738
|
+
}
|
|
1739
|
+
if (r.gitCommits.length > 0) {
|
|
1740
|
+
lines.push("{bold}\u2500\u2500\u2500 Commits \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500{/}");
|
|
1741
|
+
for (const c of r.gitCommits.slice(0, 8)) {
|
|
1742
|
+
lines.push(` {green-fg}\u25CF{/} ${c.slice(0, 50)}`);
|
|
1743
|
+
}
|
|
1744
|
+
if (r.gitCommits.length > 8) lines.push(` {gray-fg}... +${r.gitCommits.length - 8} more{/}`);
|
|
1745
|
+
lines.push("");
|
|
1746
|
+
}
|
|
1747
|
+
const allFiles = [...r.filesCreated.map((f) => `{green-fg}+{/} ${f}`), ...r.filesEdited.map((f) => `{yellow-fg}~{/} ${f}`)];
|
|
1748
|
+
if (allFiles.length > 0) {
|
|
1749
|
+
lines.push("{bold}\u2500\u2500\u2500 Files \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500{/}");
|
|
1750
|
+
for (const f of allFiles.slice(0, 10)) {
|
|
1751
|
+
lines.push(` ${f.length > 50 ? f.slice(0, 50) + "\u2026" : f}`);
|
|
1752
|
+
}
|
|
1753
|
+
if (allFiles.length > 10) lines.push(` {gray-fg}... +${allFiles.length - 10} more{/}`);
|
|
1754
|
+
lines.push("");
|
|
1755
|
+
}
|
|
1756
|
+
if (r.taskCompletions.length > 0) {
|
|
1757
|
+
lines.push("{bold}\u2500\u2500\u2500 Tasks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500{/}");
|
|
1758
|
+
for (const t of r.taskCompletions.slice(0, 5)) {
|
|
1759
|
+
lines.push(` {green-fg}\u2714{/} ${t.slice(0, 50)}`);
|
|
1760
|
+
}
|
|
1761
|
+
lines.push("");
|
|
1762
|
+
}
|
|
1763
|
+
if (r.errors.length > 0) {
|
|
1764
|
+
lines.push("{bold}{red-fg}\u2500\u2500\u2500 Errors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500{/}");
|
|
1765
|
+
for (const e of r.errors.slice(0, 3)) {
|
|
1766
|
+
lines.push(` {red-fg}\u2717{/} ${e.slice(0, 60)}`);
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
return lines.join("\n");
|
|
1770
|
+
}
|
|
1771
|
+
function headerStats(processCount, sessionCount, totalPremium, completedCount) {
|
|
1772
|
+
return ` {bold}Processes:{/} {green-fg}${processCount}{/} {bold}Sessions:{/} ${sessionCount} {bold}Premium:{/} {yellow-fg}\u2B21${totalPremium}{/} {bold}Done:{/} {green-fg}${completedCount}{/}/${sessionCount}`;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1661
1775
|
// src/commands/dashboard.ts
|
|
1776
|
+
var blessed;
|
|
1777
|
+
async function loadBlessed() {
|
|
1778
|
+
const mod = await import("blessed");
|
|
1779
|
+
blessed = mod.default || mod;
|
|
1780
|
+
}
|
|
1781
|
+
function registerDashboardCommand(program2) {
|
|
1782
|
+
program2.command("dashboard").alias("tui").description("Real-time terminal dashboard for copilot sessions (htop-style)").option("-r, --refresh <n>", "Refresh interval in seconds", "5").option("-l, --limit <n>", "Number of sessions to show", "20").option("--simple", "Use simple ANSI dashboard (no blessed)").action(async (opts) => {
|
|
1783
|
+
if (opts.simple) {
|
|
1784
|
+
runSimpleDashboard(parseInt(opts.refresh, 10), parseInt(opts.limit, 10));
|
|
1785
|
+
} else {
|
|
1786
|
+
await loadBlessed();
|
|
1787
|
+
runBlessedDashboard(parseInt(opts.refresh, 10), parseInt(opts.limit, 10));
|
|
1788
|
+
}
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
function runBlessedDashboard(refreshSec, limit) {
|
|
1792
|
+
const screen = blessed.screen({
|
|
1793
|
+
smartCSR: true,
|
|
1794
|
+
title: "copilot-agent dashboard",
|
|
1795
|
+
fullUnicode: true
|
|
1796
|
+
});
|
|
1797
|
+
const BG = "#0d1117";
|
|
1798
|
+
const FG = "#e6edf3";
|
|
1799
|
+
const BORDER_COLOR = "#30363d";
|
|
1800
|
+
const ACCENT = "#58a6ff";
|
|
1801
|
+
const MUTED = "#8b949e";
|
|
1802
|
+
const headerBox = blessed.box({
|
|
1803
|
+
parent: screen,
|
|
1804
|
+
top: 0,
|
|
1805
|
+
left: 0,
|
|
1806
|
+
width: "100%",
|
|
1807
|
+
height: 3,
|
|
1808
|
+
tags: true,
|
|
1809
|
+
style: { fg: FG, bg: "#161b22", border: { fg: BORDER_COLOR } },
|
|
1810
|
+
border: { type: "line" },
|
|
1811
|
+
content: ""
|
|
1812
|
+
});
|
|
1813
|
+
const processBox = blessed.box({
|
|
1814
|
+
parent: screen,
|
|
1815
|
+
top: 3,
|
|
1816
|
+
left: 0,
|
|
1817
|
+
width: "100%",
|
|
1818
|
+
height: 7,
|
|
1819
|
+
tags: true,
|
|
1820
|
+
label: " {cyan-fg}{bold}Active Processes{/} ",
|
|
1821
|
+
scrollable: true,
|
|
1822
|
+
style: { fg: FG, bg: BG, border: { fg: BORDER_COLOR }, label: { fg: ACCENT } },
|
|
1823
|
+
border: { type: "line" },
|
|
1824
|
+
content: ""
|
|
1825
|
+
});
|
|
1826
|
+
const sessionList = blessed.list({
|
|
1827
|
+
parent: screen,
|
|
1828
|
+
top: 10,
|
|
1829
|
+
left: 0,
|
|
1830
|
+
width: "45%",
|
|
1831
|
+
bottom: 3,
|
|
1832
|
+
tags: true,
|
|
1833
|
+
label: " {cyan-fg}{bold}Sessions{/} ",
|
|
1834
|
+
scrollable: true,
|
|
1835
|
+
keys: true,
|
|
1836
|
+
vi: true,
|
|
1837
|
+
mouse: true,
|
|
1838
|
+
style: {
|
|
1839
|
+
fg: FG,
|
|
1840
|
+
bg: BG,
|
|
1841
|
+
border: { fg: BORDER_COLOR },
|
|
1842
|
+
label: { fg: ACCENT },
|
|
1843
|
+
selected: { fg: "#ffffff", bg: "#1f6feb", bold: true },
|
|
1844
|
+
item: { fg: FG }
|
|
1845
|
+
},
|
|
1846
|
+
border: { type: "line" },
|
|
1847
|
+
scrollbar: { ch: "\u2502", style: { fg: ACCENT } }
|
|
1848
|
+
});
|
|
1849
|
+
const detailBox = blessed.box({
|
|
1850
|
+
parent: screen,
|
|
1851
|
+
top: 10,
|
|
1852
|
+
left: "45%",
|
|
1853
|
+
width: "55%",
|
|
1854
|
+
bottom: 3,
|
|
1855
|
+
tags: true,
|
|
1856
|
+
label: " {cyan-fg}{bold}Detail{/} ",
|
|
1857
|
+
scrollable: true,
|
|
1858
|
+
keys: true,
|
|
1859
|
+
vi: true,
|
|
1860
|
+
mouse: true,
|
|
1861
|
+
style: { fg: FG, bg: BG, border: { fg: BORDER_COLOR }, label: { fg: ACCENT } },
|
|
1862
|
+
border: { type: "line" },
|
|
1863
|
+
scrollbar: { ch: "\u2502", style: { fg: ACCENT } },
|
|
1864
|
+
content: "{gray-fg}Select a session with \u2191\u2193 keys{/}"
|
|
1865
|
+
});
|
|
1866
|
+
const footer = blessed.box({
|
|
1867
|
+
parent: screen,
|
|
1868
|
+
bottom: 0,
|
|
1869
|
+
left: 0,
|
|
1870
|
+
width: "100%",
|
|
1871
|
+
height: 3,
|
|
1872
|
+
tags: true,
|
|
1873
|
+
style: { fg: MUTED, bg: "#161b22", border: { fg: BORDER_COLOR } },
|
|
1874
|
+
border: { type: "line" },
|
|
1875
|
+
content: " {bold}\u2191\u2193{/} Navigate {bold}Enter{/} Detail {bold}Tab{/} Switch panel {bold}r{/} Refresh {bold}q{/} Quit"
|
|
1876
|
+
});
|
|
1877
|
+
let sessions = [];
|
|
1878
|
+
let procs = [];
|
|
1879
|
+
let selectedIdx = 0;
|
|
1880
|
+
let focusedPanel = "sessions";
|
|
1881
|
+
function refreshData() {
|
|
1882
|
+
try {
|
|
1883
|
+
procs = findAgentProcesses();
|
|
1884
|
+
sessions = listAllSessions(limit);
|
|
1885
|
+
} catch {
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
function renderHeader() {
|
|
1889
|
+
const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB");
|
|
1890
|
+
const totalPremium = sessions.reduce((s, x) => s + x.premiumRequests, 0);
|
|
1891
|
+
const completedCount = sessions.filter((s) => s.complete).length;
|
|
1892
|
+
const stats = headerStats(procs.length, sessions.length, totalPremium, completedCount);
|
|
1893
|
+
headerBox.setContent(` {bold}{cyan-fg}\u26A1 copilot-agent{/} ${stats} {gray-fg}${time}{/}`);
|
|
1894
|
+
}
|
|
1895
|
+
function renderProcesses2() {
|
|
1896
|
+
if (procs.length === 0) {
|
|
1897
|
+
processBox.setContent(" {gray-fg}No agent processes running{/}");
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
const header = ` ${"PID".padEnd(7)} ${"Agent".padEnd(10)} ${"Session".padEnd(20)} ${"Directory".padEnd(30)} Status`;
|
|
1901
|
+
const rows = procs.map((p) => processRow(p));
|
|
1902
|
+
processBox.setContent(`{gray-fg}${header}{/}
|
|
1903
|
+
${rows.join("\n")}`);
|
|
1904
|
+
}
|
|
1905
|
+
function renderSessions() {
|
|
1906
|
+
const items = sessions.map((s) => sessionRow(s));
|
|
1907
|
+
sessionList.setItems(items);
|
|
1908
|
+
if (selectedIdx >= 0 && selectedIdx < items.length) {
|
|
1909
|
+
sessionList.select(selectedIdx);
|
|
1910
|
+
}
|
|
1911
|
+
sessionList.setLabel(` {cyan-fg}{bold}Sessions (${sessions.length}){/} `);
|
|
1912
|
+
}
|
|
1913
|
+
function renderDetail2() {
|
|
1914
|
+
if (sessions.length === 0 || selectedIdx < 0 || selectedIdx >= sessions.length) {
|
|
1915
|
+
detailBox.setContent("{gray-fg}No session selected{/}");
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
const s = sessions[selectedIdx];
|
|
1919
|
+
try {
|
|
1920
|
+
const report = getAgentSessionReport(s.id, s.agent);
|
|
1921
|
+
if (report) {
|
|
1922
|
+
detailBox.setContent(detailContent(report));
|
|
1923
|
+
detailBox.setLabel(` {cyan-fg}{bold}Detail \u2014 ${s.id.slice(0, 12)}\u2026{/} `);
|
|
1924
|
+
} else {
|
|
1925
|
+
detailBox.setContent(`{gray-fg}Could not load report for ${s.id.slice(0, 8)}\u2026{/}`);
|
|
1926
|
+
}
|
|
1927
|
+
} catch {
|
|
1928
|
+
detailBox.setContent("{red-fg}Error loading session detail{/}");
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
function render() {
|
|
1932
|
+
refreshData();
|
|
1933
|
+
renderHeader();
|
|
1934
|
+
renderProcesses2();
|
|
1935
|
+
renderSessions();
|
|
1936
|
+
renderDetail2();
|
|
1937
|
+
screen.render();
|
|
1938
|
+
}
|
|
1939
|
+
screen.key(["q", "C-c"], () => {
|
|
1940
|
+
screen.destroy();
|
|
1941
|
+
process.exit(0);
|
|
1942
|
+
});
|
|
1943
|
+
screen.key(["r"], () => render());
|
|
1944
|
+
screen.key(["tab"], () => {
|
|
1945
|
+
if (focusedPanel === "sessions") {
|
|
1946
|
+
focusedPanel = "detail";
|
|
1947
|
+
detailBox.focus();
|
|
1948
|
+
sessionList.style.border.fg = BORDER_COLOR;
|
|
1949
|
+
detailBox.style.border.fg = ACCENT;
|
|
1950
|
+
} else {
|
|
1951
|
+
focusedPanel = "sessions";
|
|
1952
|
+
sessionList.focus();
|
|
1953
|
+
sessionList.style.border.fg = ACCENT;
|
|
1954
|
+
detailBox.style.border.fg = BORDER_COLOR;
|
|
1955
|
+
}
|
|
1956
|
+
screen.render();
|
|
1957
|
+
});
|
|
1958
|
+
sessionList.on("select item", (_item, index) => {
|
|
1959
|
+
selectedIdx = index;
|
|
1960
|
+
renderDetail2();
|
|
1961
|
+
screen.render();
|
|
1962
|
+
});
|
|
1963
|
+
sessionList.key(["up", "k"], () => {
|
|
1964
|
+
if (selectedIdx > 0) {
|
|
1965
|
+
selectedIdx--;
|
|
1966
|
+
sessionList.select(selectedIdx);
|
|
1967
|
+
renderDetail2();
|
|
1968
|
+
screen.render();
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1971
|
+
sessionList.key(["down", "j"], () => {
|
|
1972
|
+
if (selectedIdx < sessions.length - 1) {
|
|
1973
|
+
selectedIdx++;
|
|
1974
|
+
sessionList.select(selectedIdx);
|
|
1975
|
+
renderDetail2();
|
|
1976
|
+
screen.render();
|
|
1977
|
+
}
|
|
1978
|
+
});
|
|
1979
|
+
sessionList.key(["enter"], () => {
|
|
1980
|
+
renderDetail2();
|
|
1981
|
+
focusedPanel = "detail";
|
|
1982
|
+
detailBox.focus();
|
|
1983
|
+
sessionList.style.border.fg = BORDER_COLOR;
|
|
1984
|
+
detailBox.style.border.fg = ACCENT;
|
|
1985
|
+
screen.render();
|
|
1986
|
+
});
|
|
1987
|
+
sessionList.focus();
|
|
1988
|
+
sessionList.style.border.fg = ACCENT;
|
|
1989
|
+
render();
|
|
1990
|
+
const timer = setInterval(render, refreshSec * 1e3);
|
|
1991
|
+
screen.on("destroy", () => clearInterval(timer));
|
|
1992
|
+
}
|
|
1662
1993
|
var ESC = "\x1B";
|
|
1663
1994
|
var CLEAR = `${ESC}[2J${ESC}[H`;
|
|
1664
1995
|
var HIDE_CURSOR = `${ESC}[?25l`;
|
|
1665
1996
|
var SHOW_CURSOR = `${ESC}[?25h`;
|
|
1666
|
-
|
|
1667
|
-
var RESTORE_CURSOR = `${ESC}8`;
|
|
1668
|
-
function registerDashboardCommand(program2) {
|
|
1669
|
-
program2.command("dashboard").alias("tui").description("Real-time terminal dashboard for copilot sessions").option("-r, --refresh <n>", "Refresh interval in seconds", "5").option("-l, --limit <n>", "Number of sessions to show", "8").action((opts) => {
|
|
1670
|
-
runDashboard(parseInt(opts.refresh, 10), parseInt(opts.limit, 10));
|
|
1671
|
-
});
|
|
1672
|
-
}
|
|
1673
|
-
function runDashboard(refreshSec, limit) {
|
|
1997
|
+
function runSimpleDashboard(refreshSec, limit) {
|
|
1674
1998
|
process.stdout.write(HIDE_CURSOR);
|
|
1675
1999
|
const cleanup = () => {
|
|
1676
2000
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -1681,34 +2005,30 @@ function runDashboard(refreshSec, limit) {
|
|
|
1681
2005
|
process.on("SIGTERM", cleanup);
|
|
1682
2006
|
const render = () => {
|
|
1683
2007
|
try {
|
|
1684
|
-
const output =
|
|
2008
|
+
const output = buildSimpleScreen(limit);
|
|
1685
2009
|
process.stdout.write(CLEAR + output);
|
|
1686
2010
|
} catch {
|
|
1687
2011
|
}
|
|
1688
2012
|
};
|
|
1689
2013
|
render();
|
|
1690
|
-
|
|
2014
|
+
setInterval(render, refreshSec * 1e3);
|
|
1691
2015
|
process.stdout.on("resize", render);
|
|
1692
2016
|
process.stdin.setRawMode?.(true);
|
|
1693
2017
|
process.stdin.resume();
|
|
1694
2018
|
process.stdin.on("data", (data) => {
|
|
1695
2019
|
const key = data.toString();
|
|
1696
|
-
if (key === "q" || key === "")
|
|
1697
|
-
clearInterval(timer);
|
|
1698
|
-
cleanup();
|
|
1699
|
-
}
|
|
2020
|
+
if (key === "q" || key === "") cleanup();
|
|
1700
2021
|
if (key === "r") render();
|
|
1701
2022
|
});
|
|
1702
2023
|
}
|
|
1703
|
-
function
|
|
2024
|
+
function buildSimpleScreen(limit) {
|
|
1704
2025
|
const cols = process.stdout.columns || 80;
|
|
1705
|
-
const rows = process.stdout.rows || 40;
|
|
1706
2026
|
const lines = [];
|
|
1707
2027
|
const now = /* @__PURE__ */ new Date();
|
|
1708
2028
|
const timeStr = now.toLocaleTimeString("en-GB");
|
|
1709
2029
|
lines.push("");
|
|
1710
2030
|
lines.push(` ${BOLD}${CYAN}\u250C${"\u2500".repeat(cols - 6)}\u2510${RESET}`);
|
|
1711
|
-
lines.push(` ${BOLD}${CYAN}\u2502${RESET} \
|
|
2031
|
+
lines.push(` ${BOLD}${CYAN}\u2502${RESET} \u26A1 ${BOLD}Copilot Agent Dashboard${RESET}${" ".repeat(Math.max(0, cols - 37 - timeStr.length))}${DIM}${timeStr}${RESET} ${BOLD}${CYAN}\u2502${RESET}`);
|
|
1712
2032
|
lines.push(` ${BOLD}${CYAN}\u2514${"\u2500".repeat(cols - 6)}\u2518${RESET}`);
|
|
1713
2033
|
lines.push("");
|
|
1714
2034
|
const procs = findAgentProcesses();
|
|
@@ -1718,7 +2038,7 @@ function buildScreen(limit) {
|
|
|
1718
2038
|
lines.push(` ${DIM}No agent processes running${RESET}`);
|
|
1719
2039
|
} else {
|
|
1720
2040
|
for (const p of procs) {
|
|
1721
|
-
const agentTag = p.agent === "claude" ? `${
|
|
2041
|
+
const agentTag = p.agent === "claude" ? `${YELLOW}[claude]${RESET}` : `${CYAN}[copilot]${RESET}`;
|
|
1722
2042
|
const sid = p.sessionId ? p.sessionId.slice(0, 8) + "\u2026" : "\u2014";
|
|
1723
2043
|
const cwdShort = p.cwd ? "~/" + p.cwd.split("/").slice(-2).join("/") : "\u2014";
|
|
1724
2044
|
lines.push(` ${GREEN}\u2B24${RESET} ${agentTag} PID ${BOLD}${p.pid}${RESET} ${CYAN}${sid}${RESET} ${DIM}${cwdShort}${RESET}`);
|
|
@@ -1728,67 +2048,15 @@ function buildScreen(limit) {
|
|
|
1728
2048
|
const sessions = listAllSessions(limit);
|
|
1729
2049
|
lines.push(` ${BOLD}${CYAN}\u25CF Recent Sessions (${sessions.length})${RESET}`);
|
|
1730
2050
|
lines.push(` ${"\u2500".repeat(Math.min(cols - 4, 70))}`);
|
|
1731
|
-
const
|
|
1732
|
-
|
|
1733
|
-
pad("Premium", 8),
|
|
1734
|
-
pad("Duration", 10),
|
|
1735
|
-
pad("Project", 18),
|
|
1736
|
-
pad("Last Activity", 20)
|
|
1737
|
-
];
|
|
1738
|
-
lines.push(` ${DIM}${headerCols.join(" ")}${RESET}`);
|
|
2051
|
+
const hdr = [pad("Status", 10), pad("Agent", 8), pad("Premium", 8), pad("Project", 18), pad("Last Activity", 14)];
|
|
2052
|
+
lines.push(` ${DIM}${hdr.join(" ")}${RESET}`);
|
|
1739
2053
|
for (const s of sessions) {
|
|
1740
|
-
const
|
|
1741
|
-
const
|
|
1742
|
-
const
|
|
1743
|
-
const
|
|
1744
|
-
const
|
|
1745
|
-
|
|
1746
|
-
lines.push(` ${statusIcon}${premium}${duration}${project}${lastAct}`);
|
|
1747
|
-
}
|
|
1748
|
-
lines.push("");
|
|
1749
|
-
if (sessions.length > 0) {
|
|
1750
|
-
const latest = getAgentSessionReport(sessions[0].id, sessions[0].agent);
|
|
1751
|
-
if (latest) {
|
|
1752
|
-
lines.push(` ${BOLD}${CYAN}\u25CF Latest Session Detail${RESET} ${DIM}${latest.id.slice(0, 8)}\u2026${RESET}`);
|
|
1753
|
-
lines.push(` ${"\u2500".repeat(Math.min(cols - 4, 70))}`);
|
|
1754
|
-
lines.push(` Turns: ${BOLD}${latest.assistantTurns}${RESET} Tokens: ${BOLD}${latest.outputTokens.toLocaleString()}${RESET} Premium: ${BOLD}${latest.premiumRequests}${RESET} Commits: ${BOLD}${latest.gitCommits.length}${RESET} Files: ${BOLD}${latest.filesEdited.length}${RESET} edited, ${BOLD}${latest.filesCreated.length}${RESET} created`);
|
|
1755
|
-
lines.push("");
|
|
1756
|
-
const toolEntries = Object.entries(latest.toolUsage).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
1757
|
-
if (toolEntries.length > 0) {
|
|
1758
|
-
const maxVal = toolEntries[0][1];
|
|
1759
|
-
const barW = Math.min(20, Math.floor((cols - 30) / 2));
|
|
1760
|
-
lines.push(` ${DIM}Tools:${RESET}`);
|
|
1761
|
-
for (const [tool, count] of toolEntries) {
|
|
1762
|
-
const filled = Math.round(count / maxVal * barW);
|
|
1763
|
-
const b = `${CYAN}${"\u2588".repeat(filled)}${DIM}${"\u2591".repeat(barW - filled)}${RESET}`;
|
|
1764
|
-
lines.push(` ${b} ${String(count).padStart(4)} ${tool}`);
|
|
1765
|
-
}
|
|
1766
|
-
lines.push("");
|
|
1767
|
-
}
|
|
1768
|
-
if (latest.gitCommits.length > 0) {
|
|
1769
|
-
const maxCommits = Math.min(3, latest.gitCommits.length);
|
|
1770
|
-
lines.push(` ${DIM}Recent commits:${RESET}`);
|
|
1771
|
-
for (let i = 0; i < maxCommits; i++) {
|
|
1772
|
-
const msg = latest.gitCommits[i].split("\n")[0].slice(0, cols - 10);
|
|
1773
|
-
lines.push(` ${GREEN}\u25CF${RESET} ${msg}`);
|
|
1774
|
-
}
|
|
1775
|
-
if (latest.gitCommits.length > maxCommits) {
|
|
1776
|
-
lines.push(` ${DIM} \u2026 +${latest.gitCommits.length - maxCommits} more${RESET}`);
|
|
1777
|
-
}
|
|
1778
|
-
lines.push("");
|
|
1779
|
-
}
|
|
1780
|
-
if (latest.taskCompletions.length > 0) {
|
|
1781
|
-
const maxTasks = Math.min(3, latest.taskCompletions.length);
|
|
1782
|
-
lines.push(` ${DIM}Completed tasks:${RESET}`);
|
|
1783
|
-
for (let i = 0; i < maxTasks; i++) {
|
|
1784
|
-
const msg = latest.taskCompletions[i].split("\n")[0].slice(0, cols - 10);
|
|
1785
|
-
lines.push(` ${GREEN}\u2714${RESET} ${msg}`);
|
|
1786
|
-
}
|
|
1787
|
-
if (latest.taskCompletions.length > maxTasks) {
|
|
1788
|
-
lines.push(` ${DIM} \u2026 +${latest.taskCompletions.length - maxTasks} more${RESET}`);
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
2054
|
+
const icon = s.complete ? `${GREEN}\u2714 done ${RESET}` : `${YELLOW}\u23F8 stop ${RESET}`;
|
|
2055
|
+
const agent = s.agent === "claude" ? `${YELLOW}claude ${RESET}` : `${CYAN}copilot${RESET}`;
|
|
2056
|
+
const prem = pad(String(s.premiumRequests), 8);
|
|
2057
|
+
const proj = pad(s.cwd.split("/").pop() ?? "\u2014", 18);
|
|
2058
|
+
const ago = pad(fmtTimeAgo(s.mtime), 14);
|
|
2059
|
+
lines.push(` ${icon}${agent} ${prem}${proj}${ago}`);
|
|
1792
2060
|
}
|
|
1793
2061
|
lines.push("");
|
|
1794
2062
|
lines.push(` ${DIM}Press ${BOLD}q${RESET}${DIM} to quit, ${BOLD}r${RESET}${DIM} to refresh${RESET}`);
|
|
@@ -1798,20 +2066,6 @@ function pad(s, n) {
|
|
|
1798
2066
|
if (s.length >= n) return s.slice(0, n);
|
|
1799
2067
|
return s + " ".repeat(n - s.length);
|
|
1800
2068
|
}
|
|
1801
|
-
function formatDuration2(ms) {
|
|
1802
|
-
if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
|
|
1803
|
-
if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
|
|
1804
|
-
const h = Math.floor(ms / 36e5);
|
|
1805
|
-
const m = Math.round(ms % 36e5 / 6e4);
|
|
1806
|
-
return `${h}h ${m}m`;
|
|
1807
|
-
}
|
|
1808
|
-
function formatTimeAgo(mtimeMs) {
|
|
1809
|
-
const diff = Date.now() - mtimeMs;
|
|
1810
|
-
if (diff < 6e4) return "just now";
|
|
1811
|
-
if (diff < 36e5) return `${Math.round(diff / 6e4)}m ago`;
|
|
1812
|
-
if (diff < 864e5) return `${Math.round(diff / 36e5)}h ago`;
|
|
1813
|
-
return `${Math.round(diff / 864e5)}d ago`;
|
|
1814
|
-
}
|
|
1815
2069
|
|
|
1816
2070
|
// src/commands/web.ts
|
|
1817
2071
|
import { Hono } from "hono";
|
|
@@ -2209,9 +2463,376 @@ ${layoutFoot}`);
|
|
|
2209
2463
|
}
|
|
2210
2464
|
}
|
|
2211
2465
|
|
|
2466
|
+
// src/commands/config.ts
|
|
2467
|
+
import chalk from "chalk";
|
|
2468
|
+
|
|
2469
|
+
// src/lib/config.ts
|
|
2470
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
2471
|
+
import { join as join7 } from "path";
|
|
2472
|
+
import { homedir as homedir5 } from "os";
|
|
2473
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
2474
|
+
import { findUpSync } from "find-up";
|
|
2475
|
+
var CONFIG_DIR = join7(homedir5(), ".copilot-agent");
|
|
2476
|
+
var GLOBAL_CONFIG = join7(CONFIG_DIR, "config.yaml");
|
|
2477
|
+
var PROJECT_CONFIG_NAME = ".copilot-agent.yaml";
|
|
2478
|
+
function ensureConfigDir() {
|
|
2479
|
+
if (!existsSync6(CONFIG_DIR)) mkdirSync4(CONFIG_DIR, { recursive: true });
|
|
2480
|
+
}
|
|
2481
|
+
function loadGlobalConfig() {
|
|
2482
|
+
if (!existsSync6(GLOBAL_CONFIG)) return {};
|
|
2483
|
+
try {
|
|
2484
|
+
return parseYaml(readFileSync4(GLOBAL_CONFIG, "utf-8")) || {};
|
|
2485
|
+
} catch {
|
|
2486
|
+
return {};
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
function saveGlobalConfig(config) {
|
|
2490
|
+
ensureConfigDir();
|
|
2491
|
+
writeFileSync2(GLOBAL_CONFIG, stringifyYaml(config), "utf-8");
|
|
2492
|
+
}
|
|
2493
|
+
function loadProjectConfig(cwd) {
|
|
2494
|
+
const configPath = findUpSync(PROJECT_CONFIG_NAME, { cwd: cwd || process.cwd() });
|
|
2495
|
+
if (!configPath) return {};
|
|
2496
|
+
try {
|
|
2497
|
+
return parseYaml(readFileSync4(configPath, "utf-8")) || {};
|
|
2498
|
+
} catch {
|
|
2499
|
+
return {};
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
function resolveConfig(cliOpts = {}, cwd) {
|
|
2503
|
+
const defaults = { steps: 30, cooldown: 10, maxPremium: 50, maxTasks: 5, refreshInterval: 5 };
|
|
2504
|
+
const global = loadGlobalConfig();
|
|
2505
|
+
const project = loadProjectConfig(cwd);
|
|
2506
|
+
const merged = { ...defaults };
|
|
2507
|
+
for (const src of [global, project, cliOpts]) {
|
|
2508
|
+
for (const [k, v] of Object.entries(src)) {
|
|
2509
|
+
if (v !== void 0) merged[k] = v;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
return merged;
|
|
2513
|
+
}
|
|
2514
|
+
function setConfigValue(key, value) {
|
|
2515
|
+
const config = loadGlobalConfig();
|
|
2516
|
+
let parsed = value;
|
|
2517
|
+
if (value === "true") parsed = true;
|
|
2518
|
+
else if (value === "false") parsed = false;
|
|
2519
|
+
else if (/^\d+$/.test(value)) parsed = parseInt(value, 10);
|
|
2520
|
+
config[key] = parsed;
|
|
2521
|
+
saveGlobalConfig(config);
|
|
2522
|
+
}
|
|
2523
|
+
function deleteConfigValue(key) {
|
|
2524
|
+
const config = loadGlobalConfig();
|
|
2525
|
+
delete config[key];
|
|
2526
|
+
saveGlobalConfig(config);
|
|
2527
|
+
}
|
|
2528
|
+
function resetConfig() {
|
|
2529
|
+
if (existsSync6(GLOBAL_CONFIG)) {
|
|
2530
|
+
writeFileSync2(GLOBAL_CONFIG, "", "utf-8");
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
// src/commands/config.ts
|
|
2535
|
+
function registerConfigCommand(program2) {
|
|
2536
|
+
const cmd = program2.command("config").description("Manage persistent configuration defaults");
|
|
2537
|
+
cmd.command("list").description("Show all configuration values").action(() => {
|
|
2538
|
+
const global = loadGlobalConfig();
|
|
2539
|
+
const resolved = resolveConfig();
|
|
2540
|
+
console.log(chalk.bold.cyan("\n Global Config") + chalk.dim(" (~/.copilot-agent/config.yaml)\n"));
|
|
2541
|
+
const entries = Object.entries(global);
|
|
2542
|
+
if (entries.length === 0) {
|
|
2543
|
+
console.log(chalk.dim(" (empty \u2014 using defaults)\n"));
|
|
2544
|
+
} else {
|
|
2545
|
+
for (const [k, v] of entries) {
|
|
2546
|
+
console.log(` ${chalk.bold(k.padEnd(16))} ${chalk.green(String(v))}`);
|
|
2547
|
+
}
|
|
2548
|
+
console.log();
|
|
2549
|
+
}
|
|
2550
|
+
console.log(chalk.bold.cyan(" Resolved Config") + chalk.dim(" (defaults + global + project)\n"));
|
|
2551
|
+
for (const [k, v] of Object.entries(resolved)) {
|
|
2552
|
+
if (v !== void 0) {
|
|
2553
|
+
const isOverride = global[k] !== void 0;
|
|
2554
|
+
const marker = isOverride ? chalk.yellow("\u25CF") : chalk.dim("\u25CB");
|
|
2555
|
+
console.log(` ${marker} ${chalk.bold(k.padEnd(16))} ${String(v)}`);
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
console.log();
|
|
2559
|
+
});
|
|
2560
|
+
cmd.command("set <key> <value>").description("Set a configuration value").action((key, value) => {
|
|
2561
|
+
setConfigValue(key, value);
|
|
2562
|
+
console.log(chalk.green(` \u2714 Set ${chalk.bold(key)} = ${value}`));
|
|
2563
|
+
});
|
|
2564
|
+
cmd.command("get <key>").description("Get a configuration value").action((key) => {
|
|
2565
|
+
const resolved = resolveConfig();
|
|
2566
|
+
const val = resolved[key];
|
|
2567
|
+
if (val !== void 0) {
|
|
2568
|
+
console.log(` ${chalk.bold(key)} = ${chalk.green(String(val))}`);
|
|
2569
|
+
} else {
|
|
2570
|
+
console.log(chalk.dim(` ${key} is not set`));
|
|
2571
|
+
}
|
|
2572
|
+
});
|
|
2573
|
+
cmd.command("unset <key>").description("Remove a configuration value").action((key) => {
|
|
2574
|
+
deleteConfigValue(key);
|
|
2575
|
+
console.log(chalk.yellow(` \u2714 Removed ${chalk.bold(key)}`));
|
|
2576
|
+
});
|
|
2577
|
+
cmd.command("reset").description("Reset all configuration to defaults").action(() => {
|
|
2578
|
+
resetConfig();
|
|
2579
|
+
console.log(chalk.yellow(" \u2714 Configuration reset to defaults"));
|
|
2580
|
+
});
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
// src/commands/proxy.ts
|
|
2584
|
+
import chalk2 from "chalk";
|
|
2585
|
+
import { execa } from "execa";
|
|
2586
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync5, unlinkSync } from "fs";
|
|
2587
|
+
import { join as join8 } from "path";
|
|
2588
|
+
import { homedir as homedir6 } from "os";
|
|
2589
|
+
var PID_FILE = join8(homedir6(), ".copilot-agent", "proxy.pid");
|
|
2590
|
+
var DEFAULT_PORT = 4141;
|
|
2591
|
+
function ensureDir() {
|
|
2592
|
+
const dir = join8(homedir6(), ".copilot-agent");
|
|
2593
|
+
if (!existsSync7(dir)) mkdirSync5(dir, { recursive: true });
|
|
2594
|
+
}
|
|
2595
|
+
function findCopilotToken() {
|
|
2596
|
+
const appsPath = join8(homedir6(), ".config", "github-copilot", "apps.json");
|
|
2597
|
+
if (!existsSync7(appsPath)) return null;
|
|
2598
|
+
try {
|
|
2599
|
+
const apps = JSON.parse(readFileSync5(appsPath, "utf-8"));
|
|
2600
|
+
for (const key of Object.keys(apps)) {
|
|
2601
|
+
const token = apps[key]?.oauth_token;
|
|
2602
|
+
if (token && token.startsWith("ghu_")) return token;
|
|
2603
|
+
}
|
|
2604
|
+
return null;
|
|
2605
|
+
} catch {
|
|
2606
|
+
return null;
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
function readPid() {
|
|
2610
|
+
if (!existsSync7(PID_FILE)) return null;
|
|
2611
|
+
try {
|
|
2612
|
+
const pid = parseInt(readFileSync5(PID_FILE, "utf-8").trim(), 10);
|
|
2613
|
+
return isNaN(pid) ? null : pid;
|
|
2614
|
+
} catch {
|
|
2615
|
+
return null;
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
function isProcessRunning(pid) {
|
|
2619
|
+
try {
|
|
2620
|
+
process.kill(pid, 0);
|
|
2621
|
+
return true;
|
|
2622
|
+
} catch {
|
|
2623
|
+
return false;
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
async function isPortOpen(port) {
|
|
2627
|
+
try {
|
|
2628
|
+
const resp = await fetch(`http://localhost:${port}/v1/models`);
|
|
2629
|
+
return resp.ok;
|
|
2630
|
+
} catch {
|
|
2631
|
+
return false;
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
function registerProxyCommand(program2) {
|
|
2635
|
+
const cmd = program2.command("proxy").description("Manage copilot-api proxy for Claude Code");
|
|
2636
|
+
cmd.command("start").description("Start copilot-api proxy").option("-p, --port <n>", "Port number", String(DEFAULT_PORT)).option("--rate-limit <n>", "Rate limit in seconds", "30").action(async (opts) => {
|
|
2637
|
+
const port = parseInt(opts.port, 10);
|
|
2638
|
+
const rateLimit = parseInt(opts.rateLimit, 10);
|
|
2639
|
+
const existingPid = readPid();
|
|
2640
|
+
if (existingPid && isProcessRunning(existingPid)) {
|
|
2641
|
+
const open = await isPortOpen(port);
|
|
2642
|
+
if (open) {
|
|
2643
|
+
console.log(chalk2.yellow(` \u26A0 Proxy already running (PID ${existingPid}) on port ${port}`));
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
const token = findCopilotToken();
|
|
2648
|
+
if (!token) {
|
|
2649
|
+
console.log(chalk2.red(" \u2717 No Copilot token found"));
|
|
2650
|
+
console.log(chalk2.dim(" Expected: ~/.config/github-copilot/apps.json with ghu_* token"));
|
|
2651
|
+
console.log(chalk2.dim(" Run: copilot-api auth to authenticate"));
|
|
2652
|
+
return;
|
|
2653
|
+
}
|
|
2654
|
+
console.log(chalk2.cyan(" Starting copilot-api proxy..."));
|
|
2655
|
+
console.log(chalk2.dim(` Token: ${token.slice(0, 8)}...${token.slice(-4)}`));
|
|
2656
|
+
console.log(chalk2.dim(` Port: ${port} | Rate limit: ${rateLimit}s`));
|
|
2657
|
+
try {
|
|
2658
|
+
const child = execa("copilot-api", [
|
|
2659
|
+
"start",
|
|
2660
|
+
"--github-token",
|
|
2661
|
+
token,
|
|
2662
|
+
"--rate-limit",
|
|
2663
|
+
String(rateLimit),
|
|
2664
|
+
"--wait",
|
|
2665
|
+
"--port",
|
|
2666
|
+
String(port)
|
|
2667
|
+
], {
|
|
2668
|
+
detached: true,
|
|
2669
|
+
stdio: "ignore"
|
|
2670
|
+
});
|
|
2671
|
+
if (child.pid) {
|
|
2672
|
+
child.unref();
|
|
2673
|
+
ensureDir();
|
|
2674
|
+
writeFileSync3(PID_FILE, String(child.pid), "utf-8");
|
|
2675
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
2676
|
+
const open = await isPortOpen(port);
|
|
2677
|
+
if (open) {
|
|
2678
|
+
console.log(chalk2.green(` \u2714 Proxy started (PID ${child.pid}) on http://localhost:${port}`));
|
|
2679
|
+
} else {
|
|
2680
|
+
console.log(chalk2.yellow(` \u26A0 Proxy started (PID ${child.pid}) but port not yet responsive`));
|
|
2681
|
+
console.log(chalk2.dim(" It may take a few seconds to initialize"));
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
} catch (err) {
|
|
2685
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2686
|
+
console.log(chalk2.red(` \u2717 Failed to start proxy: ${message}`));
|
|
2687
|
+
console.log(chalk2.dim(" Make sure copilot-api is installed: npm install -g copilot-api"));
|
|
2688
|
+
}
|
|
2689
|
+
});
|
|
2690
|
+
cmd.command("stop").description("Stop copilot-api proxy").action(() => {
|
|
2691
|
+
const pid = readPid();
|
|
2692
|
+
if (!pid) {
|
|
2693
|
+
console.log(chalk2.dim(" No proxy PID found"));
|
|
2694
|
+
return;
|
|
2695
|
+
}
|
|
2696
|
+
if (!isProcessRunning(pid)) {
|
|
2697
|
+
console.log(chalk2.dim(` Proxy (PID ${pid}) is not running`));
|
|
2698
|
+
if (existsSync7(PID_FILE)) unlinkSync(PID_FILE);
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
try {
|
|
2702
|
+
process.kill(pid, "SIGTERM");
|
|
2703
|
+
if (existsSync7(PID_FILE)) unlinkSync(PID_FILE);
|
|
2704
|
+
console.log(chalk2.green(` \u2714 Proxy stopped (PID ${pid})`));
|
|
2705
|
+
} catch (err) {
|
|
2706
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2707
|
+
console.log(chalk2.red(` \u2717 Failed to stop: ${message}`));
|
|
2708
|
+
}
|
|
2709
|
+
});
|
|
2710
|
+
cmd.command("status").description("Check proxy status").option("-p, --port <n>", "Port number", String(DEFAULT_PORT)).action(async (opts) => {
|
|
2711
|
+
const port = parseInt(opts.port, 10);
|
|
2712
|
+
const pid = readPid();
|
|
2713
|
+
const running = pid ? isProcessRunning(pid) : false;
|
|
2714
|
+
const open = await isPortOpen(port);
|
|
2715
|
+
console.log(chalk2.bold.cyan("\n Copilot API Proxy Status\n"));
|
|
2716
|
+
console.log(` PID: ${running ? chalk2.green(String(pid)) : chalk2.dim("not running")}`);
|
|
2717
|
+
console.log(` Port: ${open ? chalk2.green(`localhost:${port} \u2714`) : chalk2.red(`localhost:${port} \u2717`)}`);
|
|
2718
|
+
const token = findCopilotToken();
|
|
2719
|
+
console.log(` Token: ${token ? chalk2.green(token.slice(0, 8) + "..." + token.slice(-4)) : chalk2.red("not found")}`);
|
|
2720
|
+
if (open) {
|
|
2721
|
+
try {
|
|
2722
|
+
const resp = await fetch(`http://localhost:${port}/v1/models`);
|
|
2723
|
+
const data = await resp.json();
|
|
2724
|
+
const modelCount = data?.data?.length || 0;
|
|
2725
|
+
console.log(` Models: ${chalk2.green(String(modelCount) + " available")}`);
|
|
2726
|
+
} catch {
|
|
2727
|
+
console.log(` Models: ${chalk2.dim("unknown")}`);
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
console.log();
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
// src/commands/diff.ts
|
|
2735
|
+
import chalk3 from "chalk";
|
|
2736
|
+
import { execaCommandSync } from "execa";
|
|
2737
|
+
function registerDiffCommand(program2) {
|
|
2738
|
+
program2.command("diff [session-id]").description("Show git changes made by an agent session").option("--stat", "Show diffstat summary only").option("-n, --num-commits <n>", "Number of recent commits to diff", "0").option("--project <dir>", "Filter sessions by project directory").action((sessionId, opts) => {
|
|
2739
|
+
try {
|
|
2740
|
+
showDiff(sessionId, opts);
|
|
2741
|
+
} catch (err) {
|
|
2742
|
+
console.error(chalk3.red(` \u2717 ${err.message}`));
|
|
2743
|
+
}
|
|
2744
|
+
});
|
|
2745
|
+
}
|
|
2746
|
+
function showDiff(sessionId, opts) {
|
|
2747
|
+
let sessions = listAllSessions(50);
|
|
2748
|
+
if (opts.project) {
|
|
2749
|
+
sessions = sessions.filter((s) => s.cwd.includes(opts.project));
|
|
2750
|
+
}
|
|
2751
|
+
let targetId = sessionId;
|
|
2752
|
+
let targetAgent;
|
|
2753
|
+
if (!targetId) {
|
|
2754
|
+
if (sessions.length === 0) {
|
|
2755
|
+
console.log(chalk3.dim(" No sessions found"));
|
|
2756
|
+
return;
|
|
2757
|
+
}
|
|
2758
|
+
targetId = sessions[0].id;
|
|
2759
|
+
targetAgent = sessions[0].agent;
|
|
2760
|
+
console.log(chalk3.dim(` Using latest session: ${targetId.slice(0, 8)}\u2026
|
|
2761
|
+
`));
|
|
2762
|
+
} else {
|
|
2763
|
+
const match = sessions.find((s) => s.id.startsWith(targetId));
|
|
2764
|
+
if (match) targetAgent = match.agent;
|
|
2765
|
+
}
|
|
2766
|
+
const report = getAgentSessionReport(targetId, targetAgent);
|
|
2767
|
+
if (!report) {
|
|
2768
|
+
console.log(chalk3.red(` \u2717 Session not found: ${targetId}`));
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
const project = report.cwd?.split("/").pop() || "unknown";
|
|
2772
|
+
const agentTag = report.agent === "claude" ? chalk3.yellow("[claude]") : chalk3.cyan("[copilot]");
|
|
2773
|
+
console.log(chalk3.bold(` ${agentTag} ${project}`) + chalk3.dim(` \u2014 ${report.id.slice(0, 8)}\u2026`));
|
|
2774
|
+
console.log(chalk3.dim(` ${report.summary || "No summary"}`));
|
|
2775
|
+
console.log();
|
|
2776
|
+
if (report.filesCreated.length > 0) {
|
|
2777
|
+
console.log(chalk3.bold.green(" Created files:"));
|
|
2778
|
+
for (const f of report.filesCreated) {
|
|
2779
|
+
console.log(chalk3.green(` + ${f}`));
|
|
2780
|
+
}
|
|
2781
|
+
console.log();
|
|
2782
|
+
}
|
|
2783
|
+
if (report.filesEdited.length > 0) {
|
|
2784
|
+
console.log(chalk3.bold.yellow(" Edited files:"));
|
|
2785
|
+
for (const f of report.filesEdited) {
|
|
2786
|
+
console.log(chalk3.yellow(` ~ ${f}`));
|
|
2787
|
+
}
|
|
2788
|
+
console.log();
|
|
2789
|
+
}
|
|
2790
|
+
if (report.gitCommits.length > 0) {
|
|
2791
|
+
console.log(chalk3.bold.cyan(" Commits:"));
|
|
2792
|
+
for (const c of report.gitCommits) {
|
|
2793
|
+
const first = c.split("\n")[0];
|
|
2794
|
+
console.log(chalk3.cyan(` \u25CF ${first}`));
|
|
2795
|
+
}
|
|
2796
|
+
console.log();
|
|
2797
|
+
}
|
|
2798
|
+
if (report.cwd) {
|
|
2799
|
+
const numCommits = parseInt(opts.numCommits || "0", 10);
|
|
2800
|
+
if (numCommits > 0) {
|
|
2801
|
+
try {
|
|
2802
|
+
const diffArgs = opts.stat ? ["git", "--no-pager", "diff", "--stat", `HEAD~${numCommits}`] : ["git", "--no-pager", "diff", "--color=always", `HEAD~${numCommits}`];
|
|
2803
|
+
const result = execaCommandSync(diffArgs.join(" "), { cwd: report.cwd });
|
|
2804
|
+
if (result.stdout) {
|
|
2805
|
+
console.log(chalk3.bold(" Git Diff:\n"));
|
|
2806
|
+
console.log(result.stdout);
|
|
2807
|
+
} else {
|
|
2808
|
+
console.log(chalk3.dim(" No diff (working tree clean)"));
|
|
2809
|
+
}
|
|
2810
|
+
} catch (err) {
|
|
2811
|
+
console.log(chalk3.dim(` Could not run git diff: ${err.message}`));
|
|
2812
|
+
}
|
|
2813
|
+
} else if (report.gitCommits.length > 0) {
|
|
2814
|
+
try {
|
|
2815
|
+
const n = report.gitCommits.length;
|
|
2816
|
+
const result = execaCommandSync(`git --no-pager diff --stat HEAD~${n}`, { cwd: report.cwd });
|
|
2817
|
+
if (result.stdout) {
|
|
2818
|
+
console.log(chalk3.bold(" Diffstat:\n"));
|
|
2819
|
+
console.log(result.stdout);
|
|
2820
|
+
}
|
|
2821
|
+
} catch {
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
console.log();
|
|
2826
|
+
console.log(chalk3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2827
|
+
console.log(` ${chalk3.bold("Files:")} ${chalk3.green(`+${report.filesCreated.length}`)} created, ${chalk3.yellow(`~${report.filesEdited.length}`)} edited`);
|
|
2828
|
+
console.log(` ${chalk3.bold("Commits:")} ${report.gitCommits.length}`);
|
|
2829
|
+
console.log(` ${chalk3.bold("Premium:")} ${chalk3.yellow(`\u2B21${report.premiumRequests}`)}`);
|
|
2830
|
+
console.log();
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2212
2833
|
// src/index.ts
|
|
2213
2834
|
var program = new Command();
|
|
2214
|
-
program.name("copilot-agent").version("0.
|
|
2835
|
+
program.name("copilot-agent").version("0.9.0").description("Autonomous AI agent manager \u2014 auto-resume, task discovery, overnight runs. Supports GitHub Copilot CLI + Claude Code.");
|
|
2215
2836
|
registerStatusCommand(program);
|
|
2216
2837
|
registerWatchCommand(program);
|
|
2217
2838
|
registerRunCommand(program);
|
|
@@ -2220,5 +2841,8 @@ registerResearchCommand(program);
|
|
|
2220
2841
|
registerReportCommand(program);
|
|
2221
2842
|
registerDashboardCommand(program);
|
|
2222
2843
|
registerWebCommand(program);
|
|
2844
|
+
registerConfigCommand(program);
|
|
2845
|
+
registerProxyCommand(program);
|
|
2846
|
+
registerDiffCommand(program);
|
|
2223
2847
|
program.parse();
|
|
2224
2848
|
//# sourceMappingURL=index.js.map
|