copilot-agent 0.8.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -24
- package/dist/index.js +1027 -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,680 @@ ${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
|
+
|
|
2833
|
+
// src/commands/quota.ts
|
|
2834
|
+
import chalk4 from "chalk";
|
|
2835
|
+
|
|
2836
|
+
// src/lib/quota.ts
|
|
2837
|
+
import { join as join9 } from "path";
|
|
2838
|
+
import { homedir as homedir7 } from "os";
|
|
2839
|
+
var DATA_DIR = join9(homedir7(), ".copilot-agent");
|
|
2840
|
+
var USAGE_FILE = join9(DATA_DIR, "usage.jsonl");
|
|
2841
|
+
function buildUsageSummary(days) {
|
|
2842
|
+
const sessions = listAllSessions(500);
|
|
2843
|
+
const cutoff = days ? Date.now() - days * 864e5 : 0;
|
|
2844
|
+
const filtered = sessions.filter((s) => s.mtime >= cutoff);
|
|
2845
|
+
const summary = {
|
|
2846
|
+
total: { sessions: 0, premium: 0, tokens: 0, turns: 0, durationMs: 0 },
|
|
2847
|
+
copilot: { sessions: 0, premium: 0, tokens: 0 },
|
|
2848
|
+
claude: { sessions: 0, premium: 0, tokens: 0 },
|
|
2849
|
+
byDay: {}
|
|
2850
|
+
};
|
|
2851
|
+
for (const s of filtered) {
|
|
2852
|
+
const report = getAgentSessionReport(s.id, s.agent);
|
|
2853
|
+
if (!report) continue;
|
|
2854
|
+
summary.total.sessions++;
|
|
2855
|
+
summary.total.premium += report.premiumRequests;
|
|
2856
|
+
summary.total.tokens += report.outputTokens;
|
|
2857
|
+
summary.total.turns += report.assistantTurns;
|
|
2858
|
+
summary.total.durationMs += report.durationMs;
|
|
2859
|
+
const bucket = s.agent === "claude" ? summary.claude : summary.copilot;
|
|
2860
|
+
bucket.sessions++;
|
|
2861
|
+
bucket.premium += report.premiumRequests;
|
|
2862
|
+
bucket.tokens += report.outputTokens;
|
|
2863
|
+
const day = new Date(s.mtime).toISOString().slice(0, 10);
|
|
2864
|
+
if (!summary.byDay[day]) summary.byDay[day] = { premium: 0, tokens: 0, sessions: 0 };
|
|
2865
|
+
summary.byDay[day].premium += report.premiumRequests;
|
|
2866
|
+
summary.byDay[day].tokens += report.outputTokens;
|
|
2867
|
+
summary.byDay[day].sessions++;
|
|
2868
|
+
}
|
|
2869
|
+
return summary;
|
|
2870
|
+
}
|
|
2871
|
+
function formatTokens(n) {
|
|
2872
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
2873
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
2874
|
+
return String(n);
|
|
2875
|
+
}
|
|
2876
|
+
function formatDurationShort(ms) {
|
|
2877
|
+
if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
|
|
2878
|
+
if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
|
|
2879
|
+
const h = Math.floor(ms / 36e5);
|
|
2880
|
+
const m = Math.round(ms % 36e5 / 6e4);
|
|
2881
|
+
return `${h}h ${m}m`;
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
// src/commands/quota.ts
|
|
2885
|
+
function registerQuotaCommand(program2) {
|
|
2886
|
+
program2.command("quota").description("Track premium requests, tokens, and usage over time").option("-d, --days <n>", "Number of days to show", "7").option("--all", "Show all-time usage").action((opts) => {
|
|
2887
|
+
const days = opts.all ? void 0 : parseInt(opts.days, 10);
|
|
2888
|
+
const label = days ? `Last ${days} days` : "All time";
|
|
2889
|
+
const summary = buildUsageSummary(days);
|
|
2890
|
+
console.log();
|
|
2891
|
+
console.log(chalk4.bold.cyan(` \u2B21 Usage Summary \u2014 ${label}`));
|
|
2892
|
+
console.log(chalk4.dim(` ${"\u2500".repeat(50)}`));
|
|
2893
|
+
console.log();
|
|
2894
|
+
const t = summary.total;
|
|
2895
|
+
console.log(` ${chalk4.bold("Sessions")} ${chalk4.white(String(t.sessions))}`);
|
|
2896
|
+
console.log(` ${chalk4.bold("Premium")} ${chalk4.yellow("\u2B21 " + String(t.premium))}`);
|
|
2897
|
+
console.log(` ${chalk4.bold("Tokens")} ${chalk4.green(formatTokens(t.tokens))}`);
|
|
2898
|
+
console.log(` ${chalk4.bold("Turns")} ${chalk4.white(String(t.turns))}`);
|
|
2899
|
+
console.log(` ${chalk4.bold("Total time")} ${chalk4.white(formatDurationShort(t.durationMs))}`);
|
|
2900
|
+
console.log();
|
|
2901
|
+
if (summary.copilot.sessions > 0 || summary.claude.sessions > 0) {
|
|
2902
|
+
console.log(chalk4.bold.cyan(" Per Agent"));
|
|
2903
|
+
console.log(chalk4.dim(` ${"\u2500".repeat(50)}`));
|
|
2904
|
+
if (summary.copilot.sessions > 0) {
|
|
2905
|
+
const c = summary.copilot;
|
|
2906
|
+
console.log(` ${chalk4.cyan("copilot")} ${String(c.sessions).padStart(4)} sessions ${chalk4.yellow("\u2B21" + String(c.premium).padStart(5))} ${chalk4.green(formatTokens(c.tokens).padStart(7))} tokens`);
|
|
2907
|
+
}
|
|
2908
|
+
if (summary.claude.sessions > 0) {
|
|
2909
|
+
const c = summary.claude;
|
|
2910
|
+
console.log(` ${chalk4.yellow("claude ")} ${String(c.sessions).padStart(4)} sessions ${chalk4.yellow("\u2B21" + String(c.premium).padStart(5))} ${chalk4.green(formatTokens(c.tokens).padStart(7))} tokens`);
|
|
2911
|
+
}
|
|
2912
|
+
console.log();
|
|
2913
|
+
}
|
|
2914
|
+
const dayEntries = Object.entries(summary.byDay).sort((a, b) => a[0].localeCompare(b[0]));
|
|
2915
|
+
if (dayEntries.length > 0) {
|
|
2916
|
+
console.log(chalk4.bold.cyan(" Daily Usage"));
|
|
2917
|
+
console.log(chalk4.dim(` ${"\u2500".repeat(50)}`));
|
|
2918
|
+
const maxPremium = Math.max(...dayEntries.map(([, d]) => d.premium), 1);
|
|
2919
|
+
const barWidth = 24;
|
|
2920
|
+
for (const [day, data] of dayEntries.slice(-14)) {
|
|
2921
|
+
const shortDay = day.slice(5);
|
|
2922
|
+
const filled = Math.round(data.premium / maxPremium * barWidth);
|
|
2923
|
+
const bar2 = chalk4.cyan("\u2588".repeat(filled)) + chalk4.dim("\u2591".repeat(barWidth - filled));
|
|
2924
|
+
console.log(` ${chalk4.dim(shortDay)} ${bar2} ${chalk4.yellow("\u2B21" + String(data.premium).padStart(4))} ${chalk4.dim(String(data.sessions) + " sess")}`);
|
|
2925
|
+
}
|
|
2926
|
+
console.log();
|
|
2927
|
+
}
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
// src/commands/compact.ts
|
|
2932
|
+
import chalk5 from "chalk";
|
|
2933
|
+
|
|
2934
|
+
// src/lib/compact.ts
|
|
2935
|
+
import { writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
|
|
2936
|
+
import { join as join10 } from "path";
|
|
2937
|
+
import { homedir as homedir8 } from "os";
|
|
2938
|
+
var COMPACT_DIR = join10(homedir8(), ".copilot-agent", "compacts");
|
|
2939
|
+
function ensureDir2() {
|
|
2940
|
+
if (!existsSync8(COMPACT_DIR)) mkdirSync6(COMPACT_DIR, { recursive: true });
|
|
2941
|
+
}
|
|
2942
|
+
function compactSession(sessionId, agent) {
|
|
2943
|
+
const report = getAgentSessionReport(sessionId, agent);
|
|
2944
|
+
if (!report) return null;
|
|
2945
|
+
const project = report.cwd?.split("/").pop() || "unknown";
|
|
2946
|
+
const done = [];
|
|
2947
|
+
for (const task of report.taskCompletions) {
|
|
2948
|
+
done.push(task.split("\n")[0].slice(0, 100));
|
|
2949
|
+
}
|
|
2950
|
+
for (const file of report.filesCreated) {
|
|
2951
|
+
done.push(`Created ${file}`);
|
|
2952
|
+
}
|
|
2953
|
+
for (const file of report.filesEdited) {
|
|
2954
|
+
done.push(`Edited ${file}`);
|
|
2955
|
+
}
|
|
2956
|
+
const remaining = [];
|
|
2957
|
+
if (!report.complete) {
|
|
2958
|
+
remaining.push("Session was interrupted before completion");
|
|
2959
|
+
}
|
|
2960
|
+
for (const err of report.errors) {
|
|
2961
|
+
remaining.push(`Fix: ${err.split("\n")[0].slice(0, 100)}`);
|
|
2962
|
+
}
|
|
2963
|
+
const filesChanged = [
|
|
2964
|
+
...report.filesCreated.map((f) => `+ ${f}`),
|
|
2965
|
+
...report.filesEdited.map((f) => `~ ${f}`)
|
|
2966
|
+
];
|
|
2967
|
+
const commits = report.gitCommits.map((c) => c.split("\n")[0].slice(0, 100));
|
|
2968
|
+
const durationMs = report.durationMs;
|
|
2969
|
+
const durationStr = durationMs < 6e4 ? `${Math.round(durationMs / 1e3)}s` : durationMs < 36e5 ? `${Math.round(durationMs / 6e4)}m` : `${Math.floor(durationMs / 36e5)}h ${Math.round(durationMs % 36e5 / 6e4)}m`;
|
|
2970
|
+
const lines = [];
|
|
2971
|
+
lines.push(`## Session Context (auto-generated)`);
|
|
2972
|
+
lines.push(`**Project:** ${project} | **Agent:** ${report.agent} | **Duration:** ${durationStr}`);
|
|
2973
|
+
lines.push(`**Turns:** ${report.assistantTurns} | **Tokens:** ${report.outputTokens.toLocaleString()} | **Premium:** ${report.premiumRequests}`);
|
|
2974
|
+
lines.push("");
|
|
2975
|
+
if (report.summary) {
|
|
2976
|
+
lines.push(`**Task:** ${report.summary}`);
|
|
2977
|
+
lines.push("");
|
|
2978
|
+
}
|
|
2979
|
+
if (done.length > 0) {
|
|
2980
|
+
lines.push("### \u2705 Completed");
|
|
2981
|
+
for (const d of done) lines.push(`- ${d}`);
|
|
2982
|
+
lines.push("");
|
|
2983
|
+
}
|
|
2984
|
+
if (remaining.length > 0) {
|
|
2985
|
+
lines.push("### \u23F3 Remaining");
|
|
2986
|
+
for (const r of remaining) lines.push(`- ${r}`);
|
|
2987
|
+
lines.push("");
|
|
2988
|
+
}
|
|
2989
|
+
if (commits.length > 0) {
|
|
2990
|
+
lines.push("### Git Commits");
|
|
2991
|
+
for (const c of commits) lines.push(`- ${c}`);
|
|
2992
|
+
lines.push("");
|
|
2993
|
+
}
|
|
2994
|
+
if (filesChanged.length > 0) {
|
|
2995
|
+
lines.push("### Files Changed");
|
|
2996
|
+
for (const f of filesChanged) lines.push(`- ${f}`);
|
|
2997
|
+
lines.push("");
|
|
2998
|
+
}
|
|
2999
|
+
if (report.errors.length > 0) {
|
|
3000
|
+
lines.push("### \u26A0\uFE0F Errors Encountered");
|
|
3001
|
+
for (const e of report.errors.slice(0, 5)) lines.push(`- ${e.split("\n")[0]}`);
|
|
3002
|
+
lines.push("");
|
|
3003
|
+
}
|
|
3004
|
+
const topTools = Object.entries(report.toolUsage).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
3005
|
+
if (topTools.length > 0) {
|
|
3006
|
+
lines.push("### Tools Used");
|
|
3007
|
+
for (const [tool, count] of topTools) lines.push(`- ${tool}: ${count}x`);
|
|
3008
|
+
lines.push("");
|
|
3009
|
+
}
|
|
3010
|
+
const markdown = lines.join("\n");
|
|
3011
|
+
return {
|
|
3012
|
+
sessionId,
|
|
3013
|
+
agent: report.agent,
|
|
3014
|
+
project,
|
|
3015
|
+
summary: report.summary || "",
|
|
3016
|
+
done,
|
|
3017
|
+
remaining,
|
|
3018
|
+
filesChanged,
|
|
3019
|
+
commits,
|
|
3020
|
+
errors: report.errors.map((e) => e.split("\n")[0]),
|
|
3021
|
+
stats: {
|
|
3022
|
+
turns: report.assistantTurns,
|
|
3023
|
+
tokens: report.outputTokens,
|
|
3024
|
+
premium: report.premiumRequests,
|
|
3025
|
+
duration: durationStr
|
|
3026
|
+
},
|
|
3027
|
+
markdown
|
|
3028
|
+
};
|
|
3029
|
+
}
|
|
3030
|
+
function saveCompact(compact) {
|
|
3031
|
+
ensureDir2();
|
|
3032
|
+
const filename = `${compact.sessionId.slice(0, 12)}.md`;
|
|
3033
|
+
const filepath = join10(COMPACT_DIR, filename);
|
|
3034
|
+
writeFileSync4(filepath, compact.markdown, "utf-8");
|
|
3035
|
+
return filepath;
|
|
3036
|
+
}
|
|
3037
|
+
function buildResumePrompt(compact) {
|
|
3038
|
+
const parts = [];
|
|
3039
|
+
parts.push("Continue the previous task. Here is the context from the interrupted session:");
|
|
3040
|
+
parts.push("");
|
|
3041
|
+
if (compact.summary) parts.push(`Task: ${compact.summary}`);
|
|
3042
|
+
if (compact.done.length > 0) {
|
|
3043
|
+
parts.push("Already completed:");
|
|
3044
|
+
for (const d of compact.done.slice(0, 10)) parts.push(`- ${d}`);
|
|
3045
|
+
}
|
|
3046
|
+
if (compact.remaining.length > 0) {
|
|
3047
|
+
parts.push("Still needs to be done:");
|
|
3048
|
+
for (const r of compact.remaining) parts.push(`- ${r}`);
|
|
3049
|
+
}
|
|
3050
|
+
if (compact.errors.length > 0) {
|
|
3051
|
+
parts.push("Errors to address:");
|
|
3052
|
+
for (const e of compact.errors.slice(0, 3)) parts.push(`- ${e}`);
|
|
3053
|
+
}
|
|
3054
|
+
parts.push("");
|
|
3055
|
+
parts.push("Please continue where the previous session left off.");
|
|
3056
|
+
return parts.join("\n");
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
// src/commands/compact.ts
|
|
3060
|
+
function registerCompactCommand(program2) {
|
|
3061
|
+
program2.command("compact [session-id]").description("Generate context summary from a session for handoff/resume").option("--save", "Save compact to ~/.copilot-agent/compacts/").option("--resume-prompt", "Output a resume prompt for the next session").option("-a, --agent <type>", "Agent type: copilot | claude").action((sessionId, opts) => {
|
|
3062
|
+
try {
|
|
3063
|
+
showCompact(sessionId, opts);
|
|
3064
|
+
} catch (err) {
|
|
3065
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3066
|
+
console.error(chalk5.red(` \u2717 ${msg}`));
|
|
3067
|
+
}
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
3070
|
+
function showCompact(sessionId, opts) {
|
|
3071
|
+
if (!sessionId) {
|
|
3072
|
+
const sessions = listAllSessions(10);
|
|
3073
|
+
if (sessions.length === 0) {
|
|
3074
|
+
console.log(chalk5.dim(" No sessions found"));
|
|
3075
|
+
return;
|
|
3076
|
+
}
|
|
3077
|
+
sessionId = sessions[0].id;
|
|
3078
|
+
console.log(chalk5.dim(` Using latest session: ${sessionId.slice(0, 12)}\u2026
|
|
3079
|
+
`));
|
|
3080
|
+
}
|
|
3081
|
+
const compact = compactSession(sessionId, opts.agent);
|
|
3082
|
+
if (!compact) {
|
|
3083
|
+
console.log(chalk5.red(` \u2717 Session not found: ${sessionId}`));
|
|
3084
|
+
return;
|
|
3085
|
+
}
|
|
3086
|
+
if (opts.resumePrompt) {
|
|
3087
|
+
console.log(buildResumePrompt(compact));
|
|
3088
|
+
return;
|
|
3089
|
+
}
|
|
3090
|
+
const agentTag = compact.agent === "claude" ? chalk5.yellow("[claude]") : chalk5.cyan("[copilot]");
|
|
3091
|
+
console.log(chalk5.bold.cyan(` \u{1F4CB} Session Compact \u2014 ${compact.project}`) + ` ${agentTag}`);
|
|
3092
|
+
console.log(chalk5.dim(` ${compact.sessionId}`));
|
|
3093
|
+
console.log(chalk5.dim(` ${"\u2500".repeat(50)}`));
|
|
3094
|
+
console.log();
|
|
3095
|
+
console.log(` ${chalk5.bold("Duration")} ${compact.stats.duration} ${chalk5.bold("Turns")} ${compact.stats.turns} ${chalk5.bold("Tokens")} ${compact.stats.tokens.toLocaleString()} ${chalk5.bold("Premium")} ${chalk5.yellow("\u2B21" + compact.stats.premium)}`);
|
|
3096
|
+
console.log();
|
|
3097
|
+
if (compact.summary) {
|
|
3098
|
+
console.log(chalk5.bold(" Task:") + ` ${compact.summary}`);
|
|
3099
|
+
console.log();
|
|
3100
|
+
}
|
|
3101
|
+
if (compact.done.length > 0) {
|
|
3102
|
+
console.log(chalk5.bold.green(" \u2705 Completed:"));
|
|
3103
|
+
for (const d of compact.done.slice(0, 15)) {
|
|
3104
|
+
console.log(chalk5.green(` \u25CF ${d}`));
|
|
3105
|
+
}
|
|
3106
|
+
if (compact.done.length > 15) console.log(chalk5.dim(` ... +${compact.done.length - 15} more`));
|
|
3107
|
+
console.log();
|
|
3108
|
+
}
|
|
3109
|
+
if (compact.remaining.length > 0) {
|
|
3110
|
+
console.log(chalk5.bold.yellow(" \u23F3 Remaining:"));
|
|
3111
|
+
for (const r of compact.remaining) {
|
|
3112
|
+
console.log(chalk5.yellow(` \u25CB ${r}`));
|
|
3113
|
+
}
|
|
3114
|
+
console.log();
|
|
3115
|
+
}
|
|
3116
|
+
if (compact.commits.length > 0) {
|
|
3117
|
+
console.log(chalk5.bold.cyan(" Commits:"));
|
|
3118
|
+
for (const c of compact.commits.slice(0, 8)) {
|
|
3119
|
+
console.log(chalk5.cyan(` \u25CF ${c}`));
|
|
3120
|
+
}
|
|
3121
|
+
console.log();
|
|
3122
|
+
}
|
|
3123
|
+
if (compact.errors.length > 0) {
|
|
3124
|
+
console.log(chalk5.bold.red(" \u26A0\uFE0F Errors:"));
|
|
3125
|
+
for (const e of compact.errors.slice(0, 5)) {
|
|
3126
|
+
console.log(chalk5.red(` \u2717 ${e}`));
|
|
3127
|
+
}
|
|
3128
|
+
console.log();
|
|
3129
|
+
}
|
|
3130
|
+
if (opts.save) {
|
|
3131
|
+
const path = saveCompact(compact);
|
|
3132
|
+
console.log(chalk5.green(` \u2714 Saved to ${path}`));
|
|
3133
|
+
console.log();
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
|
|
2212
3137
|
// src/index.ts
|
|
2213
3138
|
var program = new Command();
|
|
2214
|
-
program.name("copilot-agent").version("0.
|
|
3139
|
+
program.name("copilot-agent").version("0.10.0").description("Autonomous AI agent manager \u2014 auto-resume, task discovery, overnight runs. Supports GitHub Copilot CLI + Claude Code.");
|
|
2215
3140
|
registerStatusCommand(program);
|
|
2216
3141
|
registerWatchCommand(program);
|
|
2217
3142
|
registerRunCommand(program);
|
|
@@ -2220,5 +3145,10 @@ registerResearchCommand(program);
|
|
|
2220
3145
|
registerReportCommand(program);
|
|
2221
3146
|
registerDashboardCommand(program);
|
|
2222
3147
|
registerWebCommand(program);
|
|
3148
|
+
registerConfigCommand(program);
|
|
3149
|
+
registerProxyCommand(program);
|
|
3150
|
+
registerDiffCommand(program);
|
|
3151
|
+
registerQuotaCommand(program);
|
|
3152
|
+
registerCompactCommand(program);
|
|
2223
3153
|
program.parse();
|
|
2224
3154
|
//# sourceMappingURL=index.js.map
|