paqad-ai
Version:
Spec-driven development framework — AI agents that think before they type
163 lines (144 loc) • 5.38 kB
text/typescript
import { slash } from './command-name';
import { formatTokens } from './cost';
import { stageStatus, STAGE_MARK, tableStatus } from './instruments';
import { stageSegments, type BandView } from './model';
import { shipRail } from './ship';
import { BRAND, CONTROL_HOTKEYS, controlSegment, flagChip, type BandRow } from './shell';
import { showsTable } from './table';
import type { PaqadHudSnapshot, PaqadTone } from './types';
export const MINIMISED_KEY = 'band.minimised';
export const HIDDEN_KEY_PREFIX = 'band.hidden:';
export const STORE_KEY_LIMIT = 256;
export const CLOSE_HEADER = 'Close band?';
export const CLOSE_FOR_NOW = 'Close for now';
export const CLOSE_FOR_NOW_MEANS = 'Comes back in your next session';
export const HIDE_ALWAYS = 'Hide always';
export const NEEDS_YOU = 'Waiting for your answer';
export const SHOWN_AGAIN = 'The paqad band is back.';
export function hideAlwaysMeans(surface: 'terminal' | 'desktop' = 'terminal'): string {
return surface === 'desktop'
? `Stays hidden until you press Show or run ${slash('show')}`
: `Stays hidden until you click paqad under the prompt or run ${slash('show')}`;
}
export function closeQuestion(): string {
return `Close the paqad band? ${CLOSE_FOR_NOW}: ${CLOSE_FOR_NOW_MEANS}. ${HIDE_ALWAYS}: ${hideAlwaysMeans()}.`;
}
export function closedHint(): string {
return `Closed for this session · ${slash('show')}`;
}
export function hiddenHint(): string {
return `Hidden always · ${slash('show')}`;
}
export function hiddenReply(): string {
return `The paqad band is hidden. Run ${slash('show')} to bring it back.`;
}
export function hiddenKey(root: string): string {
const key = `${HIDDEN_KEY_PREFIX}${root}`;
return key.length <= STORE_KEY_LIMIT
? key
: `${HIDDEN_KEY_PREFIX}…${root.slice(root.length - (STORE_KEY_LIMIT - HIDDEN_KEY_PREFIX.length - 1))}`;
}
export interface BandVisibility {
minimised: boolean;
closed: boolean;
hidden: boolean;
}
export type VisibilityMode = 'band' | 'minimised' | 'closed' | 'hidden';
export function readBandVisibility(
stored: { minimised: unknown; hidden: unknown },
closed: boolean | null | undefined,
): BandVisibility {
return {
minimised: stored.minimised === true,
closed: closed === true,
hidden: stored.hidden === true,
};
}
export function visibilityMode(visibility: BandVisibility): VisibilityMode {
if (visibility.hidden) return 'hidden';
if (visibility.closed) return 'closed';
return visibility.minimised ? 'minimised' : 'band';
}
export function needsYouLine(): BandRow {
return [
{
text: `${BRAND} · ${NEEDS_YOU} ▸`,
tone: 'needs-you',
action: 'show',
hotkey: CONTROL_HOTKEYS.show,
key: 'paqad-needs-you',
},
];
}
export function closedLine(mode: 'closed' | 'hidden', view: BandView): BandRow {
return [
{
text: `${BRAND} · ${view.state} ▸`,
action: 'reopen',
hotkey: CONTROL_HOTKEYS.reopen,
key: 'paqad-reopen',
},
{ text: ` ${mode === 'closed' ? closedHint() : hiddenHint()}`, dim: true },
];
}
function stageBar(snapshot: PaqadHudSnapshot): string {
return stageSegments(snapshot)
.map((segment) => STAGE_MARK[segment.state].mark)
.join('');
}
type Field = { key: 'tokens' | 'ship' | 'table' | 'bar' | 'flags'; segments: BandRow };
export const IDLE_WORDS = 'Getting ready';
export function minimisedLine(
snapshot: PaqadHudSnapshot | null,
view: BandView,
columns: number,
now: number,
): BandRow {
const tone: PaqadTone = view.tone;
const head: BandRow = [{ text: BRAND, tone, opens: 'trust', key: 'paqad-brand' }];
const change = snapshot?.active ? snapshot.change : null;
const chip = change?.issue
? /^\d+$/.test(change.issue)
? `#${change.issue}`
: change.issue
: (change?.id?.slice(-6) ?? null);
if (chip) head.push({ text: ` ${chip}`, dim: true });
head.push({ text: snapshot?.active ? ` ${view.state}` : ` · ${IDLE_WORDS}`, tone });
const fields: Field[] = [];
if (snapshot?.active) {
fields.push({
key: 'bar',
segments: [{ text: ` ${stageBar(snapshot)} ${stageStatus(snapshot, now)}` }],
});
if (showsTable(snapshot) && !snapshot.table.skipped) {
fields.push({
key: 'table',
segments: [{ text: ` High Table ${tableStatus(snapshot, snapshot.table)}`, dim: true }],
});
}
const status = shipRail(snapshot).status;
if (status) fields.push({ key: 'ship', segments: [{ text: ` ${status}`, dim: true }] });
}
const flags = flagChip(view.flags);
if (flags) fields.push({ key: 'flags', segments: [{ text: ' ' }, flags] });
if (snapshot?.active && snapshot.cost) {
fields.push({
key: 'tokens',
segments: [{ text: ` ${formatTokens(snapshot.cost.tokens.total)} tokens`, dim: true }],
});
}
const tail: BandRow = [
{ text: ' ' },
controlSegment('restore'),
{ text: ' ' },
controlSegment('close'),
];
const width = (row: BandRow) => row.reduce((sum, segment) => sum + [...segment.text].length, 0);
const lineOf = (kept: Field[]) => [...head, ...kept.flatMap((field) => field.segments), ...tail];
let kept = fields;
for (const drop of ['tokens', 'ship', 'table', 'bar'] as const) {
if (width(lineOf(kept)) <= columns) break;
kept = kept.filter((field) => field.key !== drop);
}
return lineOf(kept);
}