@phnx-labs/agents-cli 1.20.91 → 1.20.93
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 +276 -0
- package/README.md +1 -1
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +72 -6
- package/dist/commands/activity.js +198 -49
- package/dist/commands/beta.js +1 -0
- package/dist/commands/doctor.js +4 -2
- package/dist/commands/events.js +91 -1
- package/dist/commands/exec.d.ts +14 -0
- package/dist/commands/exec.js +144 -14
- package/dist/commands/projects.d.ts +22 -0
- package/dist/commands/projects.js +539 -0
- package/dist/commands/secrets.d.ts +17 -0
- package/dist/commands/secrets.js +198 -7
- package/dist/commands/send.d.ts +14 -12
- package/dist/commands/send.js +105 -35
- package/dist/commands/sessions-picker.d.ts +15 -0
- package/dist/commands/sessions-picker.js +37 -12
- package/dist/commands/sessions-resume.d.ts +2 -0
- package/dist/commands/sessions-resume.js +9 -1
- package/dist/commands/sessions.d.ts +10 -5
- package/dist/commands/sessions.js +65 -27
- package/dist/commands/sync.js +9 -3
- package/dist/commands/view.js +4 -0
- package/dist/index.js +18 -1
- package/dist/lib/activity.d.ts +77 -12
- package/dist/lib/activity.js +424 -74
- package/dist/lib/beta.d.ts +1 -1
- package/dist/lib/beta.js +1 -1
- package/dist/lib/channels/send.d.ts +83 -0
- package/dist/lib/channels/send.js +112 -0
- package/dist/lib/devices/registry.d.ts +14 -0
- package/dist/lib/devices/registry.js +37 -0
- package/dist/lib/events-ingest.d.ts +46 -0
- package/dist/lib/events-ingest.js +182 -0
- package/dist/lib/events.d.ts +15 -3
- package/dist/lib/events.js +55 -3
- package/dist/lib/feed-post.js +8 -2
- package/dist/lib/hosts/remote-cmd.js +4 -0
- package/dist/lib/linear-project-counts.d.ts +62 -0
- package/dist/lib/linear-project-counts.js +122 -0
- package/dist/lib/linear-projects.d.ts +50 -0
- package/dist/lib/linear-projects.js +114 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +14 -4
- package/dist/lib/menubar/install-menubar.js +20 -6
- package/dist/lib/menubar/notify-desktop.d.ts +17 -2
- package/dist/lib/menubar/notify-desktop.js +8 -2
- package/dist/lib/project-key.d.ts +44 -0
- package/dist/lib/project-key.js +79 -0
- package/dist/lib/project-probe.d.ts +75 -0
- package/dist/lib/project-probe.js +160 -0
- package/dist/lib/project-resources.d.ts +8 -0
- package/dist/lib/project-resources.js +31 -3
- package/dist/lib/project-root.js +16 -0
- package/dist/lib/project-status.d.ts +100 -0
- package/dist/lib/project-status.js +182 -0
- package/dist/lib/projects.d.ts +144 -0
- package/dist/lib/projects.js +313 -0
- package/dist/lib/remote-agents-json.d.ts +9 -0
- package/dist/lib/remote-agents-json.js +11 -5
- package/dist/lib/routine-notify.d.ts +11 -0
- package/dist/lib/routine-notify.js +22 -0
- package/dist/lib/run-notify.js +3 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/audit.d.ts +1 -1
- package/dist/lib/secrets/audit.js +53 -10
- package/dist/lib/secrets/list-filter.d.ts +20 -5
- package/dist/lib/secrets/list-filter.js +22 -6
- package/dist/lib/secrets/usage-db.d.ts +106 -0
- package/dist/lib/secrets/usage-db.js +236 -0
- package/dist/lib/session/bash-command.d.ts +53 -0
- package/dist/lib/session/bash-command.js +364 -0
- package/dist/lib/session/digest.d.ts +6 -0
- package/dist/lib/session/digest.js +19 -0
- package/dist/lib/session/relative-time.d.ts +23 -0
- package/dist/lib/session/relative-time.js +60 -8
- package/dist/lib/session/remote-active.d.ts +5 -1
- package/dist/lib/session/remote-active.js +4 -1
- package/dist/lib/session/remote-list.js +5 -2
- package/dist/lib/session/render.d.ts +2 -9
- package/dist/lib/session/render.js +25 -56
- package/dist/lib/sqlite.js +28 -1
- package/dist/lib/ssh-exec.d.ts +6 -0
- package/dist/lib/ssh-exec.js +10 -1
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/state.d.ts +14 -0
- package/dist/lib/state.js +19 -0
- package/dist/lib/terminal/backends/index.d.ts +10 -2
- package/dist/lib/terminal/backends/index.js +14 -2
- package/dist/lib/terminal/backends/terminal-app.d.ts +13 -0
- package/dist/lib/terminal/backends/terminal-app.js +73 -0
- package/dist/lib/terminal/index.d.ts +2 -1
- package/dist/lib/terminal/index.js +2 -1
- package/dist/lib/terminal/preferred.d.ts +89 -0
- package/dist/lib/terminal/preferred.js +87 -0
- package/dist/lib/terminal/run-surface.d.ts +82 -0
- package/dist/lib/terminal/run-surface.js +146 -0
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/types.d.ts +6 -5
- package/dist/lib/versions.d.ts +6 -0
- package/dist/lib/versions.js +6 -4
- package/package.json +2 -1
|
@@ -146,7 +146,7 @@ function displayAgent(agent) {
|
|
|
146
146
|
const DOT = chalk.gray(' · ');
|
|
147
147
|
function formatHeader(session, events) {
|
|
148
148
|
const model = extractModel(events) || session.model;
|
|
149
|
-
const {
|
|
149
|
+
const { createdAgo, lastActiveAgo, duration } = extractTiming(session, events);
|
|
150
150
|
const totalMessages = session.messageCount ?? countMessages(events);
|
|
151
151
|
const totalTokens = session.tokenCount;
|
|
152
152
|
// Line 1: Agent v version · shortId · model · account
|
|
@@ -158,7 +158,7 @@ function formatHeader(session, events) {
|
|
|
158
158
|
line1.push(chalk.bold.white(shortenModel(model)));
|
|
159
159
|
if (session.account)
|
|
160
160
|
line1.push(chalk.gray(session.account));
|
|
161
|
-
// Line 2: cwd · project · branch ·
|
|
161
|
+
// Line 2: cwd · project · branch · created X ago · last active Y ago · lasted Z
|
|
162
162
|
// Project is clickable: Linear issue URL is for tickets (line 4); for the
|
|
163
163
|
// project name prefer a GitHub repo URL derived from the checkout path.
|
|
164
164
|
const line2 = [];
|
|
@@ -172,8 +172,10 @@ function formatHeader(session, events) {
|
|
|
172
172
|
}
|
|
173
173
|
if (session.gitBranch)
|
|
174
174
|
line2.push(chalk.cyan(session.gitBranch));
|
|
175
|
-
if (
|
|
176
|
-
line2.push(chalk.gray('
|
|
175
|
+
if (createdAgo)
|
|
176
|
+
line2.push(chalk.gray('created ') + chalk.white(createdAgo + ' ago'));
|
|
177
|
+
if (lastActiveAgo)
|
|
178
|
+
line2.push(chalk.gray('last active ') + chalk.white(lastActiveAgo + ' ago'));
|
|
177
179
|
if (duration)
|
|
178
180
|
line2.push(chalk.gray('lasted ') + chalk.white(duration));
|
|
179
181
|
// Line 3: N msgs · T tokens · [label ·] uuid
|
|
@@ -292,16 +294,39 @@ function extractModel(events) {
|
|
|
292
294
|
}
|
|
293
295
|
return undefined;
|
|
294
296
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
297
|
+
/** Shortest span worth reporting as a separate last-activity field — matches the
|
|
298
|
+
* listing's rule, so the pane and the row agree on what counts as a one-shot. */
|
|
299
|
+
const TIMING_SPAN_MIN_MS = 60_000;
|
|
300
|
+
/**
|
|
301
|
+
* The three timing facts the header reports: when the session was created, when
|
|
302
|
+
* it was last active, and how long it ran. Reads the parsed transcript when
|
|
303
|
+
* there is one and otherwise the indexed `SessionMeta`, so a remote or
|
|
304
|
+
* unindexed session — which has no local transcript to parse — still reports
|
|
305
|
+
* them instead of silently dropping the whole line.
|
|
306
|
+
*
|
|
307
|
+
* `lastActive` and `lasted` are omitted for a session whose whole life was under
|
|
308
|
+
* a minute: there they just restate `created`.
|
|
309
|
+
*/
|
|
310
|
+
export function extractTiming(session, events) {
|
|
311
|
+
const firstMs = Date.parse(events[0]?.timestamp ?? session.timestamp);
|
|
300
312
|
if (Number.isNaN(firstMs))
|
|
301
313
|
return {};
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
|
|
314
|
+
const createdAgo = humanDuration(Math.max(0, Date.now() - firstMs));
|
|
315
|
+
const lastMs = Date.parse(events[events.length - 1]?.timestamp ?? session.lastActivity ?? session.timestamp);
|
|
316
|
+
if (Number.isNaN(lastMs))
|
|
317
|
+
return { createdAgo };
|
|
318
|
+
// A scan-persisted duration beats the timestamp subtraction when there are no
|
|
319
|
+
// events to read, because `lastActivity` may have fallen back to file mtime.
|
|
320
|
+
const spanMs = events.length === 0 && session.durationMs !== undefined
|
|
321
|
+
? session.durationMs
|
|
322
|
+
: Math.max(0, lastMs - firstMs);
|
|
323
|
+
if (spanMs < TIMING_SPAN_MIN_MS)
|
|
324
|
+
return { createdAgo };
|
|
325
|
+
return {
|
|
326
|
+
createdAgo,
|
|
327
|
+
lastActiveAgo: humanDuration(Math.max(0, Date.now() - lastMs)),
|
|
328
|
+
duration: humanDuration(spanMs),
|
|
329
|
+
};
|
|
305
330
|
}
|
|
306
331
|
function countMessages(events) {
|
|
307
332
|
return events.filter(e => e.type === 'message').length;
|
|
@@ -11,6 +11,8 @@ interface ResumeOptions {
|
|
|
11
11
|
ghostty?: boolean;
|
|
12
12
|
tmux?: boolean;
|
|
13
13
|
vscodium?: boolean;
|
|
14
|
+
/** --terminal-app: force macOS Terminal.app. Named to avoid reading as `run --terminal`. */
|
|
15
|
+
terminalApp?: boolean;
|
|
14
16
|
splits?: boolean;
|
|
15
17
|
}
|
|
16
18
|
export declare function registerSessionsResumeCommand(sessionsCmd: Command): void;
|
|
@@ -36,6 +36,7 @@ export function registerSessionsResumeCommand(sessionsCmd) {
|
|
|
36
36
|
.option('--ghostty', 'Force the Ghostty backend')
|
|
37
37
|
.option('--tmux', 'Force the tmux backend')
|
|
38
38
|
.option('--vscodium', 'Force the VSCodium agent-terminal backend (swarm-ext)')
|
|
39
|
+
.option('--terminal-app', 'Force macOS Terminal.app (no split support — panes become tabs)')
|
|
39
40
|
.option('--splits', 'Pack two sessions side by side per tab (default: one tab per session)');
|
|
40
41
|
setHelpSections(cmd, {
|
|
41
42
|
examples: `
|
|
@@ -156,6 +157,12 @@ async function sessionsResumeAction(query, options) {
|
|
|
156
157
|
// recovery; callers can explicitly opt into pairs of side-by-side panes.
|
|
157
158
|
const packing = resolveResumePacking(options);
|
|
158
159
|
const where = options.host ? `${backend} on ${options.host}` : backend;
|
|
160
|
+
// Terminal.app has no scriptable split, so its buildSplit opens a tab. Say so
|
|
161
|
+
// when the user actually asked for panes — the layout silently not happening
|
|
162
|
+
// is worse than one line of warning.
|
|
163
|
+
if (backend === 'terminal' && packing === 'two-per-tab') {
|
|
164
|
+
console.log(chalk.yellow('Terminal.app cannot split panes — opening one tab per session instead.'));
|
|
165
|
+
}
|
|
159
166
|
console.log(chalk.gray(`Opening ${items.length} session${items.length === 1 ? '' : 's'} in ${where} (${packing})…`));
|
|
160
167
|
const results = await openSurfaces(items.map((it) => ({ cwd: it.cwd, command: it.command })), { backend, host: options.host, packing });
|
|
161
168
|
let opened = 0;
|
|
@@ -185,7 +192,8 @@ async function resolveBackend(options, ctx, count) {
|
|
|
185
192
|
: options.ghostty ? 'ghostty'
|
|
186
193
|
: options.tmux ? 'tmux'
|
|
187
194
|
: options.vscodium ? 'vscodium-agent'
|
|
188
|
-
:
|
|
195
|
+
: options.terminalApp ? 'terminal'
|
|
196
|
+
: undefined;
|
|
189
197
|
if (forced)
|
|
190
198
|
return forced;
|
|
191
199
|
// Remote defaults to tmux (headless, no GUI session assumptions); override with a backend flag.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Command } from 'commander';
|
|
2
|
+
import { type ProjectDef } from '../lib/projects.js';
|
|
2
3
|
import type { SessionAgentId, SessionMeta, ViewMode } from '../lib/session/types.js';
|
|
3
4
|
import { type ActiveSession } from '../lib/session/active.js';
|
|
4
5
|
import { gatherRemoteList, runOnPeer } from '../lib/session/remote-list.js';
|
|
@@ -351,11 +352,15 @@ export declare function teamBadge(session: SessionMeta): {
|
|
|
351
352
|
* dashes and needlessly truncate the topic. Worktree stays a trailing badge. */
|
|
352
353
|
export declare function flatSessionRow(session: SessionMeta, live?: ActiveSession, showTicket?: boolean, cols?: PickerColumns, favorite?: boolean): string;
|
|
353
354
|
/**
|
|
354
|
-
* Group key for the overview:
|
|
355
|
-
*
|
|
356
|
-
*
|
|
355
|
+
* Group key for the overview: resolve the cwd through the same canonical
|
|
356
|
+
* resolver the `agents activity` timeline groups by — a defined project's name
|
|
357
|
+
* when `defs` contains the cwd (multi-repo projects read as one group), else
|
|
358
|
+
* the repo-level key, so a monorepo subdir (`<repo>/apps/cli`) reads as
|
|
359
|
+
* `<repo>` in both views. Falls back to the indexed project name (stamped at
|
|
360
|
+
* scan time) when the cwd carries nothing usable, e.g. a remote path this
|
|
361
|
+
* machine cannot see still folds by basename through the same resolver.
|
|
357
362
|
*/
|
|
358
|
-
export declare function overviewProjectKey(s: Pick<SessionMeta, 'project' | 'cwd'
|
|
363
|
+
export declare function overviewProjectKey(s: Pick<SessionMeta, 'project' | 'cwd'>, defs?: ProjectDef[]): string;
|
|
359
364
|
export interface OverviewGroup {
|
|
360
365
|
key: string;
|
|
361
366
|
total: number;
|
|
@@ -369,7 +374,7 @@ export interface OverviewGroup {
|
|
|
369
374
|
* are ordered by their most-recent session so the newest-active project leads.
|
|
370
375
|
* `perProjectCap = Infinity` expands every group. Pure — unit-tested.
|
|
371
376
|
*/
|
|
372
|
-
export declare function buildOverviewGroups(pool: SessionMeta[], perProjectCap: number): {
|
|
377
|
+
export declare function buildOverviewGroups(pool: SessionMeta[], perProjectCap: number, defs?: ProjectDef[]): {
|
|
373
378
|
groups: OverviewGroup[];
|
|
374
379
|
projectCount: number;
|
|
375
380
|
};
|
|
@@ -14,6 +14,8 @@ import { spawn } from 'child_process';
|
|
|
14
14
|
import { Option } from 'commander';
|
|
15
15
|
import chalk from 'chalk';
|
|
16
16
|
import { truncate, padRight } from '../lib/format.js';
|
|
17
|
+
import { resolveProjectKey } from '../lib/project-key.js';
|
|
18
|
+
import { listProjectDefs, resolveProjectNameForCwd } from '../lib/projects.js';
|
|
17
19
|
import ora from 'ora';
|
|
18
20
|
import { SESSION_AGENTS } from '../lib/session/types.js';
|
|
19
21
|
import { discoverArtifacts, readArtifact, resolveArtifact } from '../lib/session/artifacts.js';
|
|
@@ -32,7 +34,7 @@ import { findSessionsById, querySessions } from '../lib/session/db.js';
|
|
|
32
34
|
import { filterTeamSessions, safeTeamText } from '../lib/session/team-filter.js';
|
|
33
35
|
import { parseSession } from '../lib/session/parse.js';
|
|
34
36
|
import { runRemoteSessions, buildForwardedArgs, ensureWholeIndex } from '../lib/session/remote.js';
|
|
35
|
-
import { formatRelativeTime } from '../lib/session/relative-time.js';
|
|
37
|
+
import { formatRelativeTime, sessionAgeParts } from '../lib/session/relative-time.js';
|
|
36
38
|
import { renderConversationMarkdown, renderSummary, renderSummaryHeader, computeSummaryStats, renderJson, filterEvents, parseRoleList, linkPath, linkUrl, shortenModel } from '../lib/session/render.js';
|
|
37
39
|
import { linearIssueUrl } from '../lib/session/linear.js';
|
|
38
40
|
import { renderMarkdown } from '../lib/markdown.js';
|
|
@@ -1768,6 +1770,33 @@ function metaSignals(s) {
|
|
|
1768
1770
|
ticket: s.ticketId ? { id: s.ticketId } : undefined,
|
|
1769
1771
|
};
|
|
1770
1772
|
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Narrowest topic a row is willing to render. The time cell drops its creation
|
|
1775
|
+
* field rather than squeeze the topic past this — a row that wraps is worse than
|
|
1776
|
+
* a row missing one field.
|
|
1777
|
+
*/
|
|
1778
|
+
const MIN_TOPIC_W = 16;
|
|
1779
|
+
/**
|
|
1780
|
+
* The trailing time cell, as two fields: `3d → 2 hours ago` — when the session
|
|
1781
|
+
* was created, then when it was last active. One label answers neither "is this
|
|
1782
|
+
* the old session I'm looking for" nor "how long did it run", since a session
|
|
1783
|
+
* touched an hour ago may have started last week.
|
|
1784
|
+
*
|
|
1785
|
+
* Collapses to last-activity alone when the session ran under a minute (both
|
|
1786
|
+
* halves would name the same moment) or when `topicSlack` — the columns the row
|
|
1787
|
+
* has left for its topic — cannot spare the extra width. `extraW` is what the
|
|
1788
|
+
* creation field cost, for the caller to take off the topic budget.
|
|
1789
|
+
*/
|
|
1790
|
+
function timeCell(age, topicSlack) {
|
|
1791
|
+
const lastOnly = { plain: age.last, text: chalk.gray(age.last), extraW: 0 };
|
|
1792
|
+
if (!age.created)
|
|
1793
|
+
return lastOnly;
|
|
1794
|
+
const prefix = `${age.created} → `;
|
|
1795
|
+
const extraW = stringWidth(prefix);
|
|
1796
|
+
if (topicSlack - extraW < MIN_TOPIC_W)
|
|
1797
|
+
return lastOnly;
|
|
1798
|
+
return { plain: prefix + age.last, text: chalk.dim(prefix) + chalk.gray(age.last), extraW };
|
|
1799
|
+
}
|
|
1771
1800
|
/** One flat table row:
|
|
1772
1801
|
* shortId · agent · version · model · project · [glyph] label·doing · [ticket] · [wt] · time
|
|
1773
1802
|
* `doing` is the live preview when running, else the topic. The `ticket` column
|
|
@@ -1776,7 +1805,7 @@ function metaSignals(s) {
|
|
|
1776
1805
|
* dashes and needlessly truncate the topic. Worktree stays a trailing badge. */
|
|
1777
1806
|
export function flatSessionRow(session, live, showTicket = false, cols = {}, favorite = false) {
|
|
1778
1807
|
const agentColor = colorAgent(session.agent);
|
|
1779
|
-
const
|
|
1808
|
+
const age = sessionAgeParts(session.timestamp, session.lastActivity);
|
|
1780
1809
|
const project = session.project || '-';
|
|
1781
1810
|
const tag = originTag(session) || teamTag(session);
|
|
1782
1811
|
const label = session.label;
|
|
@@ -1813,12 +1842,15 @@ export function flatSessionRow(session, live, showTicket = false, cols = {}, fav
|
|
|
1813
1842
|
// Same conditional 2 cells as the picker's marker, for the same reason.
|
|
1814
1843
|
const favW = cols.showFavorite ? 2 : 0;
|
|
1815
1844
|
const favCell = cols.showFavorite ? (favorite ? chalk.yellow('★ ') : ' ') : '';
|
|
1816
|
-
|
|
1817
|
-
|
|
1845
|
+
// Sized against the last-activity label alone, so the creation field is an
|
|
1846
|
+
// additive decision the row makes only once it knows what space is left.
|
|
1847
|
+
const fixedW = favW + (10 + 9 + 8 + 16) + glyphW + statusW + machineW + ticketW + wtW + team.width + stringWidth(age.last) + 1;
|
|
1848
|
+
const modelSlack = width - fixedW - MIN_TOPIC_W;
|
|
1818
1849
|
const modelW = requestedModelW <= modelSlack
|
|
1819
1850
|
? requestedModelW
|
|
1820
1851
|
: modelSlack >= PICKER_MODEL_MIN ? modelSlack : 0;
|
|
1821
|
-
const
|
|
1852
|
+
const when = timeCell(age, width - fixedW - modelW);
|
|
1853
|
+
const topicW = Math.max(MIN_TOPIC_W, width - fixedW - modelW - when.extraW);
|
|
1822
1854
|
return (favCell +
|
|
1823
1855
|
chalk.white(padToWidth(truncateToWidth(session.shortId, 9), 10)) +
|
|
1824
1856
|
agentColor(padToWidth(truncateToWidth(session.agent, 8), 9)) +
|
|
@@ -1832,12 +1864,12 @@ export function flatSessionRow(session, live, showTicket = false, cols = {}, fav
|
|
|
1832
1864
|
renderTopicCell(label, doing, '', topicW, topicW) +
|
|
1833
1865
|
ticketCell +
|
|
1834
1866
|
(wt ? wt + ' ' : '') +
|
|
1835
|
-
|
|
1867
|
+
when.text);
|
|
1836
1868
|
}
|
|
1837
1869
|
/** One tree-mode row (grouped under a dir header): id · agent · badges · topic · time. No version/project column. */
|
|
1838
1870
|
function treeSessionRow(session, live) {
|
|
1839
1871
|
const agentColor = colorAgent(session.agent);
|
|
1840
|
-
const
|
|
1872
|
+
const age = sessionAgeParts(session.timestamp, session.lastActivity);
|
|
1841
1873
|
const tag = originTag(session) || teamTag(session);
|
|
1842
1874
|
const label = session.label;
|
|
1843
1875
|
const { glyph, preview } = liveGlyphAndPreview(live);
|
|
@@ -1853,7 +1885,9 @@ function treeSessionRow(session, live) {
|
|
|
1853
1885
|
const head = label ? `${label} · ${topic}` : topic;
|
|
1854
1886
|
const { cell: statusCell, width: statusW } = liveStatusCell(live);
|
|
1855
1887
|
const glyphW = glyph ? 2 : 0;
|
|
1856
|
-
const
|
|
1888
|
+
const baseTopicW = terminalWidth() - (2 + 9 + 8) - glyphW - statusW - badgeW - team.width - stringWidth(age.last) - 1;
|
|
1889
|
+
const when = timeCell(age, baseTopicW);
|
|
1890
|
+
const topicW = Math.max(12, baseTopicW - when.extraW);
|
|
1857
1891
|
return (' ' +
|
|
1858
1892
|
chalk.dim(padToWidth(session.shortId, 9)) +
|
|
1859
1893
|
agentColor(padToWidth(truncateToWidth(session.agent, 7), 8)) +
|
|
@@ -1862,7 +1896,7 @@ function treeSessionRow(session, live) {
|
|
|
1862
1896
|
statusCell +
|
|
1863
1897
|
teamSeg +
|
|
1864
1898
|
padToWidth(chalk.white(truncateToWidth(head, topicW)), topicW) +
|
|
1865
|
-
' ' +
|
|
1899
|
+
' ' + when.text);
|
|
1866
1900
|
}
|
|
1867
1901
|
/**
|
|
1868
1902
|
* Live-session index for enriching the default listing, or undefined when
|
|
@@ -1884,21 +1918,21 @@ async function maybeLiveIndex(options) {
|
|
|
1884
1918
|
}
|
|
1885
1919
|
}
|
|
1886
1920
|
/**
|
|
1887
|
-
* Group key for the overview:
|
|
1888
|
-
*
|
|
1889
|
-
*
|
|
1921
|
+
* Group key for the overview: resolve the cwd through the same canonical
|
|
1922
|
+
* resolver the `agents activity` timeline groups by — a defined project's name
|
|
1923
|
+
* when `defs` contains the cwd (multi-repo projects read as one group), else
|
|
1924
|
+
* the repo-level key, so a monorepo subdir (`<repo>/apps/cli`) reads as
|
|
1925
|
+
* `<repo>` in both views. Falls back to the indexed project name (stamped at
|
|
1926
|
+
* scan time) when the cwd carries nothing usable, e.g. a remote path this
|
|
1927
|
+
* machine cannot see still folds by basename through the same resolver.
|
|
1890
1928
|
*/
|
|
1891
|
-
export function overviewProjectKey(s) {
|
|
1929
|
+
export function overviewProjectKey(s, defs) {
|
|
1930
|
+
const resolved = defs?.length ? resolveProjectNameForCwd(s.cwd, defs) : resolveProjectKey(s.cwd);
|
|
1931
|
+
if (resolved)
|
|
1932
|
+
return resolved;
|
|
1892
1933
|
if (s.project && s.project.trim())
|
|
1893
1934
|
return s.project.trim();
|
|
1894
|
-
|
|
1895
|
-
if (!cwd)
|
|
1896
|
-
return '(no project)';
|
|
1897
|
-
const wt = cwd.match(/\/([^/]+)\/\.agents\/worktrees\//);
|
|
1898
|
-
if (wt)
|
|
1899
|
-
return wt[1];
|
|
1900
|
-
const parts = cwd.split('/');
|
|
1901
|
-
return parts[parts.length - 1] || cwd;
|
|
1935
|
+
return '(no project)';
|
|
1902
1936
|
}
|
|
1903
1937
|
/**
|
|
1904
1938
|
* Turn a recency-descending pool into project groups: each group shows its
|
|
@@ -1906,10 +1940,10 @@ export function overviewProjectKey(s) {
|
|
|
1906
1940
|
* are ordered by their most-recent session so the newest-active project leads.
|
|
1907
1941
|
* `perProjectCap = Infinity` expands every group. Pure — unit-tested.
|
|
1908
1942
|
*/
|
|
1909
|
-
export function buildOverviewGroups(pool, perProjectCap) {
|
|
1943
|
+
export function buildOverviewGroups(pool, perProjectCap, defs) {
|
|
1910
1944
|
const byKey = new Map();
|
|
1911
1945
|
for (const s of pool) {
|
|
1912
|
-
const k = overviewProjectKey(s);
|
|
1946
|
+
const k = overviewProjectKey(s, defs);
|
|
1913
1947
|
(byKey.get(k) ?? byKey.set(k, []).get(k)).push(s);
|
|
1914
1948
|
}
|
|
1915
1949
|
const cap = Math.max(1, perProjectCap);
|
|
@@ -1927,7 +1961,7 @@ export function buildOverviewGroups(pool, perProjectCap) {
|
|
|
1927
1961
|
* `· N more` per project and a `+N more projects` when the list is capped.
|
|
1928
1962
|
*/
|
|
1929
1963
|
function printSessionOverview(pool, hiddenCount, liveIndex, opts) {
|
|
1930
|
-
const { groups } = buildOverviewGroups(pool, opts.expand ? Infinity : opts.perProjectCap);
|
|
1964
|
+
const { groups } = buildOverviewGroups(pool, opts.expand ? Infinity : opts.perProjectCap, listProjectDefs());
|
|
1931
1965
|
const shownGroups = opts.expand ? groups : groups.slice(0, OVERVIEW_MAX_PROJECTS);
|
|
1932
1966
|
const hiddenProjects = groups.length - shownGroups.length;
|
|
1933
1967
|
const total = pool.length;
|
|
@@ -2252,7 +2286,7 @@ export function liveHostLabel(a) {
|
|
|
2252
2286
|
}
|
|
2253
2287
|
export function formatPickerLabel(s, query, cols = {}, ssh, host = '', favorite = false, live) {
|
|
2254
2288
|
const agentColor = colorAgent(s.agent);
|
|
2255
|
-
const
|
|
2289
|
+
const age = sessionAgeParts(s.timestamp, s.lastActivity);
|
|
2256
2290
|
const project = s.project || '-';
|
|
2257
2291
|
// SSH-launch origin (live rows only): mirrors the flat listing's `ssh←<device>`
|
|
2258
2292
|
// badge. Rendered as its OWN red segment before the topic cell — folding it into
|
|
@@ -2306,7 +2340,11 @@ export function formatPickerLabel(s, query, cols = {}, ssh, host = '', favorite
|
|
|
2306
2340
|
const status = cols.showStatus ? liveStatusCell(live) : { cell: '', width: 0 };
|
|
2307
2341
|
const statusW = cols.showStatus ? LIVE_STATUS_W : 0;
|
|
2308
2342
|
const statusCell = cols.showStatus ? (status.cell || ' '.repeat(LIVE_STATUS_W)) : '';
|
|
2309
|
-
|
|
2343
|
+
// Sized against the last-activity label alone; the creation field is then an
|
|
2344
|
+
// additive decision made against whatever width is left (see the flat listing).
|
|
2345
|
+
const baseTopicW = terminalWidth() - gutter - favW - statusW - (10 + 9 + 8 + 16) - machineColW - hostW - ticketW - wtW - sshW - team.width - stringWidth(age.last) - 1;
|
|
2346
|
+
const when = timeCell(age, baseTopicW);
|
|
2347
|
+
const topicW = Math.max(MIN_TOPIC_W, baseTopicW - when.extraW);
|
|
2310
2348
|
return (favCell +
|
|
2311
2349
|
// Truncated, not just padded: an indexed shortId is always 8 chars, but a
|
|
2312
2350
|
// live row with no session id is named by its pid or cloud task, which can
|
|
@@ -2323,7 +2361,7 @@ export function formatPickerLabel(s, query, cols = {}, ssh, host = '', favorite
|
|
|
2323
2361
|
renderTopicCell(label, topic, query, topicW, topicW) +
|
|
2324
2362
|
ticketCell +
|
|
2325
2363
|
(wt ? wt + ' ' : '') +
|
|
2326
|
-
|
|
2364
|
+
when.text);
|
|
2327
2365
|
}
|
|
2328
2366
|
/** Hints rotated above the picker so the flags/features stay discoverable. */
|
|
2329
2367
|
const PICKER_TIPS = [
|
package/dist/commands/sync.js
CHANGED
|
@@ -37,6 +37,7 @@ import { capableAgents } from '../lib/capabilities.js';
|
|
|
37
37
|
import { parseHookManifest, registerHooksToSettings } from '../lib/hooks.js';
|
|
38
38
|
import { compileRulesForProject } from '../lib/rules/compile.js';
|
|
39
39
|
import { runLaunchSync } from '../lib/project-launch.js';
|
|
40
|
+
import { formatKeptProjectResources } from '../lib/project-resources.js';
|
|
40
41
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
41
42
|
import { runUmbrellaSync } from '../lib/sync-umbrella.js';
|
|
42
43
|
import { addHostOption } from '../lib/hosts/option.js';
|
|
@@ -516,8 +517,11 @@ function printSyncDetail(result, agent, version, cwd) {
|
|
|
516
517
|
lines.push({ kind: 'plugins', items: result.plugins });
|
|
517
518
|
if (result.workflows.length > 0)
|
|
518
519
|
lines.push({ kind: 'workflows', items: result.workflows });
|
|
520
|
+
const kept = formatKeptProjectResources(result.projectSkipped);
|
|
519
521
|
if (lines.length === 0) {
|
|
520
522
|
console.log(chalk.gray(`Already in sync — ${agentLabel(agent)}@${version}`));
|
|
523
|
+
if (kept)
|
|
524
|
+
console.log(chalk.gray(kept));
|
|
521
525
|
return;
|
|
522
526
|
}
|
|
523
527
|
console.log(chalk.green(`Synced to ${agentLabel(agent)}@${version}:`));
|
|
@@ -531,6 +535,8 @@ function printSyncDetail(result, agent, version, cwd) {
|
|
|
531
535
|
const count = chalk.cyan(`(${sorted.length})`.padStart(5));
|
|
532
536
|
console.log(` ${chalk.bold(padded)} ${count} ${chalk.gray(preview)}${more}`);
|
|
533
537
|
}
|
|
538
|
+
if (kept)
|
|
539
|
+
console.log(chalk.gray(kept));
|
|
534
540
|
}
|
|
535
541
|
function runLaunchMode(agent, version, cwd, quiet) {
|
|
536
542
|
let result;
|
|
@@ -561,7 +567,7 @@ function runLaunchMode(agent, version, cwd, quiet) {
|
|
|
561
567
|
else {
|
|
562
568
|
console.log(chalk.green(`Launch sync: ${bits.join(', ')}`));
|
|
563
569
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
570
|
+
const kept = formatKeptProjectResources(result.workspaceSkipped);
|
|
571
|
+
if (kept)
|
|
572
|
+
console.log(chalk.gray(kept));
|
|
567
573
|
}
|
package/dist/commands/view.js
CHANGED
|
@@ -11,6 +11,7 @@ import { authCacheKey, formatCheckedAge, readAuthHealthCache } from '../lib/auth
|
|
|
11
11
|
import { agentReportsUsage, deriveUsageStatusFromSnapshot, formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, } from '../lib/usage.js';
|
|
12
12
|
import { readManifest } from '../lib/manifest.js';
|
|
13
13
|
import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, isVersionIsolated, getIsolatedDefault, } from '../lib/versions.js';
|
|
14
|
+
import { formatKeptProjectResources } from '../lib/project-resources.js';
|
|
14
15
|
import { ensureVersionedAliasCurrent, removeShim, } from '../lib/shims.js';
|
|
15
16
|
import { getAgentResources } from '../lib/resources.js';
|
|
16
17
|
import { renderMergedResources } from '../lib/merged-resources.js';
|
|
@@ -722,6 +723,9 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
722
723
|
if (synced.length > 0) {
|
|
723
724
|
console.log(chalk.green(`\nSynced to ${agentLabel(filterAgentId)}@${defaultVersion}: ${synced.join(', ')}`));
|
|
724
725
|
}
|
|
726
|
+
const kept = formatKeptProjectResources(result.projectSkipped);
|
|
727
|
+
if (kept)
|
|
728
|
+
console.log(chalk.gray(kept));
|
|
725
729
|
}
|
|
726
730
|
}
|
|
727
731
|
catch (err) {
|
package/dist/index.js
CHANGED
|
@@ -94,7 +94,7 @@ if (IS_DEV_BUILD) {
|
|
|
94
94
|
// module on each invocation (which loaded the whole ~50-module tree before the
|
|
95
95
|
// first byte of output), the registry maps a command name to a thunk that
|
|
96
96
|
// imports only what that command needs. See src/lib/startup/command-registry.ts.
|
|
97
|
-
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadExport, loadPackages, loadRoutines, loadMonitors, loadRun, loadFork, loadDefaults, loadSet, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadStatus, loadProfiles, loadHarness, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadPerf, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadShare, loadSend, loadHq, loadFeed, loadActivity, loadMailboxes, } from './lib/startup/command-registry.js';
|
|
97
|
+
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadExport, loadPackages, loadRoutines, loadMonitors, loadProjects, loadRun, loadFork, loadDefaults, loadSet, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadStatus, loadProfiles, loadHarness, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadPerf, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadShare, loadSend, loadHq, loadFeed, loadActivity, loadMailboxes, } from './lib/startup/command-registry.js';
|
|
98
98
|
import { applyGlobalHelpConventions } from './lib/help.js';
|
|
99
99
|
import { renderWhatsNew } from './lib/whats-new.js';
|
|
100
100
|
import { getCliLaunch } from './lib/cli-entry.js';
|
|
@@ -147,11 +147,25 @@ function auditCommandPath(cmd) {
|
|
|
147
147
|
return parts;
|
|
148
148
|
}
|
|
149
149
|
const auditStarts = new WeakMap();
|
|
150
|
+
/**
|
|
151
|
+
* Commands that WRITE the event stream, so recording their own invocation would
|
|
152
|
+
* add records to the log they are writing into. `events emit` is batched — one
|
|
153
|
+
* flush every few seconds per open editor window — so auditing it would bury the
|
|
154
|
+
* real events under two `command.*` records per flush. `_internal friction`
|
|
155
|
+
* exists for the same reason (shell guards fire before any `agents` process
|
|
156
|
+
* exists, so they cannot emit in-process) and had the same self-logging bug.
|
|
157
|
+
*/
|
|
158
|
+
const AUDIT_EXEMPT_COMMANDS = new Set([
|
|
159
|
+
'events emit',
|
|
160
|
+
'_internal friction',
|
|
161
|
+
]);
|
|
150
162
|
program.hook('preAction', (_thisCommand, actionCommand) => {
|
|
151
163
|
try {
|
|
152
164
|
const parts = auditCommandPath(actionCommand);
|
|
153
165
|
if (parts.length === 0)
|
|
154
166
|
return;
|
|
167
|
+
if (AUDIT_EXEMPT_COMMANDS.has(parts.join(' ')))
|
|
168
|
+
return;
|
|
155
169
|
auditStarts.set(actionCommand, Date.now());
|
|
156
170
|
emit('command.start', {
|
|
157
171
|
module: parts[0],
|
|
@@ -172,6 +186,8 @@ program.hook('postAction', (_thisCommand, actionCommand) => {
|
|
|
172
186
|
const parts = auditCommandPath(actionCommand);
|
|
173
187
|
if (parts.length === 0)
|
|
174
188
|
return;
|
|
189
|
+
if (AUDIT_EXEMPT_COMMANDS.has(parts.join(' ')))
|
|
190
|
+
return;
|
|
175
191
|
const started = auditStarts.get(actionCommand);
|
|
176
192
|
const durationMs = started !== undefined ? Date.now() - started : undefined;
|
|
177
193
|
const command = parts.join(' ');
|
|
@@ -871,6 +887,7 @@ async function registerAllEagerCommands() {
|
|
|
871
887
|
await reg(loadPackages);
|
|
872
888
|
await reg(loadRoutines);
|
|
873
889
|
await reg(loadMonitors);
|
|
890
|
+
await reg(loadProjects);
|
|
874
891
|
await reg(loadRun);
|
|
875
892
|
await reg(loadFork);
|
|
876
893
|
await reg(loadDefaults);
|