@zgeoff/atc 0.1.8 → 0.1.10
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 +108 -57
- package/package.json +1 -1
- package/src/agent-adapter.ts +44 -0
- package/src/boot-daemon.ts +7 -1
- package/src/build-cli-command.ts +16 -0
- package/src/claude-adapter.ts +26 -18
- package/src/cli.ts +21 -2
- package/src/collect-clean-env.ts +30 -5
- package/src/config.ts +11 -1
- package/src/daemon-connection.ts +25 -7
- package/src/daemon.ts +61 -59
- package/src/format-overlay-agent-mark.ts +9 -0
- package/src/grok-adapter.ts +274 -0
- package/src/hook-report.ts +8 -4
- package/src/index.ts +121 -67
- package/src/mcp-server.ts +16 -4
- package/src/normalize-hook-event.ts +11 -0
- package/src/pick-tab-target.ts +3 -1
- package/src/print-grok-hook-file.ts +27 -0
- package/src/protocol.ts +1 -1
- package/src/resolve-repo-root.ts +51 -0
- package/src/sessions.ts +197 -75
- package/src/state-store.ts +91 -30
- package/src/ui.ts +41 -20
package/src/ui.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { AgentKind } from './agent-adapter';
|
|
1
2
|
import { formatDir } from './dirs';
|
|
3
|
+
import { formatOverlayAgentMark } from './format-overlay-agent-mark';
|
|
2
4
|
import { RESET_INPUT_MODES } from './reset-input-modes';
|
|
5
|
+
import { PINNED_GROUP_KEY } from './sessions';
|
|
3
6
|
import type { SessionState } from './sessions';
|
|
4
7
|
|
|
5
8
|
// The slice of a session the drawing layer needs; satisfied by both the
|
|
@@ -158,9 +161,11 @@ function dimRow(width: number, text: string): Row {
|
|
|
158
161
|
// Selection-dependent hints need the liveness facts of each row.
|
|
159
162
|
export interface OverlaySessionView extends SessionView {
|
|
160
163
|
readonly alive: boolean;
|
|
161
|
-
readonly kind: 'pty' | '
|
|
164
|
+
readonly kind: 'pty' | 'headless';
|
|
162
165
|
readonly resumable: boolean;
|
|
163
|
-
readonly
|
|
166
|
+
readonly agent: AgentKind;
|
|
167
|
+
readonly pinned: boolean;
|
|
168
|
+
readonly repoRoot: string;
|
|
164
169
|
}
|
|
165
170
|
|
|
166
171
|
export interface OverlayView {
|
|
@@ -169,6 +174,7 @@ export interface OverlayView {
|
|
|
169
174
|
confirmKill: boolean;
|
|
170
175
|
filter: string | null;
|
|
171
176
|
stale: boolean;
|
|
177
|
+
grouped: boolean;
|
|
172
178
|
}
|
|
173
179
|
|
|
174
180
|
export function drawOverlay(view: OverlayView) {
|
|
@@ -181,32 +187,36 @@ export function drawOverlay(view: OverlayView) {
|
|
|
181
187
|
rowsList.push(dimRow(width, empty));
|
|
182
188
|
}
|
|
183
189
|
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
// directory. The rows keep the sorted order
|
|
187
|
-
const grouped = new Set(view.sessions.map((s) => s.group ?? s.cwd)).size > 1;
|
|
190
|
+
// The grouped view clusters sessions under dim repository headers, with
|
|
191
|
+
// pinned sessions leading in their own cluster; the flat view shows a
|
|
192
|
+
// directory column instead. The rows keep the sorted order either way.
|
|
188
193
|
let lastKey: string | null = null;
|
|
189
194
|
|
|
190
195
|
for (const [i, s] of view.sessions.entries()) {
|
|
191
|
-
const key = s.
|
|
196
|
+
const key = s.pinned ? PINNED_GROUP_KEY : s.repoRoot;
|
|
192
197
|
|
|
193
|
-
if (grouped && key !== lastKey) {
|
|
198
|
+
if (view.grouped && key !== lastKey) {
|
|
194
199
|
lastKey = key;
|
|
195
200
|
|
|
196
|
-
rowsList.push(dimRow(width, `▸ ${s.
|
|
201
|
+
rowsList.push(dimRow(width, `▸ ${s.pinned ? 'pinned' : formatDir(s.repoRoot)}`));
|
|
197
202
|
}
|
|
198
203
|
|
|
199
204
|
const sel = i === view.selected;
|
|
200
205
|
const name = truncate(s.name, 16).padEnd(16);
|
|
201
206
|
const state = STATE_LABEL[s.state].padEnd(9);
|
|
202
|
-
const dir = grouped ? '' : ` ${truncate(formatDir(s.cwd), 18).padEnd(18)}`;
|
|
203
|
-
const msgWidth = Math.max(4, width - 4 -
|
|
207
|
+
const dir = view.grouped ? '' : ` ${truncate(formatDir(s.cwd), 18).padEnd(18)}`;
|
|
208
|
+
const msgWidth = Math.max(4, width - 4 - 4 - 17 - 10 - (view.grouped ? 0 : 19));
|
|
204
209
|
const msg = truncate(s.lastMsg, msgWidth).padEnd(msgWidth);
|
|
205
210
|
const unread = s.unread ? `${ESC}[1;33m!${ESC}[0m` : ' ';
|
|
211
|
+
const pin = s.pinned ? `${ESC}[93m⋆${ESC}[0m` : ' ';
|
|
212
|
+
const mark = formatOverlayAgentMark(s.agent);
|
|
213
|
+
const styledMark = mark === 'g' ? `${ESC}[90m${mark}${ESC}[0m` : mark;
|
|
206
214
|
const body = `${name} ${state}${dir} ${msg}`;
|
|
207
215
|
const styledBody = sel ? `${ESC}[7m${body}${ESC}[0m` : body;
|
|
208
216
|
|
|
209
|
-
rowsList.push(
|
|
217
|
+
rowsList.push(
|
|
218
|
+
boxRow(width, `${GLYPH[s.state]}${unread}${pin}${styledMark}${styledBody}`, 4 + body.length),
|
|
219
|
+
);
|
|
210
220
|
}
|
|
211
221
|
|
|
212
222
|
rowsList.push(boxDivider(width));
|
|
@@ -239,18 +249,18 @@ export function drawOverlay(view: OverlayView) {
|
|
|
239
249
|
drawBox(rowsList);
|
|
240
250
|
}
|
|
241
251
|
|
|
242
|
-
const GLOBAL_HINT = 'n new · ? keys';
|
|
252
|
+
const GLOBAL_HINT = 'g groups · n new · ? keys';
|
|
243
253
|
|
|
244
254
|
// Only the actions valid for the selected row appear; the full reference
|
|
245
|
-
// lives behind ?.
|
|
246
|
-
function buildOverlayHint(s: OverlaySessionView | undefined): string {
|
|
255
|
+
// lives behind ?. Grok has no headless handoff, so H is omitted there.
|
|
256
|
+
export function buildOverlayHint(s: OverlaySessionView | undefined): string {
|
|
247
257
|
if (s === undefined) {
|
|
248
258
|
return GLOBAL_HINT;
|
|
249
259
|
}
|
|
250
260
|
|
|
251
261
|
const actions: string[] = [];
|
|
252
262
|
|
|
253
|
-
if (s.kind === '
|
|
263
|
+
if (s.kind === 'headless' && s.alive) {
|
|
254
264
|
actions.push('P reattach', 'K kill');
|
|
255
265
|
} else if (s.alive) {
|
|
256
266
|
actions.push('⏎ attach');
|
|
@@ -259,7 +269,11 @@ function buildOverlayHint(s: OverlaySessionView | undefined): string {
|
|
|
259
269
|
actions.push('a ack');
|
|
260
270
|
}
|
|
261
271
|
|
|
262
|
-
|
|
272
|
+
if (s.agent !== 'grok') {
|
|
273
|
+
actions.push('H headless');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
actions.push('y yank', 'Y eject', 'K kill');
|
|
263
277
|
} else {
|
|
264
278
|
if (s.resumable) {
|
|
265
279
|
actions.push('P revive', 'y yank');
|
|
@@ -268,6 +282,10 @@ function buildOverlayHint(s: OverlaySessionView | undefined): string {
|
|
|
268
282
|
actions.push('K forget');
|
|
269
283
|
}
|
|
270
284
|
|
|
285
|
+
const pinAction = s.pinned ? 'p unpin' : 'p pin';
|
|
286
|
+
|
|
287
|
+
actions.push(pinAction);
|
|
288
|
+
|
|
271
289
|
return `${actions.join(' · ')} ▏ ${GLOBAL_HINT}`;
|
|
272
290
|
}
|
|
273
291
|
|
|
@@ -278,11 +296,14 @@ export function drawHelp() {
|
|
|
278
296
|
'⏎ attach the selected session',
|
|
279
297
|
'⇥ attach the most urgent needs-you, else latest done',
|
|
280
298
|
'a ack its notification without attaching',
|
|
281
|
-
'H eject to a headless run',
|
|
299
|
+
'H eject to a headless run (Claude only)',
|
|
282
300
|
'P revive a dead or headless session',
|
|
283
301
|
'y yank the resume command to the clipboard',
|
|
284
302
|
'Y yank the resume command, then kill here',
|
|
285
303
|
'K kill (K again on a dead session forgets it)',
|
|
304
|
+
'p pin or unpin — pinned sessions stay on top',
|
|
305
|
+
'g toggle grouping by repository',
|
|
306
|
+
' Grok rows show a dim g after the pin mark',
|
|
286
307
|
'n new session',
|
|
287
308
|
'r adopt an external session',
|
|
288
309
|
'/ filter · ↑↓/jk move · q quit',
|
|
@@ -350,10 +371,10 @@ export function drawHome(fleetCount = 0, leaderLabel = '^Space') {
|
|
|
350
371
|
out(ansi.clear + ansi.hideCursor);
|
|
351
372
|
|
|
352
373
|
const msgs = [
|
|
353
|
-
'atc — control tower for
|
|
374
|
+
'atc — control tower for Claude and Grok sessions',
|
|
354
375
|
'',
|
|
355
376
|
'n spawn a session',
|
|
356
|
-
'r adopt an existing session
|
|
377
|
+
'r adopt an existing session',
|
|
357
378
|
...(fleetCount > 0 ? [`R restore last fleet (${fleetCount} sessions)`] : []),
|
|
358
379
|
`${leaderLabel.padEnd(7)} session list`,
|
|
359
380
|
'q quit',
|