pi-cockpit 0.1.0 → 0.1.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cockpit",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Agent cockpit for Pi: a list-mode status stack (live teammates + todo plan) pinned above the editor, plus a Starship-style footer. Renders only via public extension APIs (setWidget/setFooter).",
5
5
  "type": "module",
6
6
  "keywords": [
package/src/footer.ts CHANGED
@@ -27,9 +27,10 @@ export interface FooterParts {
27
27
  ctxTokens: number;
28
28
  ctxWindow: number;
29
29
  totals: UsageTotals;
30
- git?: string;
31
- agentSummary?: string;
32
- workflowStatus?: string;
30
+ git?: string;
31
+ agentSummary?: string;
32
+ bashBgStatus?: string;
33
+ workflowStatus?: string;
33
34
  extensionStatuses?: readonly ExtensionStatusSegment[];
34
35
  width: number;
35
36
  glyphs: IconGlyphs;
@@ -322,11 +323,14 @@ export function renderFooter(p: FooterParts): string[] {
322
323
  }
323
324
  if (!selected) fittedLeft = fitLeftBeside("");
324
325
 
325
- const line1 = alignRight(fittedLeft.join(identitySeparator), right1, width, utils.measure);
326
- const lines = [utils.clip(line1, width, ell)];
327
- if (p.workflowStatus) {
328
- lines.push(utils.clip(theme.fg("muted", p.workflowStatus), width, ell));
329
- }
326
+ const line1 = alignRight(fittedLeft.join(identitySeparator), right1, width, utils.measure);
327
+ const lines = [utils.clip(line1, width, ell)];
328
+ if (p.bashBgStatus) {
329
+ lines.push(utils.clip(p.bashBgStatus, width, ell));
330
+ }
331
+ if (p.workflowStatus) {
332
+ lines.push(utils.clip(theme.fg("muted", p.workflowStatus), width, ell));
333
+ }
330
334
  const statuses = visibleStatuses.filter((status) => !controlStatuses.includes(status));
331
335
  if (statuses.length > 0) {
332
336
  const statusSeparator = ` ${sep} `;
package/src/index.ts CHANGED
@@ -2,16 +2,17 @@ import { isAbsolute, relative, resolve, sep } from "node:path";
2
2
  import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
3
3
  import type { TUI } from "@earendil-works/pi-tui";
4
4
  import { Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
5
- import { AgentsStore, type CompletePayload, type MessagePayload, type StartedPayload } from "./agents-store.ts";
6
- import { statusText, titleFor, workingMessage, type AmbientState } from "./ambient.ts";
7
- import { BashBgStore } from "./bash-bg-store.ts";
8
- import { BashBgOverlay } from "./bash-bg-overlay.ts";
9
- import { TodoStore } from "./todo-store.ts";
5
+ import { AgentsStore, type CompletePayload, type MessagePayload, type StartedPayload } from "./agents-store.ts";
6
+ import { statusText, titleFor, workingMessage, type AmbientState } from "./ambient.ts";
7
+ import { BashBgStore } from "./bash-bg-store.ts";
8
+ import { BashBgOverlay } from "./bash-bg-overlay.ts";
9
+ import { renderBashBgSummary } from "./bash-bg-widget.ts";
10
+ import { TodoStore } from "./todo-store.ts";
10
11
  import { makeTodoWidget, makeAgentWidget, terminalRows } from "./stack-widget.ts";
11
12
  import { activeThemeName, ThemePicker } from "./theme-picker.ts";
12
13
  import { getUsageTotals, invalidateUsageCache, renderFooter, type PaintTheme, type WidthUtils } from "./footer.ts";
13
14
  import { collectExtensionStatuses } from "./extension-status.ts";
14
- import { ANIMATION_PERIOD_MS, resolveGlyphs } from "./icons.ts";
15
+ import { ANIMATION_PERIOD_MS, resolveGlyphs, spinFrame } from "./icons.ts";
15
16
  import { ensureConfigExists, loadConfig, saveConfig } from "./config.ts";
16
17
  import { applyRow, buildRows, rowKeyForAccel, type SaveState } from "./settings-view.ts";
17
18
  import {
@@ -180,13 +181,12 @@ export default function (pi: ExtensionAPI): void {
180
181
  ctx.ui.setWidget(
181
182
  AGENT_WIDGET_KEY,
182
183
  (tui, theme) => {
183
- capturedTui = tui;
184
- return makeAgentWidget({
185
- getAgents: () => agents.snapshot(),
186
- getBashBgJobs: () => bashBg.snapshot(),
187
- getConfig: () => config,
188
- isRunning: () => running,
189
- isAnimating,
184
+ capturedTui = tui;
185
+ return makeAgentWidget({
186
+ getAgents: () => agents.snapshot(),
187
+ getConfig: () => config,
188
+ isRunning: () => running,
189
+ isAnimating,
190
190
  })(tui, theme);
191
191
  },
192
192
  { placement: "belowEditor" },
@@ -210,10 +210,23 @@ export default function (pi: ExtensionAPI): void {
210
210
  const component = {
211
211
  render(width: number): string[] {
212
212
  if (Number.isFinite(width) && width > 0) observedWidth = width;
213
- const cu = ctx.getContextUsage();
214
- const branch = footerData.getGitBranch();
215
- const extensionStatuses = collectExtensionStatuses(footerData.getExtensionStatuses());
216
- return renderFooter({
213
+ const cu = ctx.getContextUsage();
214
+ const branch = footerData.getGitBranch();
215
+ const extensionStatuses = collectExtensionStatuses(footerData.getExtensionStatuses());
216
+ const glyphs = resolveGlyphs(config.icons.mode);
217
+ const now = Date.now();
218
+ const bashBgStatus = renderBashBgSummary(
219
+ bashBg.snapshot(),
220
+ width,
221
+ theme,
222
+ FOOTER_UTILS,
223
+ {
224
+ glyphs,
225
+ spin: spinFrame(glyphs, now, isAnimating()),
226
+ now,
227
+ },
228
+ )[0];
229
+ return renderFooter({
217
230
  width,
218
231
  model: ctx.model?.id ?? "no-model",
219
232
  provider: ctx.model?.provider,
@@ -221,15 +234,13 @@ export default function (pi: ExtensionAPI): void {
221
234
  cwd: formatCwd(ctx.sessionManager.getCwd()),
222
235
  ctxPct: cu?.percent ?? 0,
223
236
  ctxTokens: cu?.tokens ?? 0,
224
- ctxWindow: cu?.contextWindow ?? ctx.model?.contextWindow ?? 0,
225
- totals: getUsageTotals(ctx.sessionManager.getEntries()),
226
- git: branch ?? undefined,
227
- // The Agents header one line above already states the roster and
228
- // the failure count. Repeating it here spent a footer segment to
229
- // say nothing new, and the two could disagree mid-update.
230
- workflowStatus: extensionStatuses.find((status) => status.key === WORKFLOW_STATUS_KEY)?.text,
231
- extensionStatuses: extensionStatuses.filter((status) => status.key !== WORKFLOW_STATUS_KEY),
232
- glyphs: resolveGlyphs(config.icons.mode),
237
+ ctxWindow: cu?.contextWindow ?? ctx.model?.contextWindow ?? 0,
238
+ totals: getUsageTotals(ctx.sessionManager.getEntries()),
239
+ git: branch ?? undefined,
240
+ bashBgStatus,
241
+ workflowStatus: extensionStatuses.find((status) => status.key === WORKFLOW_STATUS_KEY)?.text,
242
+ extensionStatuses: extensionStatuses.filter((status) => status.key !== WORKFLOW_STATUS_KEY),
243
+ glyphs,
233
244
  theme,
234
245
  utils: FOOTER_UTILS,
235
246
  });
@@ -2,11 +2,10 @@ import type { Theme } from "@earendil-works/pi-coding-agent";
2
2
  import type { TUI } from "@earendil-works/pi-tui";
3
3
  import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
4
4
  import { renderAgents, renderTodos, type PaintTheme, type WidthUtils } from "./render.ts";
5
- import { renderBashBgSummary } from "./bash-bg-widget.ts";
6
5
  import { fitLineByPriority, type PrioritizedSegment } from "./layout.ts";
7
6
  import { resolveGlyphs, spinFrame } from "./icons.ts";
8
7
  import { panelRows } from "./viewport.ts";
9
- import type { AgentRow, BashBgJob, CockpitConfig, TodoItem } from "./types.ts";
8
+ import type { AgentRow, CockpitConfig, TodoItem } from "./types.ts";
10
9
 
11
10
  export interface TodoWidgetDeps {
12
11
  getTodos: () => TodoItem[];
@@ -17,7 +16,6 @@ export interface TodoWidgetDeps {
17
16
 
18
17
  export interface AgentWidgetDeps {
19
18
  getAgents: () => AgentRow[];
20
- getBashBgJobs: () => BashBgJob[];
21
19
  getConfig: () => CockpitConfig;
22
20
  isRunning: () => boolean;
23
21
  isAnimating?: () => boolean;
@@ -71,24 +69,17 @@ export function makeAgentWidget(deps: AgentWidgetDeps) {
71
69
  render(width: number): string[] {
72
70
  const cfg = deps.getConfig();
73
71
  const agents = deps.getAgents();
74
- const bashBgJobs = deps.getBashBgJobs();
72
+ if (agents.length === 0) return [];
75
73
  const g = resolveGlyphs(cfg.icons.mode);
76
74
  const now = Date.now();
77
75
  const animating = deps.isAnimating?.() ?? true;
78
76
  const spin = spinFrame(g, now, animating);
79
- const bashBgLines = renderBashBgSummary(bashBgJobs, width, paint, UTILS, {
80
- glyphs: g,
81
- spin,
82
- now,
83
- });
84
- if (agents.length === 0) return bashBgLines;
85
77
  const running = deps.isRunning();
86
- // The panel budget covers everything this widget prints, so the header
87
- // and the background strip are charged to it before the roster is.
78
+ // The panel budget covers the roster header and its rows.
88
79
  const panel = panelRows(terminalRows(tui));
89
80
  const rosterRows = panel === undefined
90
81
  ? undefined
91
- : Math.max(1, panel - 1 - bashBgLines.length);
82
+ : Math.max(1, panel - 1);
92
83
  const opts = { glyphs: g, spin, now, maxRows: rosterRows };
93
84
  const dot = theme.fg(running ? "success" : "muted", running ? g.dotRunning : g.dotIdle);
94
85
  const failedCount = agents.filter((a) => a.status === "failed").length;
@@ -111,7 +102,6 @@ export function makeAgentWidget(deps: AgentWidgetDeps) {
111
102
  // Was the one line in the package pushed without any width clipping.
112
103
  lines.push(fitLineByPriority(headerSegs, width, UTILS, theme.fg("dim", g.separator), g.ellipsis));
113
104
  lines.push(...renderAgents(agents, cfg.agentsMode, width, paint, UTILS, { ...opts, withHead: false }));
114
- lines.push(...bashBgLines);
115
105
  return lines;
116
106
  },
117
107
  invalidate(): void {},