ai-agentmanager 1.0.0 โ 1.0.2
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/CHANGELOG.md +57 -0
- package/NOTICE +1 -1
- package/README.md +33 -15
- package/SECURITY.md +6 -6
- package/bin/agentmgr-hub.js +1 -1
- package/bin/agentmgr.js +10 -3
- package/docs/architecture.md +1 -1
- package/docs/distributed.md +1 -1
- package/docs/engines.md +5 -5
- package/docs/images/agents.jpg +0 -0
- package/docs/images/conflict-radar.jpg +0 -0
- package/docs/plugins.md +2 -2
- package/package.json +6 -4
- package/public/app.js +180 -9
- package/public/hub/index.html +3 -3
- package/public/icons/icon-192.png +0 -0
- package/public/icons/icon-512.png +0 -0
- package/public/icons/icon.svg +1 -0
- package/public/index.html +25 -6
- package/public/manifest.webmanifest +16 -0
- package/public/share.js +1 -1
- package/public/style.css +36 -2
- package/src/about.js +70 -0
- package/src/brief.js +1 -1
- package/src/cli.js +42 -1
- package/src/conflicts.js +3 -3
- package/src/doctor.js +113 -0
- package/src/engines/antigravity.js +1 -1
- package/src/engines/claude.js +14 -0
- package/src/engines/codex.js +1 -1
- package/src/engines/index.js +9 -1
- package/src/git.js +2 -2
- package/src/inbox.js +1 -1
- package/src/judge.js +12 -0
- package/src/manager.js +29 -7
- package/src/notify.js +1 -1
- package/src/plugins.js +1 -1
- package/src/preview.js +4 -4
- package/src/server.js +4 -1
- package/src/service.js +1 -1
- package/src/sessions.js +2 -2
- package/src/store.js +2 -2
- package/src/tracker.js +5 -1
package/public/app.js
CHANGED
|
@@ -464,7 +464,7 @@ function resultsCard(a) {
|
|
|
464
464
|
const [icon, label] = OUTCOME[r.outcome] ?? ['๐', r.outcome];
|
|
465
465
|
const item = (x, cls) => `<li class="${cls}">${x.url ? `<a href="${esc(x.url)}" target="_blank" rel="noopener">${md(x.text).replace(/^<p>|<\/p>\s*$/g, '')}</a>` : md(x.text).replace(/^<p>|<\/p>\s*$/g, '')}</li>`;
|
|
466
466
|
return `<div class="results ${r.outcome.replace(' ', '-')}">
|
|
467
|
-
<h4>Results <span class="outcome">${icon} ${label}</span>${r.fromAgent ? '' : ' <span class="hint">(from what Agent Manager can see; the agent adds its own when it next updates its brief)</span>'}</h4>
|
|
467
|
+
<h4>Results <span class="outcome">${icon} ${label}</span>${r.fromAgent ? '' : ' <span class="hint">(from what AI Agent Manager can see; the agent adds its own when it next updates its brief)</span>'}</h4>
|
|
468
468
|
${r.blockers.length ? `<ul class="blockers">${r.blockers.map((x) => item(x, 'blocker')).join('')}</ul>` : ''}
|
|
469
469
|
${r.delivered.length ? `<ul class="delivered">${r.delivered.map((x) => item(x, 'delivered')).join('')}</ul>` : '<p class="hint">Nothing delivered yet.</p>'}
|
|
470
470
|
${r.notes ? `<div class="md">${md(r.notes)}</div>` : ''}
|
|
@@ -556,6 +556,11 @@ const copyBtn = (value) => `<button class="link" data-copy="${esc(value)}" title
|
|
|
556
556
|
/** Agents waiting on you first, then running ones, then the rest. */
|
|
557
557
|
const rank = (a) => (a.runtime.attention ? 0 : a.runtime.running ? 1 : 2);
|
|
558
558
|
|
|
559
|
+
// Agents with no work and no input from you for a day sink below the active ones.
|
|
560
|
+
const ACTIVE_MS = 24 * 3600 * 1000;
|
|
561
|
+
const isActive = (a) =>
|
|
562
|
+
Boolean(a.runtime.attention || a.runtime.busy || a.status === 'preparing' || (a.activeAt && Date.now() - Date.parse(a.activeAt) < ACTIVE_MS));
|
|
563
|
+
|
|
559
564
|
// Projects the user collapsed in the tree (kept per browser).
|
|
560
565
|
const collapsed = new Set(
|
|
561
566
|
(() => {
|
|
@@ -578,12 +583,14 @@ const prioChip = (p, { always = false } = {}) =>
|
|
|
578
583
|
|
|
579
584
|
function agentItem(a) {
|
|
580
585
|
const st = statusOf(a);
|
|
581
|
-
|
|
586
|
+
const idle = !isActive(a);
|
|
587
|
+
return `<li data-id="${a.id}" class="agent pr${a.priority ?? 2} ${a.id === selectedId ? 'selected' : ''} ${idle ? 'idle' : ''}">
|
|
582
588
|
<div class="line1">${prioChip(a.priority ?? 2)}${a.results?.outcome === 'blocked' ? '<span class="outcome-mark" title="Blocked">โ</span>' : a.results?.outcome === 'done' ? '<span class="outcome-mark" title="Done">โ
</span>' : ''}<span class="name">${esc(a.name)}</span>${flags(a)}<span class="pill ${st}">${pillText(st)}</span></div>
|
|
583
589
|
<div class="intent">${a.kind === 'general' ? '<span class="tag">general</span> ' : ''}${a.engine && a.engine !== 'claude' ? `<span class="tag engine">${esc(ENGINE_LABEL[a.engine] ?? a.engine)}</span> ` : ''}${esc(a.intent || 'General session')}</div>
|
|
584
590
|
${a.status === 'preparing' && a.progress ? `<div class="progress-line">${esc(progressText(a.progress))}</div>` : ''}
|
|
585
591
|
<div class="meta">
|
|
586
592
|
<span class="mono">${a.id}</span>
|
|
593
|
+
${idle && a.activeAt ? `<span title="Idle: no work and no input from you for over a day">๐ค ${ago(a.activeAt)}</span>` : ''}
|
|
587
594
|
${a.git?.branch ? `<span class="branch" title="${esc(a.git.branch)}">โ ${esc(a.git.branch)}</span>` : ''}
|
|
588
595
|
${a.prs.length ? `<span>${a.prs.length} PR</span>` : ''}
|
|
589
596
|
${a.bugs.length ? `<span>${a.bugs.length} bug</span>` : ''}
|
|
@@ -608,8 +615,15 @@ function projectGroups(list) {
|
|
|
608
615
|
}
|
|
609
616
|
const out = [...groups.values()];
|
|
610
617
|
for (const g of out) {
|
|
611
|
-
//
|
|
612
|
-
g.agents.sort(
|
|
618
|
+
// Active in the last day first; then highest priority, agents needing you, running ones, most recent.
|
|
619
|
+
g.agents.sort(
|
|
620
|
+
(a, b) =>
|
|
621
|
+
isActive(b) - isActive(a) ||
|
|
622
|
+
(a.priority ?? 2) - (b.priority ?? 2) ||
|
|
623
|
+
rank(a) - rank(b) ||
|
|
624
|
+
(b.activeAt ?? b.createdAt).localeCompare(a.activeAt ?? a.createdAt),
|
|
625
|
+
);
|
|
626
|
+
g.active = g.agents.filter(isActive).length;
|
|
613
627
|
g.top = Math.min(...g.agents.map((a) => a.priority ?? 2));
|
|
614
628
|
g.running = g.agents.filter((a) => a.runtime.running).length;
|
|
615
629
|
g.waiting = g.agents.filter((a) => a.runtime.attention).length;
|
|
@@ -623,13 +637,19 @@ function projectGroups(list) {
|
|
|
623
637
|
}
|
|
624
638
|
const special = (g) => (g.key === 'general' ? 1 : g.key === 'other' ? 2 : 0);
|
|
625
639
|
return out.sort(
|
|
626
|
-
(a, b) =>
|
|
640
|
+
(a, b) =>
|
|
641
|
+
special(a) - special(b) ||
|
|
642
|
+
(b.active > 0) - (a.active > 0) ||
|
|
643
|
+
a.top - b.top ||
|
|
644
|
+
b.waiting - a.waiting ||
|
|
645
|
+
b.running - a.running ||
|
|
646
|
+
a.label.localeCompare(b.label),
|
|
627
647
|
);
|
|
628
648
|
}
|
|
629
649
|
|
|
630
650
|
function renderList() {
|
|
631
651
|
const waiting = [...agents.values()].filter((a) => a.runtime.attention).length;
|
|
632
|
-
document.title = waiting ? `(${waiting}) Agent Manager` : 'Agent Manager';
|
|
652
|
+
document.title = waiting ? `(${waiting}) AI Agent Manager` : 'AI Agent Manager';
|
|
633
653
|
// "@cc" shows one project; any other text also matches shortcuts.
|
|
634
654
|
const at = filterText.startsWith('@') ? filterText.slice(1) : null;
|
|
635
655
|
const list = [...agents.values()].filter((a) =>
|
|
@@ -1725,11 +1745,107 @@ const term = {
|
|
|
1725
1745
|
this.agentId = null;
|
|
1726
1746
|
$('#term-wrap').hidden = true;
|
|
1727
1747
|
$('#detail').classList.remove('with-term');
|
|
1748
|
+
if (document.body.classList.contains('term-max')) setExpanded(false);
|
|
1749
|
+
if (document.fullscreenElement) document.exitFullscreen();
|
|
1728
1750
|
},
|
|
1729
1751
|
};
|
|
1730
1752
|
|
|
1731
1753
|
$('#term-close').onclick = () => term.close();
|
|
1732
1754
|
$('#term-fit').onclick = () => term.refit();
|
|
1755
|
+
|
|
1756
|
+
// ------------------------------------------------------------------ resizing, expanding, full screen
|
|
1757
|
+
|
|
1758
|
+
const layoutPref = {
|
|
1759
|
+
get(key) {
|
|
1760
|
+
try {
|
|
1761
|
+
return localStorage.getItem(`agentmgr.${key}`);
|
|
1762
|
+
} catch {
|
|
1763
|
+
return null;
|
|
1764
|
+
}
|
|
1765
|
+
},
|
|
1766
|
+
set(key, value) {
|
|
1767
|
+
try {
|
|
1768
|
+
value == null ? localStorage.removeItem(`agentmgr.${key}`) : localStorage.setItem(`agentmgr.${key}`, value);
|
|
1769
|
+
} catch {}
|
|
1770
|
+
},
|
|
1771
|
+
};
|
|
1772
|
+
|
|
1773
|
+
function applyLayout() {
|
|
1774
|
+
const side = layoutPref.get('sideWidth');
|
|
1775
|
+
const brief = layoutPref.get('briefHeight');
|
|
1776
|
+
document.documentElement.style.setProperty('--side-w', side ? `${side}px` : '340px');
|
|
1777
|
+
if (brief) document.documentElement.style.setProperty('--brief-h', `${brief}px`);
|
|
1778
|
+
else document.documentElement.style.removeProperty('--brief-h');
|
|
1779
|
+
$('#detail').classList.toggle('brief-sized', Boolean(brief));
|
|
1780
|
+
}
|
|
1781
|
+
applyLayout();
|
|
1782
|
+
|
|
1783
|
+
/** Drag a handle; `move` gets the pointer position and returns the value to keep. */
|
|
1784
|
+
function dragHandle(el, axis, move, key) {
|
|
1785
|
+
el.addEventListener('pointerdown', (e) => {
|
|
1786
|
+
e.preventDefault();
|
|
1787
|
+
el.setPointerCapture(e.pointerId);
|
|
1788
|
+
el.classList.add('dragging');
|
|
1789
|
+
document.body.classList.add('resizing', axis);
|
|
1790
|
+
let value = null;
|
|
1791
|
+
const onMove = (ev) => {
|
|
1792
|
+
value = move(ev);
|
|
1793
|
+
applyLayout();
|
|
1794
|
+
};
|
|
1795
|
+
const onUp = () => {
|
|
1796
|
+
el.classList.remove('dragging');
|
|
1797
|
+
document.body.classList.remove('resizing', axis);
|
|
1798
|
+
el.removeEventListener('pointermove', onMove);
|
|
1799
|
+
el.removeEventListener('pointerup', onUp);
|
|
1800
|
+
if (value != null) layoutPref.set(key, Math.round(value));
|
|
1801
|
+
term.refit();
|
|
1802
|
+
};
|
|
1803
|
+
el.addEventListener('pointermove', onMove);
|
|
1804
|
+
el.addEventListener('pointerup', onUp);
|
|
1805
|
+
});
|
|
1806
|
+
el.addEventListener('dblclick', () => {
|
|
1807
|
+
layoutPref.set(key, null);
|
|
1808
|
+
applyLayout();
|
|
1809
|
+
requestAnimationFrame(() => term.refit());
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
dragHandle($('#split-side'), 'cols', (ev) => {
|
|
1814
|
+
const w = Math.min(Math.max(ev.clientX, 220), window.innerWidth * 0.6);
|
|
1815
|
+
layoutPref.set('sideWidth', Math.round(w));
|
|
1816
|
+
return w;
|
|
1817
|
+
}, 'sideWidth');
|
|
1818
|
+
|
|
1819
|
+
dragHandle($('#split-term'), 'rows', (ev) => {
|
|
1820
|
+
const top = $('#detail').getBoundingClientRect().top;
|
|
1821
|
+
const h = Math.min(Math.max(ev.clientY - top, 60), $('#detail').clientHeight - 160);
|
|
1822
|
+
layoutPref.set('briefHeight', Math.round(h));
|
|
1823
|
+
return h;
|
|
1824
|
+
}, 'briefHeight');
|
|
1825
|
+
|
|
1826
|
+
function setExpanded(on) {
|
|
1827
|
+
document.body.classList.toggle('term-max', on);
|
|
1828
|
+
$('#term-max').classList.toggle('on', on);
|
|
1829
|
+
$('#term-max').textContent = on ? 'โคก Restore' : 'โคข Expand';
|
|
1830
|
+
requestAnimationFrame(() => term.refit());
|
|
1831
|
+
}
|
|
1832
|
+
$('#term-max').onclick = () => setExpanded(!document.body.classList.contains('term-max'));
|
|
1833
|
+
$('#term-full').onclick = () => {
|
|
1834
|
+
if (document.fullscreenElement) document.exitFullscreen();
|
|
1835
|
+
else $('#term-wrap').requestFullscreen?.().catch(() => setExpanded(true));
|
|
1836
|
+
};
|
|
1837
|
+
document.addEventListener('fullscreenchange', () => {
|
|
1838
|
+
const on = document.fullscreenElement === $('#term-wrap');
|
|
1839
|
+
$('#term-full').classList.toggle('on', on);
|
|
1840
|
+
$('#term-full').textContent = on ? 'โถ Exit full screen' : 'โถ Full screen';
|
|
1841
|
+
requestAnimationFrame(() => term.refit());
|
|
1842
|
+
});
|
|
1843
|
+
document.addEventListener('keydown', (e) => {
|
|
1844
|
+
if (e.ctrlKey && e.shiftKey && e.code === 'KeyE' && !$('#term-wrap').hidden) {
|
|
1845
|
+
e.preventDefault();
|
|
1846
|
+
setExpanded(!document.body.classList.contains('term-max'));
|
|
1847
|
+
}
|
|
1848
|
+
}, true);
|
|
1733
1849
|
$('#term-resume').onclick = () => {
|
|
1734
1850
|
const id = term.agentId;
|
|
1735
1851
|
if (isShell(term.kind)) {
|
|
@@ -1995,7 +2111,7 @@ function renderImport() {
|
|
|
1995
2111
|
.join('')}</div>`,
|
|
1996
2112
|
)
|
|
1997
2113
|
.join('')
|
|
1998
|
-
: `<p class="hint">${importSessions.length ? 'Nothing matches.' : 'No Claude sessions started in a terminal in this period (Agent Manager\'s own agents are left out).'}</p>`;
|
|
2114
|
+
: `<p class="hint">${importSessions.length ? 'Nothing matches.' : 'No Claude sessions started in a terminal in this period (AI Agent Manager\'s own agents are left out).'}</p>`;
|
|
1999
2115
|
const picked = importSessions.filter((s) => importPicked.has(s.sessionId));
|
|
2000
2116
|
const open = picked.filter((s) => s.inUse).length;
|
|
2001
2117
|
Object.assign($('#import-go'), {
|
|
@@ -2562,8 +2678,8 @@ $('#detail-body').addEventListener('click', async (e) => {
|
|
|
2562
2678
|
break;
|
|
2563
2679
|
case 'delete': {
|
|
2564
2680
|
const cancelNote = a.status === 'preparing' ? '\n\nThis also cancels the workspace setup (clone / checkout) in progress.' : '';
|
|
2565
|
-
if (!confirm(`Delete agent "${a.name}"? Its session IDs will be forgotten by Agent Manager.${cancelNote}`)) return;
|
|
2566
|
-
const del = a.worktree && confirm('Also remove the git worktree Agent Manager created for it?\n(Uncommitted changes there will be lost.)');
|
|
2681
|
+
if (!confirm(`Delete agent "${a.name}"? Its session IDs will be forgotten by AI Agent Manager.${cancelNote}`)) return;
|
|
2682
|
+
const del = a.worktree && confirm('Also remove the git worktree AI Agent Manager created for it?\n(Uncommitted changes there will be lost.)');
|
|
2567
2683
|
act(async () => {
|
|
2568
2684
|
const res = await api('DELETE', `/agents/${a.id}${del ? '?deleteWorkspace=1' : ''}`);
|
|
2569
2685
|
if (res.warnings?.length) toast(res.warnings.join('; '), 6000);
|
|
@@ -2730,6 +2846,61 @@ $('#new-form').addEventListener('submit', async (e) => {
|
|
|
2730
2846
|
|
|
2731
2847
|
// ------------------------------------------------------------------ ask (general session)
|
|
2732
2848
|
|
|
2849
|
+
// ---------------------------------------------------------------- about
|
|
2850
|
+
const aboutDialog = $('#about-dialog');
|
|
2851
|
+
let aboutInfo = null;
|
|
2852
|
+
async function loadAbout() {
|
|
2853
|
+
aboutInfo = await api('GET', '/about');
|
|
2854
|
+
$('#about-btn').textContent = `v${aboutInfo.version}`;
|
|
2855
|
+
return aboutInfo;
|
|
2856
|
+
}
|
|
2857
|
+
loadAbout().catch(() => {});
|
|
2858
|
+
function renderAbout(a) {
|
|
2859
|
+
const link = (url, text = url.replace(/^https:\/\//, '')) => `<a href="${esc(url)}" target="_blank" rel="noopener">${esc(text)}</a>`;
|
|
2860
|
+
const when = (iso) => new Date(iso).toLocaleString();
|
|
2861
|
+
const rows = [
|
|
2862
|
+
['Version', `${esc(a.version)}${a.releaseDate ? ` <span class="hint">released ${esc(a.releaseDate)}</span>` : ''}`],
|
|
2863
|
+
['Author', esc(a.author ?? 'โ')],
|
|
2864
|
+
['License', esc(a.license ?? 'โ')],
|
|
2865
|
+
['Install', `<code>npm install -g ${esc(a.package)}</code>, then <code>${esc(a.command ?? 'agentmgr')}</code>`],
|
|
2866
|
+
['Code', a.links.repository ? link(a.links.repository) : 'โ'],
|
|
2867
|
+
['Releases', a.links.releases ? link(a.links.releases, 'Release notes') : 'โ'],
|
|
2868
|
+
['Report a problem', a.links.issues ? link(a.links.issues, 'GitHub issues') : 'โ'],
|
|
2869
|
+
null,
|
|
2870
|
+
['Daemon', `pid ${esc(a.daemon.pid)} ยท port ${esc(a.daemon.port ?? 'โ')} ยท running since ${esc(when(a.daemon.startedAt))}`],
|
|
2871
|
+
['Data folder', `<code>${esc(a.daemon.home ?? 'โ')}</code>`],
|
|
2872
|
+
['Node.js', esc(a.daemon.node)],
|
|
2873
|
+
['System', esc(a.daemon.platform)],
|
|
2874
|
+
['Claude Code', `<code>${esc(a.daemon.claudeBin ?? 'claude')}</code>`],
|
|
2875
|
+
];
|
|
2876
|
+
$('#about-list').innerHTML = rows.map((r) => (r ? `<dt>${r[0]}</dt><dd>${r[1]}</dd>` : '<div class="sep"></div>')).join('');
|
|
2877
|
+
}
|
|
2878
|
+
$('#about-btn').onclick = async () => {
|
|
2879
|
+
$('#about-update').hidden = true;
|
|
2880
|
+
if (aboutInfo) renderAbout(aboutInfo);
|
|
2881
|
+
else $('#about-list').innerHTML = '<dt>Loadingโฆ</dt><dd></dd>';
|
|
2882
|
+
aboutDialog.showModal();
|
|
2883
|
+
try {
|
|
2884
|
+
renderAbout(await loadAbout()); // fresh daemon details (uptime, pid) each time it opens
|
|
2885
|
+
} catch (err) {
|
|
2886
|
+
$('#about-list').innerHTML = `<dt>Error</dt><dd>${esc(err.message)}</dd>`;
|
|
2887
|
+
}
|
|
2888
|
+
};
|
|
2889
|
+
$('#about-close').onclick = () => aboutDialog.close();
|
|
2890
|
+
$('#about-check').onclick = async () => {
|
|
2891
|
+
const out = $('#about-update');
|
|
2892
|
+
out.hidden = false;
|
|
2893
|
+
out.textContent = 'Checkingโฆ';
|
|
2894
|
+
try {
|
|
2895
|
+
const r = await api('GET', '/about/latest');
|
|
2896
|
+
out.innerHTML = r.newer
|
|
2897
|
+
? `Version ${esc(r.latest)} is available. Update with <code>npm install -g ${esc(aboutInfo?.package ?? 'ai-agentmanager')}@latest</code>, then <code>agentmgr restart</code>.`
|
|
2898
|
+
: `You have the latest version (${esc(r.current)}).`;
|
|
2899
|
+
} catch (err) {
|
|
2900
|
+
out.textContent = `Couldn't check: ${err.message}`;
|
|
2901
|
+
}
|
|
2902
|
+
};
|
|
2903
|
+
|
|
2733
2904
|
const askDialog = $('#ask-dialog');
|
|
2734
2905
|
$('#ask-btn').onclick = () => {
|
|
2735
2906
|
$('#ask-error').hidden = true;
|
package/public/hub/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<meta name="referrer" content="no-referrer" />
|
|
7
|
-
<title>Agent Manager Hub</title>
|
|
7
|
+
<title>AI Agent Manager Hub</title>
|
|
8
8
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22%3E%3Cpath d=%22M8 1l7 7-7 7-7-7z%22 fill=%22%233b5bdb%22/%3E%3C/svg%3E" />
|
|
9
9
|
<link rel="stylesheet" href="/vendor/xterm/css/xterm.css" />
|
|
10
10
|
<link rel="stylesheet" href="/hub.css" />
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<header class="topbar">
|
|
14
14
|
<div class="brand">
|
|
15
15
|
<span class="logo" aria-hidden="true">โ</span>
|
|
16
|
-
<h1>Agent Manager <span class="muted">Hub</span></h1>
|
|
16
|
+
<h1>AI Agent Manager <span class="muted">Hub</span></h1>
|
|
17
17
|
<span id="hub-name" class="muted"></span>
|
|
18
18
|
</div>
|
|
19
19
|
<div class="top-actions" id="top-actions" hidden>
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
<dialog id="invite-dialog">
|
|
63
63
|
<form method="dialog" class="dialog">
|
|
64
64
|
<h2>Add a machine</h2>
|
|
65
|
-
<p class="muted">On the Mac to add (with Agent Manager installed), run:</p>
|
|
65
|
+
<p class="muted">On the Mac to add (with AI Agent Manager installed), run:</p>
|
|
66
66
|
<div class="cmd"><code id="invite-cmd">โฆ</code><button type="button" id="copy-btn" class="ghost">Copy</button></div>
|
|
67
67
|
<p class="muted small" id="invite-expiry"></p>
|
|
68
68
|
<p class="error small" id="invite-warning" hidden></p>
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><rect width="512" height="512" rx="112" fill="#151821"/><path d="M256 76l180 180-180 180L76 256z" fill="#6d8bff"/><path d="M256 166l90 90-90 90-90-90z" fill="#151821"/></svg>
|
package/public/index.html
CHANGED
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
6
6
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
7
|
-
<title>Agent Manager</title>
|
|
7
|
+
<title>AI Agent Manager</title>
|
|
8
8
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22%3E%3Cpath d=%22M8 1l7 7-7 7-7-7z%22 fill=%22%233b5bdb%22/%3E%3C/svg%3E" />
|
|
9
|
+
<link rel="manifest" href="/manifest.webmanifest" crossorigin="use-credentials" />
|
|
10
|
+
<meta name="theme-color" content="#151821" />
|
|
11
|
+
<link rel="apple-touch-icon" href="/icons/icon-192.png" />
|
|
9
12
|
<link rel="stylesheet" href="/vendor/xterm/css/xterm.css" />
|
|
10
13
|
<link rel="stylesheet" href="/style.css" />
|
|
11
14
|
</head>
|
|
@@ -13,13 +16,14 @@
|
|
|
13
16
|
<header class="topbar">
|
|
14
17
|
<div class="brand">
|
|
15
18
|
<span class="logo" aria-hidden="true">โ</span>
|
|
16
|
-
<h1>Agent Manager</h1>
|
|
19
|
+
<h1>AI Agent Manager</h1>
|
|
20
|
+
<button id="about-btn" class="about-btn" type="button" title="About AI Agent Manager: version, release, links">About</button>
|
|
17
21
|
<span id="conn" class="conn" title="Daemon connection">connectingโฆ</span>
|
|
18
22
|
</div>
|
|
19
23
|
<div class="top-actions">
|
|
20
24
|
<input id="filter" type="search" placeholder="Filter agents" aria-label="Filter agents" />
|
|
21
25
|
<button id="alerts-btn" class="local-only" title="Slack alerts when an agent needs you">Alerts</button>
|
|
22
|
-
<button id="remote-btn" class="local-only" title="Open Agent Manager from your phone">Remote access</button>
|
|
26
|
+
<button id="remote-btn" class="local-only" title="Open AI Agent Manager from your phone">Remote access</button>
|
|
23
27
|
<button id="ask-btn" title="A general Claude session with no repo or folder">Ask Claude</button>
|
|
24
28
|
<button id="new-btn" class="primary">+ New agents</button>
|
|
25
29
|
</div>
|
|
@@ -43,10 +47,12 @@
|
|
|
43
47
|
<p>Add one or more intents and a repo or folder to get started.</p>
|
|
44
48
|
</div>
|
|
45
49
|
</aside>
|
|
50
|
+
<div class="splitter split-side" id="split-side" role="separator" aria-orientation="vertical" title="Drag to resize the list; double-click to reset"></div>
|
|
46
51
|
|
|
47
52
|
<section class="detail" id="detail">
|
|
48
53
|
<div class="placeholder" id="placeholder">Select an agent to see its session, folder, repo, PRs and bugs.</div>
|
|
49
54
|
<div id="detail-body" hidden></div>
|
|
55
|
+
<div class="splitter split-term" id="split-term" role="separator" aria-orientation="horizontal" title="Drag to resize the terminal; double-click to reset"></div>
|
|
50
56
|
<div class="term-wrap" id="term-wrap" hidden>
|
|
51
57
|
<div class="term-bar">
|
|
52
58
|
<div class="term-tabs" role="tablist">
|
|
@@ -61,6 +67,8 @@
|
|
|
61
67
|
<button id="term-attach" class="ghost small" title="Attach files or images (you can also drop or paste them into the terminal)">๐ Attach</button>
|
|
62
68
|
<input type="file" id="term-file" multiple hidden>
|
|
63
69
|
<button id="term-fit" class="ghost small" title="Refit the terminal to the panel">Fit</button>
|
|
70
|
+
<button id="term-max" class="ghost small desk-only" title="Expand to fill the window (Ctrl+Shift+E)">โคข Expand</button>
|
|
71
|
+
<button id="term-full" class="ghost small desk-only" title="Full screen (Esc to leave)">โถ Full screen</button>
|
|
64
72
|
<button id="term-close" class="ghost small" title="Hide the terminal (the agent keeps running)">Hide terminal</button>
|
|
65
73
|
</div>
|
|
66
74
|
<div id="signin-bar" class="signin-bar" hidden></div>
|
|
@@ -79,12 +87,12 @@
|
|
|
79
87
|
</form>
|
|
80
88
|
<div id="browser-hint" class="browser-hint" hidden>Blank? Many sites refuse to show inside another page. Open it with โ instead.</div>
|
|
81
89
|
<div id="browser-cert" class="browser-start" hidden>
|
|
82
|
-
<p><b>One more step on this device.</b> Each browser tab goes through its own port, and this device hasn't accepted Agent Manager's certificate for this one yet.</p>
|
|
83
|
-
<p>Open it, accept the warning the same way you did for Agent Manager (Safari: Show Details โ visit this website), then come back here. You only do this once per site; it keeps its port.</p>
|
|
90
|
+
<p><b>One more step on this device.</b> Each browser tab goes through its own port, and this device hasn't accepted AI Agent Manager's certificate for this one yet.</p>
|
|
91
|
+
<p>Open it, accept the warning the same way you did for AI Agent Manager (Safari: Show Details โ visit this website), then come back here. You only do this once per site; it keeps its port.</p>
|
|
84
92
|
<div><a id="browser-cert-open" class="button primary small" target="_blank" rel="noopener">Open and accept</a> <button type="button" id="browser-cert-retry" class="small">Done, load it</button></div>
|
|
85
93
|
</div>
|
|
86
94
|
<div id="browser-start" class="browser-start" hidden>
|
|
87
|
-
<p>Open a page as this machine sees it: the agent's dev server, a site on its network or VPN, or any address. It works from your phone or laptop too: Agent Manager relays it over its own secure connection, for signed-in devices only.</p>
|
|
95
|
+
<p>Open a page as this machine sees it: the agent's dev server, a site on its network or VPN, or any address. It works from your phone or laptop too: AI Agent Manager relays it over its own secure connection, for signed-in devices only.</p>
|
|
88
96
|
<div id="browser-suggest"></div>
|
|
89
97
|
</div>
|
|
90
98
|
<div id="browser-frames" class="browser-frames"></div>
|
|
@@ -337,6 +345,17 @@
|
|
|
337
345
|
</div>
|
|
338
346
|
</dialog>
|
|
339
347
|
|
|
348
|
+
<dialog id="about-dialog" class="about-dialog">
|
|
349
|
+
<h2>AI Agent Manager</h2>
|
|
350
|
+
<p class="hint">Run and track background coding agents, one per intent.</p>
|
|
351
|
+
<dl id="about-list" class="about-list"></dl>
|
|
352
|
+
<p id="about-update" class="hint" hidden></p>
|
|
353
|
+
<div class="dialog-actions">
|
|
354
|
+
<button type="button" id="about-check" class="ghost" title="Asks the npm registry for the newest version">Check for updates</button>
|
|
355
|
+
<button type="button" id="about-close" class="primary">Close</button>
|
|
356
|
+
</div>
|
|
357
|
+
</dialog>
|
|
358
|
+
|
|
340
359
|
<script type="module" src="/app.js"></script>
|
|
341
360
|
</body>
|
|
342
361
|
</html>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "AI Agent Manager",
|
|
3
|
+
"short_name": "Agent Manager",
|
|
4
|
+
"description": "Run many coding agents in parallel without babysitting terminals.",
|
|
5
|
+
"id": "/",
|
|
6
|
+
"start_url": "/",
|
|
7
|
+
"scope": "/",
|
|
8
|
+
"display": "standalone",
|
|
9
|
+
"background_color": "#0d0f14",
|
|
10
|
+
"theme_color": "#151821",
|
|
11
|
+
"icons": [
|
|
12
|
+
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
|
|
13
|
+
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
|
|
14
|
+
{ "src": "/icons/icon.svg", "sizes": "any", "type": "image/svg+xml" }
|
|
15
|
+
]
|
|
16
|
+
}
|
package/public/share.js
CHANGED
|
@@ -9,7 +9,7 @@ $('#print').onclick = () => window.print();
|
|
|
9
9
|
|
|
10
10
|
async function main() {
|
|
11
11
|
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}`, { credentials: 'same-origin' });
|
|
12
|
-
if (!res.ok) throw new Error(res.status === 401 ? 'Sign in to Agent Manager first, then reopen this page.' : `HTTP ${res.status}`);
|
|
12
|
+
if (!res.ok) throw new Error(res.status === 401 ? 'Sign in to AI Agent Manager first, then reopen this page.' : `HTTP ${res.status}`);
|
|
13
13
|
const a = await res.json();
|
|
14
14
|
const d = a.docs?.[kind];
|
|
15
15
|
if (!d?.markdown) throw new Error('This document has not been written yet.');
|
package/public/style.css
CHANGED
|
@@ -56,6 +56,15 @@ a:hover { text-decoration: underline; }
|
|
|
56
56
|
.brand h1 { font-size: 16px; margin: 0; font-weight: 600; }
|
|
57
57
|
.logo { color: var(--accent); }
|
|
58
58
|
.conn { font-size: 12px; color: var(--muted); }
|
|
59
|
+
.about-btn { font-size: 12px; padding: 2px 8px; color: var(--muted); background: transparent; border: 1px solid var(--border); border-radius: 999px; font-variant-numeric: tabular-nums; }
|
|
60
|
+
.about-btn:hover, .about-btn:focus-visible { color: var(--text); border-color: var(--accent); }
|
|
61
|
+
.about-dialog { width: min(560px, calc(100vw - 32px)); padding: 20px; }
|
|
62
|
+
.about-dialog h2 { margin: 0 0 2px; font-size: 17px; }
|
|
63
|
+
.about-dialog .hint { margin: 0; }
|
|
64
|
+
.about-list { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 6px 16px; margin: 14px 0; font-size: 13px; }
|
|
65
|
+
.about-list dt { color: var(--muted); }
|
|
66
|
+
.about-list dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
|
|
67
|
+
.about-list .sep { grid-column: 1 / -1; border-top: 1px solid var(--border); margin: 4px 0; }
|
|
59
68
|
.conn.ok::before { content: 'โ '; color: var(--green); }
|
|
60
69
|
.conn.bad::before { content: 'โ '; color: var(--red); }
|
|
61
70
|
.top-actions { display: flex; gap: 8px; }
|
|
@@ -79,7 +88,27 @@ input, select, textarea {
|
|
|
79
88
|
textarea { resize: vertical; font-family: var(--mono); font-size: 13px; }
|
|
80
89
|
|
|
81
90
|
/* ---------- layout */
|
|
82
|
-
.layout { flex: 1; display: grid; grid-template-columns: 340px 1fr; min-height: 0; }
|
|
91
|
+
.layout { flex: 1; display: grid; grid-template-columns: var(--side-w, 340px) 0 1fr; min-height: 0; }
|
|
92
|
+
|
|
93
|
+
/* Drag handles: the list's width, and the brief above the terminal. */
|
|
94
|
+
.splitter { position: relative; z-index: 3; }
|
|
95
|
+
.splitter::after { content: ''; position: absolute; }
|
|
96
|
+
.split-side::after { top: 0; bottom: 0; left: -4px; width: 8px; cursor: col-resize; }
|
|
97
|
+
.split-term { display: none; flex: none; height: 0; }
|
|
98
|
+
.detail.with-term > #detail-body:not([hidden]) + .split-term { display: block; }
|
|
99
|
+
.split-term::after { left: 0; right: 0; top: -4px; height: 8px; cursor: row-resize; }
|
|
100
|
+
.splitter:hover::after, .splitter.dragging::after { background: var(--accent); opacity: .5; }
|
|
101
|
+
body.resizing { user-select: none; }
|
|
102
|
+
body.resizing iframe, body.resizing .terminal { pointer-events: none; }
|
|
103
|
+
body.resizing.cols { cursor: col-resize; }
|
|
104
|
+
body.resizing.rows { cursor: row-resize; }
|
|
105
|
+
|
|
106
|
+
/* Expanded: the terminal, shell or browser fills the window. */
|
|
107
|
+
body.term-max .topbar, body.term-max .sidebar, body.term-max .split-side,
|
|
108
|
+
body.term-max #detail-body, body.term-max .split-term, body.term-max #placeholder { display: none !important; }
|
|
109
|
+
body.term-max .layout { grid-template-columns: 1fr; }
|
|
110
|
+
.term-wrap:fullscreen { width: 100vw; height: 100vh; }
|
|
111
|
+
.term-bar button.on { background: #262c38; border-color: #58a6ff; }
|
|
83
112
|
.sidebar { border-right: 1px solid var(--border); overflow-y: auto; background: var(--panel); }
|
|
84
113
|
.side-tabs { display: flex; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--panel); z-index: 2; }
|
|
85
114
|
.side-tab { flex: 1; border: 0; border-radius: 0; background: transparent; padding: 9px; font-weight: 600; color: var(--muted); border-bottom: 2px solid transparent; }
|
|
@@ -163,7 +192,8 @@ textarea { resize: vertical; font-family: var(--mono); font-size: 13px; }
|
|
|
163
192
|
.detail { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
|
|
164
193
|
.placeholder { margin: auto; color: var(--muted); padding: 24px; text-align: center; }
|
|
165
194
|
#detail-body { padding: 16px 20px; overflow-y: auto; flex: 0 1 auto; max-height: 100%; }
|
|
166
|
-
.detail.with-term #detail-body { max-height: 42
|
|
195
|
+
.detail.with-term #detail-body { max-height: var(--brief-h, 42%); border-bottom: 1px solid var(--border); }
|
|
196
|
+
.detail.with-term.brief-sized #detail-body { height: var(--brief-h); flex: none; }
|
|
167
197
|
.d-head { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
|
|
168
198
|
.d-title { flex: 1 1 420px; min-width: 0; }
|
|
169
199
|
.d-title h2 { margin: 0 0 4px; font-size: 18px; display: flex; gap: 6px 10px; align-items: center; flex-wrap: wrap; }
|
|
@@ -390,6 +420,7 @@ dialog::backdrop { background: rgba(0, 0, 0, .45); }
|
|
|
390
420
|
.term-bar { flex-wrap: wrap; row-gap: 6px; }
|
|
391
421
|
.term-bar > .term-tabs { flex: 1 0 100%; }
|
|
392
422
|
.layout { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
|
|
423
|
+
.splitter, .desk-only { display: none !important; }
|
|
393
424
|
.sidebar { max-height: 38vh; border-right: 0; border-bottom: 1px solid var(--border); }
|
|
394
425
|
.top-actions input { width: 130px; }
|
|
395
426
|
.row { grid-template-columns: 1fr; }
|
|
@@ -539,3 +570,6 @@ pre.diff .meta { color: var(--muted); font-weight: 600; }
|
|
|
539
570
|
#import-dialog .import-foot label { display: flex; flex-direction: row; align-items: center; gap: 6px; }
|
|
540
571
|
#import-dialog .import-foot input { width: auto; }
|
|
541
572
|
.judge-line { margin: 14px 12px; line-height: 1.5; }
|
|
573
|
+
|
|
574
|
+
.agent-list li.agent.idle { opacity: .62; }
|
|
575
|
+
.agent-list li.agent.idle:hover, .agent-list li.agent.idle.selected { opacity: 1; }
|
package/src/about.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// What the About box and "agentmgr about" show: the release, who makes it, where to get help,
|
|
2
|
+
// and how this daemon runs. Nothing here leaves the machine; the update check is a separate,
|
|
3
|
+
// user-triggered request to the npm registry.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
10
|
+
const read = (f) => {
|
|
11
|
+
try {
|
|
12
|
+
return fs.readFileSync(path.join(root, f), 'utf8');
|
|
13
|
+
} catch {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const pkg = JSON.parse(read('package.json'));
|
|
18
|
+
const startedAt = new Date().toISOString();
|
|
19
|
+
|
|
20
|
+
/** The date in a changelog heading like "## 1.0.0 (2026-10-25)", or null. */
|
|
21
|
+
export function releaseDate(changelog, version) {
|
|
22
|
+
const esc = version.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
23
|
+
return changelog.match(new RegExp(`^##\\s+v?${esc}\\s+\\((\\d{4}-\\d{2}-\\d{2})\\)`, 'm'))?.[1] ?? null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const authorName = (a) => (typeof a === 'string' ? a.replace(/\s*[<(].*$/, '') : a?.name) || null;
|
|
27
|
+
const repoUrl = (r) => String((typeof r === 'string' ? r : r?.url) ?? '').replace(/^git\+/, '').replace(/\.git$/, '') || null;
|
|
28
|
+
|
|
29
|
+
export function about({ port, config = {} } = {}) {
|
|
30
|
+
return {
|
|
31
|
+
product: 'AI Agent Manager',
|
|
32
|
+
package: pkg.name,
|
|
33
|
+
command: Object.keys(pkg.bin ?? {})[0] ?? null,
|
|
34
|
+
version: pkg.version,
|
|
35
|
+
releaseDate: releaseDate(read('CHANGELOG.md'), pkg.version),
|
|
36
|
+
author: authorName(pkg.author),
|
|
37
|
+
license: pkg.license,
|
|
38
|
+
links: {
|
|
39
|
+
repository: repoUrl(pkg.repository),
|
|
40
|
+
issues: pkg.bugs?.url ?? null,
|
|
41
|
+
releases: repoUrl(pkg.repository) ? `${repoUrl(pkg.repository)}/releases` : null,
|
|
42
|
+
npm: `https://www.npmjs.com/package/${pkg.name}`,
|
|
43
|
+
},
|
|
44
|
+
daemon: {
|
|
45
|
+
pid: process.pid,
|
|
46
|
+
port: port ?? null,
|
|
47
|
+
home: config.home ?? null,
|
|
48
|
+
startedAt,
|
|
49
|
+
node: process.version,
|
|
50
|
+
platform: `${os.type()} ${os.release()} (${process.arch})`,
|
|
51
|
+
claudeBin: config.claudeBin ?? null,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The newest version on npm. Only called when someone clicks "Check for updates". */
|
|
57
|
+
export async function latestRelease({ fetchImpl = fetch, timeoutMs = 5000 } = {}) {
|
|
58
|
+
const res = await fetchImpl(`https://registry.npmjs.org/${encodeURIComponent(pkg.name)}/latest`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
59
|
+
if (!res.ok) throw Object.assign(new Error(`npm answered ${res.status}`), { status: 502 });
|
|
60
|
+
const { version } = await res.json();
|
|
61
|
+
return { current: pkg.version, latest: version, newer: newerThan(version, pkg.version) };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** True when semver `a` is newer than `b` (pre-releases count as older than the release). */
|
|
65
|
+
export function newerThan(a, b) {
|
|
66
|
+
const parse = (v) => String(v).split('-')[0].split('.').map(Number);
|
|
67
|
+
const [x, y] = [parse(a), parse(b)];
|
|
68
|
+
for (let i = 0; i < 3; i++) if ((x[i] ?? 0) !== (y[i] ?? 0)) return (x[i] ?? 0) > (y[i] ?? 0);
|
|
69
|
+
return String(b).includes('-') && !String(a).includes('-');
|
|
70
|
+
}
|
package/src/brief.js
CHANGED
|
@@ -15,7 +15,7 @@ export function briefPath(agentId) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export const briefPrompt = (file) =>
|
|
18
|
-
`Keep a short, living brief of this task in ${file} (Markdown). Agent Manager shows it to the user ` +
|
|
18
|
+
`Keep a short, living brief of this task in ${file} (Markdown). AI Agent Manager shows it to the user ` +
|
|
19
19
|
'next to your terminal. Start it with one status line, then use exactly these four sections:\n' +
|
|
20
20
|
'Status: planning | working | blocked | done\n' +
|
|
21
21
|
'Set "done" only when the task is complete and its checks pass; set "blocked" when you need the user.\n' +
|
package/src/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Command, Option } from 'commander';
|
|
|
4
4
|
import WebSocket from 'ws';
|
|
5
5
|
import { HOST, PORT } from './config.js';
|
|
6
6
|
import { api, BASE, ensureDaemon, isRunning } from './client.js';
|
|
7
|
+
import { about, latestRelease } from './about.js';
|
|
7
8
|
|
|
8
9
|
const tty = process.stdout.isTTY;
|
|
9
10
|
const c = (code) => (s) => (tty ? `\x1b[${code}m${s}\x1b[0m` : String(s));
|
|
@@ -855,7 +856,47 @@ export async function main(argv) {
|
|
|
855
856
|
console.log(j.enabled ? `AI judge ${green('on')} ${j.calls} of ${j.cap} calls today (on your own engine plan)` : `AI judge ${dim('off')} (fixed rules only; turn on: agentmgr judge on)`);
|
|
856
857
|
});
|
|
857
858
|
|
|
858
|
-
|
|
859
|
+
program
|
|
860
|
+
.command('about')
|
|
861
|
+
.description('Version, release date, author, license and links; the running daemon, if any')
|
|
862
|
+
.option('--check', 'also ask the npm registry for the newest version')
|
|
863
|
+
.action(async (opts) => {
|
|
864
|
+
// An older daemon has no /about; this install's own details still apply.
|
|
865
|
+
const a = (await isRunning()) ? await api('GET', '/about').catch(() => about()) : about();
|
|
866
|
+
const row = (k, v) => console.log(`${dim(k.padEnd(13))} ${v}`);
|
|
867
|
+
console.log(`${a.product} ${green(a.version)}${a.releaseDate ? dim(` released ${a.releaseDate}`) : ''}`);
|
|
868
|
+
row('Author', a.author ?? 'โ');
|
|
869
|
+
row('License', a.license ?? 'โ');
|
|
870
|
+
row('Package', `${a.package} (npm install -g ${a.package})`);
|
|
871
|
+
row('Code', a.links.repository ?? 'โ');
|
|
872
|
+
row('Issues', a.links.issues ?? 'โ');
|
|
873
|
+
if (a.daemon.port) row('Daemon', `pid ${a.daemon.pid}, port ${a.daemon.port}, since ${new Date(a.daemon.startedAt).toLocaleString()}, data in ${a.daemon.home}`);
|
|
874
|
+
else row('Daemon', dim('not running (start: agentmgr ui)'));
|
|
875
|
+
row('Node.js', `${a.daemon.node} on ${a.daemon.platform}`);
|
|
876
|
+
if (opts.check) {
|
|
877
|
+
const r = await latestRelease();
|
|
878
|
+
row('Latest', r.newer ? yellow(`${r.latest} is available: npm install -g ${a.package}@latest`) : `${r.latest} (up to date)`);
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
program
|
|
883
|
+
.command('doctor')
|
|
884
|
+
.description('Check what AI Agent Manager needs on this machine (Node.js, Claude Code, git, gh, the daemon) and how to fix it')
|
|
885
|
+
.action(async () => {
|
|
886
|
+
const { doctor } = await import('./doctor.js');
|
|
887
|
+
const checks = await doctor();
|
|
888
|
+
const mark = { ok: green('โ'), warn: yellow('!'), fail: red('โ'), info: dim('ยท') };
|
|
889
|
+
const width = Math.max(...checks.map((c) => c.name.length));
|
|
890
|
+
for (const c of checks) {
|
|
891
|
+
console.log(`${mark[c.status]} ${c.name.padEnd(width)} ${c.status === 'fail' ? red(c.detail) : c.detail}`);
|
|
892
|
+
if (c.fix) console.log(` ${' '.repeat(width)} ${dim('โ')} ${c.fix}`);
|
|
893
|
+
}
|
|
894
|
+
const failed = checks.filter((c) => c.status === 'fail').length;
|
|
895
|
+
console.log(failed ? red(`\n${failed} problem${failed === 1 ? '' : 's'} to fix`) : green('\nReady: start with `agentmgr ui`'));
|
|
896
|
+
if (failed) process.exitCode = 1;
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
const remoteCmd = program.command('remote').description('Open AI Agent Manager from a phone or another computer on the same network');
|
|
859
900
|
const printRemote = async (st, { qr = true } = {}) => {
|
|
860
901
|
if (!st.enabled || !st.running) return console.log(`remote access ${dim('off')} (turn on: agentmgr remote on)`);
|
|
861
902
|
console.log(`remote access ${green('on')} HTTPS port ${st.port}`);
|
package/src/conflicts.js
CHANGED
|
@@ -95,11 +95,11 @@ export function conflictMessage(self, group) {
|
|
|
95
95
|
const what = files ? `is changing the same files as you in this repository: ${files}.` : `is working on related code in this repository.`;
|
|
96
96
|
const order =
|
|
97
97
|
group.mergeFirst === self.id
|
|
98
|
-
? `Agent Manager suggests your change lands first; ${group.agent.name} will rebase onto it.`
|
|
99
|
-
: `Agent Manager suggests ${group.agent.name}'s change lands first: when it does, rebase onto it and resolve the overlap.`;
|
|
98
|
+
? `AI Agent Manager suggests your change lands first; ${group.agent.name} will rebase onto it.`
|
|
99
|
+
: `AI Agent Manager suggests ${group.agent.name}'s change lands first: when it does, rebase onto it and resolve the overlap.`;
|
|
100
100
|
const ai = group.ai?.reason ? ` Why this matters: ${group.ai.reason}${group.ai.advice ? ` ${group.ai.advice}` : ''}` : '';
|
|
101
101
|
return (
|
|
102
|
-
`Heads-up from Agent Manager: another agent, ${group.agent.name}, ${what} ${order}${ai} ` +
|
|
102
|
+
`Heads-up from AI Agent Manager: another agent, ${group.agent.name}, ${what} ${order}${ai} ` +
|
|
103
103
|
`Keep your edits in those files focused, avoid unrelated reformatting there, and mention the overlap in your PR description.`
|
|
104
104
|
);
|
|
105
105
|
}
|