agenter-app 1.0.3 → 1.2.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/Agenter.vbs +1 -1
- package/README.md +166 -121
- package/cli.js +45 -2
- package/hook.js +7 -2
- package/lib/memory.js +101 -0
- package/lib/open-dashboard.js +4 -1
- package/lib/terminal.js +127 -0
- package/lib/token.js +47 -0
- package/lib/update.js +69 -0
- package/package.json +1 -1
- package/public/app.js +387 -22
- package/public/index.html +84 -20
- package/public/style.css +109 -10
- package/public/theme.js +9 -0
- package/scripts/install-shortcuts.ps1 +3 -2
- package/scripts/pick-folder.ps1 +85 -0
- package/scripts/tray.ps1 +40 -0
- package/server.js +247 -21
package/public/index.html
CHANGED
|
@@ -11,27 +11,20 @@
|
|
|
11
11
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
12
12
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap">
|
|
13
13
|
<link rel="stylesheet" href="/style.css">
|
|
14
|
-
<script>
|
|
15
|
-
// Applied before first paint so there's no flash of the wrong theme.
|
|
16
|
-
(() => {
|
|
17
|
-
const read = (k) => { try { return JSON.parse(localStorage.getItem('agenter.' + k)); } catch { return null; } };
|
|
18
|
-
const theme = read('theme') || 'system';
|
|
19
|
-
const dark = theme === 'dark' || (theme === 'system' && matchMedia('(prefers-color-scheme: dark)').matches);
|
|
20
|
-
document.documentElement.dataset.theme = dark ? 'dark' : 'light';
|
|
21
|
-
const accent = read('accent');
|
|
22
|
-
if (accent && accent !== 'sage') document.documentElement.dataset.accent = accent;
|
|
23
|
-
})();
|
|
24
|
-
</script>
|
|
14
|
+
<script src="/theme.js"></script>
|
|
25
15
|
</head>
|
|
26
16
|
<body>
|
|
27
17
|
<div class="app" id="app">
|
|
28
18
|
<aside class="sidebar" id="sidebar">
|
|
29
19
|
<div class="brand-row">
|
|
30
|
-
<div class="brand"><span class="logo"></span>Agenter</div>
|
|
20
|
+
<div class="brand"><span class="logo"></span><span class="brand-name">Agenter</span></div>
|
|
31
21
|
<span class="conn" id="conn" title="Connecting…"></span>
|
|
22
|
+
<button class="btn ghost icon nav-min-btn" id="navMinBtn" type="button" data-nav-min title="Collapse sidebar (B)" aria-label="Collapse sidebar" aria-expanded="true">
|
|
23
|
+
<svg class="ic" viewBox="0 0 24 24"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/></svg>
|
|
24
|
+
</button>
|
|
32
25
|
</div>
|
|
33
26
|
|
|
34
|
-
<button class="nav-item" id="navInbox" type="button">
|
|
27
|
+
<button class="nav-item" id="navInbox" type="button" title="Inbox">
|
|
35
28
|
<svg class="ic" viewBox="0 0 24 24"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></svg>
|
|
36
29
|
<span>Inbox</span>
|
|
37
30
|
<span class="count" id="inboxCount" hidden></span>
|
|
@@ -90,13 +83,24 @@
|
|
|
90
83
|
</button>
|
|
91
84
|
</div>
|
|
92
85
|
</div>
|
|
93
|
-
<div class="accent-row"
|
|
86
|
+
<div class="accent-row">
|
|
94
87
|
<span>Accent</span>
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
88
|
+
<div class="swatches" id="accentPicker" role="radiogroup" aria-label="Accent color">
|
|
89
|
+
<button type="button" role="radio" data-accent="sage" style="--sw:#b7d4a6" title="Sage"></button>
|
|
90
|
+
<button type="button" role="radio" data-accent="mint" style="--sw:#8fe0b4" title="Mint"></button>
|
|
91
|
+
<button type="button" role="radio" data-accent="teal" style="--sw:#73cfcb" title="Teal"></button>
|
|
92
|
+
<button type="button" role="radio" data-accent="sky" style="--sw:#8cc4ef" title="Sky"></button>
|
|
93
|
+
<button type="button" role="radio" data-accent="indigo" style="--sw:#a0aef5" title="Indigo"></button>
|
|
94
|
+
<button type="button" role="radio" data-accent="lavender" style="--sw:#b8acf2" title="Lavender"></button>
|
|
95
|
+
<button type="button" role="radio" data-accent="plum" style="--sw:#d49ae0" title="Plum"></button>
|
|
96
|
+
<button type="button" role="radio" data-accent="magenta" style="--sw:#f08fd0" title="Magenta"></button>
|
|
97
|
+
<button type="button" role="radio" data-accent="rose" style="--sw:#e58aa6" title="Rose"></button>
|
|
98
|
+
<button type="button" role="radio" data-accent="coral" style="--sw:#f29a8b" title="Coral"></button>
|
|
99
|
+
<button type="button" role="radio" data-accent="clay" style="--sw:#dc9474" title="Clay"></button>
|
|
100
|
+
<button type="button" role="radio" data-accent="amber" style="--sw:#eec15f" title="Amber"></button>
|
|
101
|
+
<button type="button" role="radio" data-accent="lemon" style="--sw:#e3d96b" title="Lemon"></button>
|
|
102
|
+
<button type="button" role="radio" data-accent="slate" style="--sw:#a9b8c6" title="Slate"></button>
|
|
103
|
+
</div>
|
|
100
104
|
</div>
|
|
101
105
|
</div>
|
|
102
106
|
</details>
|
|
@@ -113,6 +117,7 @@
|
|
|
113
117
|
<div class="scrim" id="scrim"></div>
|
|
114
118
|
|
|
115
119
|
<main class="main">
|
|
120
|
+
<div class="update-bar" id="updateBar" role="status" hidden></div>
|
|
116
121
|
<header class="main-head" id="mainHead"></header>
|
|
117
122
|
<div class="split" id="split">
|
|
118
123
|
<div class="chat-col">
|
|
@@ -139,11 +144,42 @@
|
|
|
139
144
|
<div class="pv-body" id="pvBody"></div>
|
|
140
145
|
</section>
|
|
141
146
|
</div>
|
|
147
|
+
<section class="term" id="term" aria-label="Terminal" hidden>
|
|
148
|
+
<div class="term-bar">
|
|
149
|
+
<span class="term-title">PowerShell</span>
|
|
150
|
+
<span class="term-cwd mono" id="termCwd"></span>
|
|
151
|
+
<button class="btn ghost small" type="button" id="termStop" data-term-stop title="Stop the running command. This also ends the shell, so variables start fresh" hidden>Stop</button>
|
|
152
|
+
<button class="btn ghost small" type="button" data-term-clear>Clear</button>
|
|
153
|
+
<button class="btn ghost icon" type="button" data-term-toggle title="Close terminal" aria-label="Close terminal">
|
|
154
|
+
<svg class="ic" viewBox="0 0 24 24"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
<pre class="term-out" id="termOut" tabindex="0"></pre>
|
|
158
|
+
<form class="term-line" id="termForm" autocomplete="off">
|
|
159
|
+
<span class="term-prompt mono" id="termPrompt">></span>
|
|
160
|
+
<input class="term-input mono" id="termInput" type="text" spellcheck="false" aria-label="Command" placeholder="Type a command">
|
|
161
|
+
</form>
|
|
162
|
+
</section>
|
|
142
163
|
</main>
|
|
143
164
|
</div>
|
|
144
165
|
|
|
145
166
|
<div class="toasts" id="toasts" aria-live="polite"></div>
|
|
146
167
|
|
|
168
|
+
<div id="moreMenu" class="more-menu" popover role="menu" aria-label="More actions">
|
|
169
|
+
<button type="button" role="menuitem" data-memory>
|
|
170
|
+
<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/></svg>
|
|
171
|
+
<span>Memory</span>
|
|
172
|
+
</button>
|
|
173
|
+
<button type="button" role="menuitem" data-carry>
|
|
174
|
+
<svg class="ic" viewBox="0 0 24 24"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
|
|
175
|
+
<span>Carry to a new chat</span>
|
|
176
|
+
</button>
|
|
177
|
+
<button type="button" role="menuitem" data-open-vscode>
|
|
178
|
+
<svg class="ic" viewBox="0 0 24 24"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
|
179
|
+
<span>Open in VS Code</span>
|
|
180
|
+
</button>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
147
183
|
<dialog id="newDialog" class="new-dialog">
|
|
148
184
|
<form id="newForm" autocomplete="off">
|
|
149
185
|
<div class="new-head">
|
|
@@ -155,8 +191,15 @@
|
|
|
155
191
|
</div>
|
|
156
192
|
<p class="dlg-sub" id="newSub"></p>
|
|
157
193
|
<label class="field-label" for="newCwd">Project folder</label>
|
|
158
|
-
<
|
|
194
|
+
<div class="folder-row">
|
|
195
|
+
<input class="field mono" id="newCwd" list="newCwdList" placeholder="C:\Users\you\Projects\my-app" spellcheck="false" required>
|
|
196
|
+
<button class="btn" type="button" id="newBrowse">Browse…</button>
|
|
197
|
+
</div>
|
|
159
198
|
<datalist id="newCwdList"></datalist>
|
|
199
|
+
<div class="new-carry" id="newCarry" hidden>
|
|
200
|
+
<span id="newCarryText"></span>
|
|
201
|
+
<button class="btn ghost small" type="button" data-carry-drop>Remove</button>
|
|
202
|
+
</div>
|
|
160
203
|
<label class="field-label" for="newMsg">First message</label>
|
|
161
204
|
<textarea class="field" id="newMsg" rows="4" placeholder="What should Claude do?" required></textarea>
|
|
162
205
|
<div class="dlg-error" id="newError" hidden></div>
|
|
@@ -167,6 +210,25 @@
|
|
|
167
210
|
</form>
|
|
168
211
|
</dialog>
|
|
169
212
|
|
|
213
|
+
<dialog id="memDialog" class="mem-dialog">
|
|
214
|
+
<div class="mem-head">
|
|
215
|
+
<h2>Memory</h2>
|
|
216
|
+
<span id="memSub"></span>
|
|
217
|
+
</div>
|
|
218
|
+
<div class="mem-body">
|
|
219
|
+
<nav class="mem-list" id="memList" aria-label="Memory files"></nav>
|
|
220
|
+
<div class="mem-edit">
|
|
221
|
+
<div class="mem-path mono" id="memPath"></div>
|
|
222
|
+
<textarea class="field mono" id="memText" spellcheck="false" aria-label="File contents"></textarea>
|
|
223
|
+
<div class="dlg-error" id="memError" hidden></div>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
<div class="dlg-actions">
|
|
227
|
+
<button class="btn ghost" type="button" data-mem-close>Close</button>
|
|
228
|
+
<button class="btn primary" type="button" id="memSave" disabled>Save</button>
|
|
229
|
+
</div>
|
|
230
|
+
</dialog>
|
|
231
|
+
|
|
170
232
|
<dialog id="helpDialog">
|
|
171
233
|
<h2>Keyboard shortcuts</h2>
|
|
172
234
|
<dl class="keys">
|
|
@@ -180,6 +242,8 @@
|
|
|
180
242
|
<dt><kbd>I</kbd></dt><dd>Open the inbox</dd>
|
|
181
243
|
<dt><kbd>/</kbd></dt><dd>Filter sessions</dd>
|
|
182
244
|
<dt><kbd>T</kbd></dt><dd>Switch theme (system → light → dark)</dd>
|
|
245
|
+
<dt><kbd>`</kbd></dt><dd>Show or hide the terminal</dd>
|
|
246
|
+
<dt><kbd>B</kbd></dt><dd>Collapse or expand the sidebar</dd>
|
|
183
247
|
<dt><kbd>Esc</kbd></dt><dd>Leave a text box / close this</dd>
|
|
184
248
|
</dl>
|
|
185
249
|
<form method="dialog"><button class="btn primary">Got it</button></form>
|
package/public/style.css
CHANGED
|
@@ -72,6 +72,15 @@
|
|
|
72
72
|
:root[data-accent="teal"] { --accent: #73cfcb; --accent-deep: #1d7a76; }
|
|
73
73
|
:root[data-accent="rose"] { --accent: #e58aa6; --accent-deep: #ad3a61; }
|
|
74
74
|
:root[data-accent="lavender"] { --accent: #b8acf2; --accent-deep: #6450c4; }
|
|
75
|
+
:root[data-accent="mint"] { --accent: #8fe0b4; --accent-deep: #1b7049; }
|
|
76
|
+
:root[data-accent="sky"] { --accent: #8cc4ef; --accent-deep: #27689f; }
|
|
77
|
+
:root[data-accent="indigo"] { --accent: #a0aef5; --accent-deep: #3f51bf; }
|
|
78
|
+
:root[data-accent="plum"] { --accent: #d49ae0; --accent-deep: #8b3a9b; }
|
|
79
|
+
:root[data-accent="magenta"] { --accent: #f08fd0; --accent-deep: #a82a83; }
|
|
80
|
+
:root[data-accent="coral"] { --accent: #f29a8b; --accent-deep: #b03f2b; }
|
|
81
|
+
:root[data-accent="amber"] { --accent: #eec15f; --accent-deep: #855b05; }
|
|
82
|
+
:root[data-accent="lemon"] { --accent: #e3d96b; --accent-deep: #6d640b; }
|
|
83
|
+
:root[data-accent="slate"] { --accent: #a9b8c6; --accent-deep: #4b5f72; }
|
|
75
84
|
|
|
76
85
|
* { box-sizing: border-box; }
|
|
77
86
|
html, body { height: 100%; margin: 0; }
|
|
@@ -101,7 +110,9 @@ kbd {
|
|
|
101
110
|
width: 300px; flex: none; display: flex; flex-direction: column; gap: 6px;
|
|
102
111
|
background: var(--sidebar); border-right: 1px solid var(--border); padding: 14px 10px 10px; min-height: 0;
|
|
103
112
|
}
|
|
104
|
-
.brand-row { display: flex; align-items: center;
|
|
113
|
+
.brand-row { display: flex; align-items: center; gap: 8px; padding: 0 6px 8px; }
|
|
114
|
+
.brand-row .brand { margin-right: auto; }
|
|
115
|
+
.nav-min-btn { padding: 4px; color: var(--muted); }
|
|
105
116
|
.brand { display: flex; align-items: center; gap: 9px; font-weight: 700; font-size: 15px; letter-spacing: -.01em; }
|
|
106
117
|
.logo { width: 22px; height: 22px; border-radius: 7px; background: var(--accent); position: relative; }
|
|
107
118
|
.logo::after { content: ""; position: absolute; inset: 6px; background: var(--accent-text); transform: rotate(45deg); border-radius: 2px; }
|
|
@@ -115,6 +126,7 @@ kbd {
|
|
|
115
126
|
}
|
|
116
127
|
.nav-item:hover { background: var(--hover); }
|
|
117
128
|
.nav-item.active { background: var(--panel); box-shadow: var(--shadow-sm); }
|
|
129
|
+
.nav-item:disabled { opacity: .6; cursor: progress; }
|
|
118
130
|
.count { margin-left: auto; min-width: 20px; padding: 0 6px; border-radius: 99px; background: var(--warn); color: #fff; font-size: 11.5px; font-weight: 700; text-align: center; line-height: 19px; }
|
|
119
131
|
|
|
120
132
|
.search { position: relative; display: flex; align-items: center; margin: 4px 0 2px; }
|
|
@@ -236,7 +248,8 @@ kbd {
|
|
|
236
248
|
.segmented button:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
|
|
237
249
|
.segmented .ic { width: 14px; height: 14px; }
|
|
238
250
|
.accent-row { display: flex; align-items: center; gap: 7px; font-size: 13px; color: var(--text-2); }
|
|
239
|
-
.accent-row span { margin-right: auto; }
|
|
251
|
+
.accent-row > span { margin-right: auto; }
|
|
252
|
+
.swatches { display: grid; grid-template-columns: repeat(7, 16px); gap: 7px; }
|
|
240
253
|
.accent-row button {
|
|
241
254
|
width: 16px; height: 16px; border-radius: 4px; border: 0; padding: 0; cursor: pointer;
|
|
242
255
|
background: var(--sw); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .15);
|
|
@@ -276,16 +289,17 @@ kbd {
|
|
|
276
289
|
.btn.primary kbd { background: transparent; color: inherit; border-color: currentColor; opacity: .7; }
|
|
277
290
|
|
|
278
291
|
/* ---------- main ---------- */
|
|
279
|
-
|
|
292
|
+
/* Measured as a container: what matters is the room left beside the sidebar, not the window. */
|
|
293
|
+
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; container: main / inline-size; }
|
|
280
294
|
.main-head {
|
|
281
|
-
display: flex; align-items: center; gap: 12px; padding: 12px 24px; min-height: 64px;
|
|
295
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding: 12px 24px; min-height: 64px;
|
|
282
296
|
border-bottom: 1px solid var(--border); background: var(--panel);
|
|
283
297
|
}
|
|
284
|
-
.head-text { min-width: 0; flex: 1; }
|
|
298
|
+
.head-text { min-width: 0; flex: 1 1 180px; }
|
|
285
299
|
.head-title { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 16px; letter-spacing: -.01em; }
|
|
286
300
|
.head-title span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
287
301
|
.head-sub { font-size: 12.5px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
288
|
-
.head-actions { display: flex; align-items: center; gap: 8px;
|
|
302
|
+
.head-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; }
|
|
289
303
|
.menu-btn { display: none; }
|
|
290
304
|
.path-chip { cursor: pointer; border: 0; background: none; padding: 0; color: var(--muted); font-size: 12px; }
|
|
291
305
|
.path-chip:hover { color: var(--text); text-decoration: underline; }
|
|
@@ -318,6 +332,59 @@ dialog.new-dialog { max-width: 520px; }
|
|
|
318
332
|
.new-dialog .dlg-error { margin-top: 12px; font-size: 13px; color: var(--danger); }
|
|
319
333
|
.new-dialog .dlg-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; }
|
|
320
334
|
|
|
335
|
+
.new-dialog .folder-row { display: flex; gap: 8px; }
|
|
336
|
+
.new-dialog .folder-row .field { flex: 1; min-width: 0; }
|
|
337
|
+
.new-carry { display: flex; align-items: center; gap: 8px; margin-top: 10px; padding: 7px 10px; border-radius: 8px; background: var(--accent-soft); font-size: 12.5px; }
|
|
338
|
+
.new-carry[hidden] { display: none; }
|
|
339
|
+
.new-carry span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
340
|
+
|
|
341
|
+
/* ---------- update bar ---------- */
|
|
342
|
+
.update-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; padding: 8px 24px; background: var(--accent-soft); border-bottom: 1px solid var(--border); font-size: 13px; }
|
|
343
|
+
.update-bar[hidden] { display: none; }
|
|
344
|
+
.update-bar .update-text { flex: 1 1 240px; min-width: 0; }
|
|
345
|
+
.update-bar .update-text code { font-size: 12px; }
|
|
346
|
+
|
|
347
|
+
/* ---------- more menu ---------- */
|
|
348
|
+
.more-menu { position: fixed; inset: auto; margin: 0; min-width: 200px; padding: 4px; border: 1px solid var(--border); border-radius: 10px; background: var(--panel); color: var(--text); box-shadow: var(--shadow); }
|
|
349
|
+
.more-menu button { display: flex; align-items: center; gap: 10px; width: 100%; padding: 7px 10px; border: 0; border-radius: 7px; background: none; cursor: pointer; font-size: 13px; text-align: left; }
|
|
350
|
+
.more-menu button:hover, .more-menu button:focus-visible { background: var(--hover); outline: none; }
|
|
351
|
+
.more-menu .ic { color: var(--muted); }
|
|
352
|
+
|
|
353
|
+
/* ---------- terminal ---------- */
|
|
354
|
+
.term { flex: none; height: min(36vh, 360px); min-height: 160px; display: flex; flex-direction: column; border-top: 1px solid var(--border); background: var(--code-bg); }
|
|
355
|
+
.term[hidden] { display: none; }
|
|
356
|
+
.term-bar { display: flex; align-items: center; gap: 6px; padding: 4px 8px 4px 14px; border-bottom: 1px solid var(--border); background: var(--panel); }
|
|
357
|
+
.term-title { font-size: 12px; font-weight: 600; flex: none; }
|
|
358
|
+
.term-cwd { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: 12px; }
|
|
359
|
+
.term-bar .btn.icon { padding: 4px; }
|
|
360
|
+
.term-out { flex: 1; min-height: 0; overflow: auto; padding: 8px 14px; font-size: 12.5px; line-height: 1.45; outline: none; }
|
|
361
|
+
.term-line { display: flex; align-items: center; gap: 8px; padding: 6px 14px; border-top: 1px solid var(--border); }
|
|
362
|
+
.term-prompt { color: var(--accent-ink); flex: none; }
|
|
363
|
+
.term-input { flex: 1; min-width: 0; border: 0; outline: none; background: none; color: var(--text); padding: 0; }
|
|
364
|
+
.term-input::placeholder { color: var(--muted); }
|
|
365
|
+
|
|
366
|
+
/* ---------- memory ---------- */
|
|
367
|
+
dialog.mem-dialog { max-width: 860px; }
|
|
368
|
+
.mem-dialog[open] { display: flex; flex-direction: column; height: min(640px, calc(100vh - 48px)); }
|
|
369
|
+
.mem-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px; min-width: 0; }
|
|
370
|
+
.mem-head h2 { margin: 0; flex: none; }
|
|
371
|
+
.mem-head span { color: var(--muted); font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
372
|
+
.mem-body { flex: 1; min-height: 0; display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 14px; }
|
|
373
|
+
.mem-list { overflow-y: auto; }
|
|
374
|
+
.mem-group { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); padding: 10px 8px 4px; }
|
|
375
|
+
.mem-group:first-child { padding-top: 0; }
|
|
376
|
+
.mem-file { display: flex; align-items: center; gap: 6px; width: 100%; padding: 5px 8px; border: 0; border-radius: 7px; background: none; cursor: pointer; font-size: 13px; text-align: left; }
|
|
377
|
+
.mem-file span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
378
|
+
.mem-file:hover { background: var(--hover); }
|
|
379
|
+
.mem-file[aria-current="true"] { background: var(--accent-soft); }
|
|
380
|
+
.mem-file .new { margin-left: auto; flex: none; color: var(--muted); font-size: 11px; }
|
|
381
|
+
.mem-file .dirty { margin-left: auto; flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--warn); }
|
|
382
|
+
.mem-edit { display: flex; flex-direction: column; gap: 8px; min-height: 0; min-width: 0; }
|
|
383
|
+
.mem-path { font-size: 11.5px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
384
|
+
.mem-edit textarea { flex: 1; min-height: 0; resize: none; line-height: 1.5; }
|
|
385
|
+
.mem-dialog .dlg-error { font-size: 13px; color: var(--danger); }
|
|
386
|
+
.mem-dialog .dlg-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
|
|
387
|
+
|
|
321
388
|
/* ---------- steering ---------- */
|
|
322
389
|
.kind-steer .reply-row .hint { white-space: normal; }
|
|
323
390
|
.link { border: 0; background: none; padding: 0; font: inherit; color: var(--accent-ink); text-decoration: underline; cursor: pointer; }
|
|
@@ -363,6 +430,10 @@ dialog.new-dialog { max-width: 520px; }
|
|
|
363
430
|
.convo { display: flex; flex-direction: column; gap: 10px; }
|
|
364
431
|
.msg-user { align-self: flex-end; max-width: 78%; background: var(--accent-soft); border: 1px solid transparent; padding: 9px 14px; border-radius: 14px 14px 4px 14px; white-space: pre-wrap; word-break: break-word; }
|
|
365
432
|
.msg-user.from-dash { border-color: var(--accent-ink); }
|
|
433
|
+
.carried { margin-top: 6px; font-size: 12.5px; color: var(--text-2); white-space: normal; }
|
|
434
|
+
.carried > summary { cursor: pointer; }
|
|
435
|
+
.carried pre { margin-top: 6px; max-height: 280px; overflow: auto; font-size: 12px; }
|
|
436
|
+
.live-echo { display: flex; justify-content: flex-end; margin-top: 12px; }
|
|
366
437
|
.msg-claude { display: grid; grid-template-columns: 26px 1fr; gap: 10px; align-items: start; }
|
|
367
438
|
.claude-mark { width: 26px; height: 26px; border-radius: 8px; background: var(--panel); border: 1px solid var(--border); display: grid; place-items: center; color: var(--accent-ink); }
|
|
368
439
|
.claude-mark .ic { width: 14px; height: 14px; }
|
|
@@ -494,7 +565,7 @@ dialog.new-dialog { max-width: 520px; }
|
|
|
494
565
|
.section-title { display: flex; align-items: baseline; gap: 8px; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: 26px 0 10px; }
|
|
495
566
|
#inboxPending > .section-title { margin-top: 0; }
|
|
496
567
|
.all-clear { display: flex; align-items: center; gap: 14px; padding: 18px 20px; border-radius: var(--radius); background: var(--panel); border: 1px solid var(--border); }
|
|
497
|
-
.all-clear .big { width: 38px; height: 38px; border-radius: 50%; background: var(--
|
|
568
|
+
.all-clear .big { width: 38px; height: 38px; border-radius: 50%; background: var(--accent-soft); color: var(--accent-ink); display: grid; place-items: center; flex: none; }
|
|
498
569
|
.all-clear b { display: block; }
|
|
499
570
|
.all-clear span { color: var(--muted); font-size: 13px; }
|
|
500
571
|
.tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 10px; }
|
|
@@ -537,21 +608,49 @@ dialog form { text-align: right; }
|
|
|
537
608
|
*, *::before, *::after { animation: none !important; transition: none !important; }
|
|
538
609
|
}
|
|
539
610
|
|
|
611
|
+
/* ---------- collapsed sidebar: a rail of icons and sessions ---------- */
|
|
612
|
+
@media (min-width: 801px) {
|
|
613
|
+
.app.nav-min .sidebar { width: 60px; padding: 14px 8px 10px; align-items: stretch; }
|
|
614
|
+
.app.nav-min .brand-row { flex-direction: column; padding: 0 0 8px; }
|
|
615
|
+
.app.nav-min .brand { margin: 0; }
|
|
616
|
+
.app.nav-min .brand-name, .app.nav-min .search, .app.nav-min .usage, .app.nav-min .sidebar-foot, .app.nav-min .group-label { display: none; }
|
|
617
|
+
.app.nav-min .nav-item { justify-content: center; padding: 8px 0; position: relative; }
|
|
618
|
+
.app.nav-min .nav-item > span:not(.count) { display: none; }
|
|
619
|
+
.app.nav-min .nav-item .count { position: absolute; top: 0; right: 0; min-width: 16px; padding: 0 4px; line-height: 15px; font-size: 10px; }
|
|
620
|
+
.app.nav-min .session-list { margin: 0; padding: 0; }
|
|
621
|
+
.app.nav-min .s-item { grid-template-columns: 32px; justify-content: center; padding: 6px 0; }
|
|
622
|
+
.app.nav-min .s-main, .app.nav-min .s-when { display: none; }
|
|
623
|
+
}
|
|
624
|
+
|
|
540
625
|
/* ---------- small screens ---------- */
|
|
541
626
|
@media (max-width: 800px) {
|
|
542
627
|
.sidebar { position: fixed; inset: 0 auto 0 0; z-index: 40; transform: translateX(-100%); transition: transform .2s; box-shadow: var(--shadow); width: min(300px, 86vw); }
|
|
543
628
|
.app.nav-open .sidebar { transform: none; }
|
|
544
629
|
.app.nav-open .scrim { display: block; position: fixed; inset: 0; background: rgba(0, 0, 0, .35); z-index: 30; }
|
|
545
630
|
.menu-btn { display: inline-flex; }
|
|
631
|
+
.search kbd { display: none; }
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/* Going by the space beside the sidebar, so the same room lays out the same way whether
|
|
635
|
+
the sidebar is open, collapsed or a drawer. */
|
|
636
|
+
@container main (max-width: 900px) {
|
|
637
|
+
.head-actions .label { display: none; }
|
|
638
|
+
}
|
|
639
|
+
@container main (max-width: 760px) {
|
|
546
640
|
.main-head { padding: 10px 16px; }
|
|
641
|
+
.update-bar { padding: 8px 16px; }
|
|
547
642
|
.content { padding: 16px; }
|
|
548
643
|
.dock { padding: 0 16px 14px; }
|
|
549
|
-
.head-actions .label { display: none; }
|
|
550
644
|
.msg-user { max-width: 92%; }
|
|
551
|
-
.btn kbd, .reply-row .hint
|
|
645
|
+
.btn kbd, .reply-row .hint { display: none; }
|
|
552
646
|
.tools { margin-left: 8px; }
|
|
553
647
|
/* No room side by side: the app goes on top, the conversation below. */
|
|
554
648
|
.split { flex-direction: column; }
|
|
555
649
|
.splitter { display: none; }
|
|
556
|
-
.preview { order: -1; width: auto; max-width: none; min-width: 0; height: 48vh; border-bottom: 1px solid var(--border); }
|
|
650
|
+
.preview { order: -1; width: auto; max-width: none; min-width: 0; height: 48vh; flex: none; border-bottom: 1px solid var(--border); }
|
|
557
651
|
}
|
|
652
|
+
@media (max-width: 640px) {
|
|
653
|
+
.mem-body { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 2fr) minmax(0, 5fr); }
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.auth-lost { position: fixed; top: 12px; left: 50%; transform: translateX(-50%); z-index: 1000; max-width: calc(100vw - 32px); padding: 10px 14px; border-radius: 10px; background: #5c2a26; color: #fff; font-size: 13px; }
|
package/public/theme.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Applied before first paint so there's no flash of the wrong theme.
|
|
2
|
+
(() => {
|
|
3
|
+
const read = (k) => { try { return JSON.parse(localStorage.getItem('agenter.' + k)); } catch { return null; } };
|
|
4
|
+
const theme = read('theme') || 'system';
|
|
5
|
+
const dark = theme === 'dark' || (theme === 'system' && matchMedia('(prefers-color-scheme: dark)').matches);
|
|
6
|
+
document.documentElement.dataset.theme = dark ? 'dark' : 'light';
|
|
7
|
+
const accent = read('accent');
|
|
8
|
+
if (accent && accent !== 'sage') document.documentElement.dataset.accent = accent;
|
|
9
|
+
})();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Creates "Agenter" shortcuts on the Desktop and in the Start Menu (pin it to the taskbar from there).
|
|
2
|
-
# -Startup also starts the
|
|
2
|
+
# -Startup also starts Agenter in the system tray when you log in to Windows.
|
|
3
3
|
param([switch]$Startup, [switch]$Remove)
|
|
4
4
|
|
|
5
5
|
$root = Split-Path -Parent $PSScriptRoot
|
|
@@ -31,6 +31,7 @@ if ($Startup) {
|
|
|
31
31
|
$lnk.TargetPath = "$env:SystemRoot\System32\wscript.exe"
|
|
32
32
|
$lnk.Arguments = "`"$root\Agenter.vbs`" server"
|
|
33
33
|
$lnk.WorkingDirectory = $root
|
|
34
|
+
$lnk.IconLocation = "$root\public\agenter.ico,0"
|
|
34
35
|
$lnk.Save()
|
|
35
|
-
"Created $startupLink (
|
|
36
|
+
"Created $startupLink (Agenter starts in the tray at login)"
|
|
36
37
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# The Windows folder window behind New session. Prints the folder you chose, or nothing if
|
|
2
|
+
# you cancel.
|
|
3
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
4
|
+
|
|
5
|
+
# Windows won't let a background process take focus, so without this the window could open
|
|
6
|
+
# behind the dashboard and clicking New session would seem to do nothing. Joining the input
|
|
7
|
+
# of the window that has focus is what makes SetForegroundWindow allowed.
|
|
8
|
+
Add-Type @"
|
|
9
|
+
using System;
|
|
10
|
+
using System.Text;
|
|
11
|
+
using System.Runtime.InteropServices;
|
|
12
|
+
public static class PickFolderWindow {
|
|
13
|
+
[DllImport("user32.dll")] static extern IntPtr GetForegroundWindow();
|
|
14
|
+
[DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, IntPtr p);
|
|
15
|
+
[DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();
|
|
16
|
+
[DllImport("user32.dll")] static extern bool AttachThreadInput(uint a, uint b, bool attach);
|
|
17
|
+
[DllImport("user32.dll")] static extern bool SetForegroundWindow(IntPtr h);
|
|
18
|
+
[DllImport("user32.dll")] static extern bool BringWindowToTop(IntPtr h);
|
|
19
|
+
[DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h);
|
|
20
|
+
[DllImport("user32.dll", CharSet = CharSet.Unicode)] static extern int GetClassName(IntPtr h, StringBuilder name, int size);
|
|
21
|
+
delegate bool WindowVisitor(IntPtr h, IntPtr data);
|
|
22
|
+
[DllImport("user32.dll")] static extern bool EnumThreadWindows(uint thread, WindowVisitor visit, IntPtr data);
|
|
23
|
+
[DllImport("user32.dll")] static extern bool SetWindowPos(IntPtr h, IntPtr after, int x, int y, int cx, int cy, uint flags);
|
|
24
|
+
// Dialogs are the #32770 class. Runs on the dialog's thread, so its windows are ours.
|
|
25
|
+
static IntPtr FindDialog(uint thread) {
|
|
26
|
+
IntPtr found = IntPtr.Zero;
|
|
27
|
+
EnumThreadWindows(thread, (h, data) => {
|
|
28
|
+
StringBuilder name = new StringBuilder(16);
|
|
29
|
+
GetClassName(h, name, 16);
|
|
30
|
+
if (name.ToString() != "#32770" || !IsWindowVisible(h)) return true;
|
|
31
|
+
found = h;
|
|
32
|
+
return false;
|
|
33
|
+
}, IntPtr.Zero);
|
|
34
|
+
return found;
|
|
35
|
+
}
|
|
36
|
+
// True once the dialog really has focus.
|
|
37
|
+
public static bool BringForward() {
|
|
38
|
+
uint self = GetCurrentThreadId();
|
|
39
|
+
IntPtr dialog = FindDialog(self);
|
|
40
|
+
if (dialog == IntPtr.Zero) return false; // not up yet
|
|
41
|
+
if (GetForegroundWindow() == dialog) return true;
|
|
42
|
+
SetWindowPos(dialog, new IntPtr(-1), 0, 0, 0, 0, 0x0043); // topmost, no move or size, show
|
|
43
|
+
uint focused = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);
|
|
44
|
+
bool joined = focused != 0 && focused != self && AttachThreadInput(self, focused, true);
|
|
45
|
+
BringWindowToTop(dialog);
|
|
46
|
+
SetForegroundWindow(dialog);
|
|
47
|
+
if (joined) AttachThreadInput(self, focused, false);
|
|
48
|
+
return GetForegroundWindow() == dialog;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
"@
|
|
52
|
+
|
|
53
|
+
[Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
|
|
54
|
+
|
|
55
|
+
$owner = New-Object System.Windows.Forms.Form
|
|
56
|
+
$owner.TopMost = $true
|
|
57
|
+
# Windows can still refuse focus, and this keeps the window findable when it does.
|
|
58
|
+
$owner.ShowInTaskbar = $true
|
|
59
|
+
$owner.Text = 'Agenter: choose a folder'
|
|
60
|
+
$owner.FormBorderStyle = 'None'
|
|
61
|
+
$owner.Opacity = 0
|
|
62
|
+
$owner.StartPosition = 'Manual'
|
|
63
|
+
$owner.Location = New-Object System.Drawing.Point(-32000, -32000)
|
|
64
|
+
$owner.Show()
|
|
65
|
+
|
|
66
|
+
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
|
|
67
|
+
$dialog.Description = 'Choose the project folder for this session'
|
|
68
|
+
$dialog.ShowNewFolderButton = $true
|
|
69
|
+
if ($env:AGENTER_PICK_START -and (Test-Path -LiteralPath $env:AGENTER_PICK_START)) { $dialog.SelectedPath = $env:AGENTER_PICK_START }
|
|
70
|
+
|
|
71
|
+
# Runs inside the dialog's own message loop. Keeps trying for a few seconds, then lets you be.
|
|
72
|
+
$raise = New-Object System.Windows.Forms.Timer
|
|
73
|
+
$raise.Interval = 50
|
|
74
|
+
$script:tries = 0
|
|
75
|
+
$raise.add_Tick({
|
|
76
|
+
$script:tries++
|
|
77
|
+
if ([PickFolderWindow]::BringForward() -or $script:tries -ge 60) { $raise.Stop() }
|
|
78
|
+
})
|
|
79
|
+
$raise.Start()
|
|
80
|
+
|
|
81
|
+
if ($dialog.ShowDialog($owner) -eq [System.Windows.Forms.DialogResult]::OK) {
|
|
82
|
+
[Console]::Out.Write($dialog.SelectedPath)
|
|
83
|
+
}
|
|
84
|
+
$raise.Dispose()
|
|
85
|
+
$owner.Dispose()
|
package/scripts/tray.ps1
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# The Agenter icon in the Windows system tray. server.js starts this, and it lives exactly as
|
|
2
|
+
# long as the server does. Open shows the dashboard; Quit stops the server, and with it this.
|
|
3
|
+
param([int]$ServerPid, [string]$Node, [int]$Port)
|
|
4
|
+
|
|
5
|
+
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
|
|
6
|
+
|
|
7
|
+
$root = Split-Path -Parent $PSScriptRoot
|
|
8
|
+
|
|
9
|
+
$open = {
|
|
10
|
+
Start-Process -FilePath $Node -ArgumentList "`"$root\open.js`"" -WindowStyle Hidden
|
|
11
|
+
}
|
|
12
|
+
$quit = {
|
|
13
|
+
$token = (Get-Content (Join-Path $env:USERPROFILE '.agenter\token') -Raw).Trim()
|
|
14
|
+
Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:$Port/api/quit" -Headers @{ 'x-agenter-token' = $token } -ContentType 'application/json' -Body '{}' | Out-Null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
$menu = New-Object System.Windows.Forms.ContextMenuStrip
|
|
18
|
+
$menu.Items.Add('Open Agenter').add_Click($open)
|
|
19
|
+
$menu.Items.Add('Quit Agenter').add_Click($quit)
|
|
20
|
+
|
|
21
|
+
$tray = New-Object System.Windows.Forms.NotifyIcon
|
|
22
|
+
$tray.Icon = New-Object System.Drawing.Icon (Join-Path $root 'public\agenter.ico')
|
|
23
|
+
$tray.Text = 'Agenter'
|
|
24
|
+
$tray.ContextMenuStrip = $menu
|
|
25
|
+
$tray.add_MouseClick({ if ($_.Button -eq 'Left') { & $open } })
|
|
26
|
+
$tray.Visible = $true
|
|
27
|
+
|
|
28
|
+
# However the server ended, don't leave a dead icon behind.
|
|
29
|
+
$watch = New-Object System.Windows.Forms.Timer
|
|
30
|
+
$watch.Interval = 1000
|
|
31
|
+
$watch.add_Tick({
|
|
32
|
+
if (-not (Get-Process -Id $ServerPid -ErrorAction SilentlyContinue)) {
|
|
33
|
+
$tray.Visible = $false
|
|
34
|
+
[System.Windows.Forms.Application]::Exit()
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
$watch.Start()
|
|
38
|
+
|
|
39
|
+
[System.Windows.Forms.Application]::Run()
|
|
40
|
+
$tray.Dispose()
|