@aemonge-dev/pi-stardeck 0.0.0-stage → 0.6.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/LICENSE +21 -0
- package/README.md +240 -2
- package/demo/pi-stardeck.tape +59 -0
- package/docs/architecture.md +106 -0
- package/docs/assets/pi-stardeck.gif +0 -0
- package/docs/development.md +128 -0
- package/licenses/README.md +19 -0
- package/licenses/pi-vim-LICENSE +21 -0
- package/package.json +81 -4
- package/packages/footer/README.md +15 -0
- package/packages/footer/index.ts +37 -0
- package/packages/footer/package.json +32 -0
- package/packages/footer/src/protocol.ts +10 -0
- package/packages/footer/src/quota.ts +26 -0
- package/packages/footer/src/telemetry.ts +59 -0
- package/packages/pi-stardeck/README.md +220 -0
- package/packages/pi-stardeck/index.ts +691 -0
- package/packages/pi-stardeck/package.json +32 -0
- package/packages/pi-stardeck/src/activity-age.ts +184 -0
- package/packages/pi-stardeck/src/capabilities.ts +105 -0
- package/packages/pi-stardeck/src/deck.ts +807 -0
- package/packages/pi-stardeck/src/gauge.ts +1323 -0
- package/packages/pi-stardeck/src/git-refresh.ts +5 -0
- package/packages/pi-stardeck/src/process-resources.ts +428 -0
- package/packages/pi-stardeck/src/title-state.ts +195 -0
- package/packages/pi-stardeck/src/title.ts +443 -0
- package/packages/pi-vim-top-border/LICENSE +21 -0
- package/packages/pi-vim-top-border/LOCAL-CHANGES.md +71 -0
- package/packages/pi-vim-top-border/README.md +577 -0
- package/packages/pi-vim-top-border/clipboard-mirror.ts +451 -0
- package/packages/pi-vim-top-border/clipboard-policy.ts +23 -0
- package/packages/pi-vim-top-border/cursor-shape.ts +131 -0
- package/packages/pi-vim-top-border/index.ts +4195 -0
- package/packages/pi-vim-top-border/input-keys.ts +49 -0
- package/packages/pi-vim-top-border/mode-change-command.ts +120 -0
- package/packages/pi-vim-top-border/mode-colors.ts +97 -0
- package/packages/pi-vim-top-border/mode-label.ts +62 -0
- package/packages/pi-vim-top-border/motions.ts +297 -0
- package/packages/pi-vim-top-border/package.json +47 -0
- package/packages/pi-vim-top-border/prompt-external-editor.ts +152 -0
- package/packages/pi-vim-top-border/prompt-rail.ts +334 -0
- package/packages/pi-vim-top-border/prompt-status.ts +131 -0
- package/packages/pi-vim-top-border/settings.ts +287 -0
- package/packages/pi-vim-top-border/text-objects.ts +389 -0
- package/packages/pi-vim-top-border/types.ts +51 -0
- package/packages/pi-vim-top-border/visual.ts +65 -0
- package/packages/pi-vim-top-border/word-boundary-cache.ts +196 -0
- package/packages/status/README.md +398 -0
- package/packages/status/index.ts +1717 -0
- package/packages/status/package.json +33 -0
- package/packages/status/src/active-work.ts +235 -0
- package/packages/status/src/config.ts +218 -0
- package/packages/status/src/diagnostics.ts +238 -0
- package/packages/status/src/goal-block.ts +377 -0
- package/packages/status/src/goal.ts +170 -0
- package/packages/status/src/ledger.ts +101 -0
- package/packages/status/src/openspec.ts +394 -0
- package/packages/status/src/orchestration.ts +237 -0
- package/packages/status/src/publisher.ts +1294 -0
- package/packages/status/src/reducer.ts +190 -0
- package/packages/status/src/runtime-runs.ts +267 -0
- package/packages/status/src/state.ts +178 -0
- package/packages/status/src/title-activity.ts +224 -0
- package/packages/status/src/title-state.ts +218 -0
- package/packages/status/src/turn-cost.ts +63 -0
- package/packages/status/src/types.ts +228 -0
- package/src/deck-surface.ts +46 -0
- package/src/health.ts +227 -0
- package/src/index.ts +27 -0
- package/src/pi-status.ts +334 -0
package/package.json
CHANGED
|
@@ -1,6 +1,83 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aemonge-dev/pi-stardeck",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "A Vim- and tmux-inspired command deck for Pi",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package",
|
|
8
|
+
"pi-extension",
|
|
9
|
+
"vim",
|
|
10
|
+
"tmux",
|
|
11
|
+
"cockpit"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"files": [
|
|
15
|
+
"README.md",
|
|
16
|
+
"docs/architecture.md",
|
|
17
|
+
"docs/development.md",
|
|
18
|
+
"docs/assets/pi-stardeck.gif",
|
|
19
|
+
"demo/pi-stardeck.tape",
|
|
20
|
+
"licenses",
|
|
21
|
+
"src/*.ts",
|
|
22
|
+
"!src/local-load-probe.ts",
|
|
23
|
+
"packages/*/*.ts",
|
|
24
|
+
"packages/*/src/**/*.ts",
|
|
25
|
+
"!packages/*/src/**/*.test.ts",
|
|
26
|
+
"packages/*/package.json",
|
|
27
|
+
"packages/*/README.md",
|
|
28
|
+
"packages/*/LICENSE",
|
|
29
|
+
"packages/*/LOCAL-CHANGES.md"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=24.0.0"
|
|
33
|
+
},
|
|
34
|
+
"workspaces": [
|
|
35
|
+
"packages/*"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"format": "prettier --write . --ignore-path .prettierignore",
|
|
39
|
+
"format:check": "prettier --check . --ignore-path .prettierignore",
|
|
40
|
+
"lint": "biome check --config-path=biome.json packages/pi-vim-top-border && eslint packages scripts src test",
|
|
41
|
+
"typecheck": "tsc --noEmit && npm run typecheck --workspaces --if-present",
|
|
42
|
+
"test": "npm run test:tooling && npm run test:components",
|
|
43
|
+
"check": "npm run check:baseline && npm run check:package:load && npm run format:check && npm run lint && npm run typecheck && npm test && npm run check:composition && npm run check:openspec",
|
|
44
|
+
"check:baseline": "node scripts/check-baseline.mjs",
|
|
45
|
+
"check:package": "node scripts/check-package.mjs",
|
|
46
|
+
"check:package:load": "node scripts/check-package.mjs --load",
|
|
47
|
+
"check:composition": "node --import tsx/esm src/local-load-probe.ts",
|
|
48
|
+
"check:openspec": "node scripts/check-openspec.mjs",
|
|
49
|
+
"test:tooling": "node --import tsx/esm --test test/*.test.ts",
|
|
50
|
+
"test:status": "npm --prefix packages/status test",
|
|
51
|
+
"test:stardeck": "npm --prefix packages/pi-stardeck test",
|
|
52
|
+
"test:footer": "npm --prefix packages/footer test",
|
|
53
|
+
"test:vim": "npm --prefix packages/pi-vim-top-border test",
|
|
54
|
+
"test:components": "npm run test:status && npm run test:stardeck && npm run test:footer && npm run test:vim"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@earendil-works/pi-ai": "*",
|
|
58
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
59
|
+
"@earendil-works/pi-tui": "*",
|
|
60
|
+
"typebox": "*"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@biomejs/biome": "2.4.8",
|
|
64
|
+
"@earendil-works/pi-ai": "0.84.4",
|
|
65
|
+
"@earendil-works/pi-coding-agent": "0.84.4",
|
|
66
|
+
"@earendil-works/pi-tui": "0.84.4",
|
|
67
|
+
"@eslint/js": "9.39.5",
|
|
68
|
+
"@types/node": "24.13.3",
|
|
69
|
+
"eslint": "9.39.5",
|
|
70
|
+
"lefthook": "2.1.12",
|
|
71
|
+
"prettier": "3.9.6",
|
|
72
|
+
"tsx": "4.23.13",
|
|
73
|
+
"typebox": "1.3.25",
|
|
74
|
+
"typescript": "5.9.3",
|
|
75
|
+
"typescript-eslint": "8.69.0"
|
|
76
|
+
},
|
|
77
|
+
"pi": {
|
|
78
|
+
"image": "docs/assets/pi-stardeck.gif",
|
|
79
|
+
"extensions": [
|
|
80
|
+
"./src/index.ts"
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# footer
|
|
2
|
+
|
|
3
|
+
Footer telemetry producer for the [pi-stardeck](../../README.md) deck.
|
|
4
|
+
|
|
5
|
+
The deck renders the telemetry row; this module only feeds it. It walks the
|
|
6
|
+
session branch for assistant usage cost and publishes snapshots on
|
|
7
|
+
`pi-stardeck:footer-telemetry/v1` after the events that can change them
|
|
8
|
+
(session start, message end, model selection, compaction).
|
|
9
|
+
|
|
10
|
+
It renders nothing, reads no files, sets no timers, and talks to no network.
|
|
11
|
+
Quota gauges are a seam (`src/quota.ts`) with no implementations: register a
|
|
12
|
+
provider there and the deck row gains its gauge back without renderer
|
|
13
|
+
changes.
|
|
14
|
+
|
|
15
|
+
Composed by the root package; do not install separately.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
|
|
3
|
+
import { activeQuotaProviders } from './src/quota.ts';
|
|
4
|
+
import { FOOTER_TELEMETRY_CHANNEL } from './src/protocol.ts';
|
|
5
|
+
import { buildFooterTelemetryMessage, collectTotalCost } from './src/telemetry.ts';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Footer telemetry producer for the pi-stardeck deck. Renders nothing,
|
|
9
|
+
* configures nothing, polls nothing: it walks the session branch for usage
|
|
10
|
+
* cost and publishes deck-consumable snapshots on the footer-telemetry
|
|
11
|
+
* channel after the events that can change them.
|
|
12
|
+
*/
|
|
13
|
+
export default function footerTelemetry(pi: ExtensionAPI): void {
|
|
14
|
+
let context: ExtensionContext | undefined;
|
|
15
|
+
|
|
16
|
+
const publish = () => {
|
|
17
|
+
if (!context) return;
|
|
18
|
+
const quota = activeQuotaProviders()
|
|
19
|
+
.map((provider) => provider.snapshot())
|
|
20
|
+
.find((snapshot) => snapshot !== undefined);
|
|
21
|
+
pi.events.emit(
|
|
22
|
+
FOOTER_TELEMETRY_CHANNEL,
|
|
23
|
+
buildFooterTelemetryMessage(collectTotalCost(context), quota),
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
pi.on('session_start', (_event, ctx) => {
|
|
28
|
+
context = ctx;
|
|
29
|
+
publish();
|
|
30
|
+
});
|
|
31
|
+
pi.on('message_end', publish);
|
|
32
|
+
pi.on('model_select', publish);
|
|
33
|
+
pi.on('session_compact', publish);
|
|
34
|
+
pi.on('session_shutdown', () => {
|
|
35
|
+
context = undefined;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "footer",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Footer telemetry producer for the pi-stardeck deck",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"pi-package",
|
|
9
|
+
"pi-extension",
|
|
10
|
+
"telemetry"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=24.0.0"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}\" --ignore-path .prettierignore",
|
|
17
|
+
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}\" --ignore-path .prettierignore",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"test": "node --test --experimental-strip-types test/*.test.ts",
|
|
20
|
+
"check": "npm run format:check && npm run typecheck && npm test"
|
|
21
|
+
},
|
|
22
|
+
"pi": {
|
|
23
|
+
"extensions": [
|
|
24
|
+
"./index.ts"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@earendil-works/pi-ai": "*",
|
|
29
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
30
|
+
"typebox": "*"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const FOOTER_TELEMETRY_PROTOCOL_VERSION = 1 as const;
|
|
2
|
+
|
|
3
|
+
export const FOOTER_TELEMETRY_CHANNEL = 'pi-stardeck:footer-telemetry/v1';
|
|
4
|
+
|
|
5
|
+
export interface FooterTelemetryMessage {
|
|
6
|
+
protocol: typeof FOOTER_TELEMETRY_PROTOCOL_VERSION;
|
|
7
|
+
type: 'snapshot';
|
|
8
|
+
totalCost: number;
|
|
9
|
+
quotaPercent?: number;
|
|
10
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quota seam. The provider ships with no implementations: every quota source
|
|
3
|
+
* this cockpit had (Codex OAuth, Claude OAuth, z.ai coding plan) is gone with
|
|
4
|
+
* the previous footer module. A future slice registers a provider here
|
|
5
|
+
* and the deck row gains its gauge back without any renderer change.
|
|
6
|
+
*/
|
|
7
|
+
export interface QuotaSnapshot {
|
|
8
|
+
/** Whole-session quota usage, 0–100. */
|
|
9
|
+
percent: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface QuotaProvider {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
snapshot(): QuotaSnapshot | undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const quotaProviders: QuotaProvider[] = [];
|
|
18
|
+
|
|
19
|
+
export function registerQuotaProvider(provider: QuotaProvider): void {
|
|
20
|
+
if (quotaProviders.some(({ id }) => id === provider.id)) return;
|
|
21
|
+
quotaProviders.push(provider);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function activeQuotaProviders(): readonly QuotaProvider[] {
|
|
25
|
+
return quotaProviders;
|
|
26
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
|
|
3
|
+
import type { QuotaSnapshot } from './quota.ts';
|
|
4
|
+
import {
|
|
5
|
+
FOOTER_TELEMETRY_PROTOCOL_VERSION,
|
|
6
|
+
type FooterTelemetryMessage,
|
|
7
|
+
} from './protocol.ts';
|
|
8
|
+
|
|
9
|
+
function toCost(value: unknown): number {
|
|
10
|
+
return typeof value === 'number' && Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Sums assistant usage cost over a session branch. Non-message entries,
|
|
15
|
+
* non-assistant messages, and malformed usage contribute nothing, matching
|
|
16
|
+
* the walk the deck row has consumed since 0.1.0.
|
|
17
|
+
*/
|
|
18
|
+
export function totalCostFromEntries(entries: readonly unknown[]): number {
|
|
19
|
+
let totalCost = 0;
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
if (typeof entry !== 'object' || entry === null) continue;
|
|
22
|
+
const { type, message } = entry as { type?: unknown; message?: unknown };
|
|
23
|
+
if (type !== 'message') continue;
|
|
24
|
+
if (typeof message !== 'object' || message === null) continue;
|
|
25
|
+
const { role, usage } = message as { role?: unknown; usage?: unknown };
|
|
26
|
+
if (role !== 'assistant') continue;
|
|
27
|
+
if (typeof usage !== 'object' || usage === null) continue;
|
|
28
|
+
const cost = (usage as { cost?: unknown }).cost;
|
|
29
|
+
if (typeof cost !== 'object' || cost === null) continue;
|
|
30
|
+
totalCost += toCost((cost as { total?: unknown }).total);
|
|
31
|
+
}
|
|
32
|
+
return totalCost;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function collectTotalCost(ctx: ExtensionContext): number {
|
|
36
|
+
return totalCostFromEntries(ctx.sessionManager.getBranch());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function buildFooterTelemetryMessage(
|
|
40
|
+
totalCost: number,
|
|
41
|
+
quota?: QuotaSnapshot,
|
|
42
|
+
): FooterTelemetryMessage {
|
|
43
|
+
const cost =
|
|
44
|
+
typeof totalCost === 'number' && Number.isFinite(totalCost)
|
|
45
|
+
? Math.max(0, totalCost)
|
|
46
|
+
: 0;
|
|
47
|
+
const percent =
|
|
48
|
+
quota !== undefined &&
|
|
49
|
+
typeof quota.percent === 'number' &&
|
|
50
|
+
Number.isFinite(quota.percent)
|
|
51
|
+
? Math.min(100, Math.max(0, quota.percent))
|
|
52
|
+
: undefined;
|
|
53
|
+
return {
|
|
54
|
+
protocol: FOOTER_TELEMETRY_PROTOCOL_VERSION,
|
|
55
|
+
type: 'snapshot',
|
|
56
|
+
totalCost: cost,
|
|
57
|
+
...(percent !== undefined ? { quotaPercent: percent } : {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# pi-stardeck
|
|
2
|
+
|
|
3
|
+
A dependency-free Pi extension that coordinates the prompt information architecture
|
|
4
|
+
through Pi's supported widget and event APIs.
|
|
5
|
+
|
|
6
|
+
```text
|
|
7
|
+
─ › ─────────────────────────────────────────────────────────────────────
|
|
8
|
+
( 3 › 2 · 00:05'12) working ⟩ checking Stabilize runtime and focus · step 3/5 task 1/2 › stps 3/5
|
|
9
|
+
Stabilize runtime and focus in the cockpit
|
|
10
|
+
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
|
|
11
|
+
[] ~/projects/example main ⟩ clean
|
|
12
|
+
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
|
|
13
|
+
Opus › high ⟩ 42% › 2 10% › $0.10 ⟩ 12% › 375M › 3/3
|
|
14
|
+
─ ─────────────────────────────────────────────────────────────── ‹ ─
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The preferred editor-deck surface keeps two stable semantic rows. The first is a
|
|
18
|
+
left-packed operational strip: runtime capsule, lifecycle, activity, controlled next
|
|
19
|
+
action, and Task/Step progress. The second is a full-width explicit selection canvas
|
|
20
|
+
that wraps to at most two lines. Every slot remains present; zero, idle, unavailable,
|
|
21
|
+
and unselected states are dim instead of appearing or disappearing. Long content may
|
|
22
|
+
wrap responsively, but runtime state never changes the deck topology. Decorative rails
|
|
23
|
+
remain slim and contain no telemetry.
|
|
24
|
+
|
|
25
|
+
The editor deck uses blue for the active runtime capsule, the bold lifecycle anchor,
|
|
26
|
+
and regular-weight explicit selection. Ordinary active activity remains neutral, except
|
|
27
|
+
that `recovering` and its ordinary suggestion are blue together while only the lifecycle
|
|
28
|
+
is bold. Other ordinary suggestions remain green. Incomplete Task/Step progress is
|
|
29
|
+
neutral until each counter completes and stays right-aligned without a major boundary.
|
|
30
|
+
The lifecycle/activity boundary and rails remain purple; minor separators are dim.
|
|
31
|
+
Human-input and validation cues stay yellow, blockers stay red, and every inactive value
|
|
32
|
+
stays dim.
|
|
33
|
+
|
|
34
|
+
The legacy above-editor widget owns only current work identity and preserves the
|
|
35
|
+
` ready ⟩ no focused task` idle row when no work is selected. The leading icon is
|
|
36
|
+
runtime-authoritative: `` while Pi is busy in an active lifecycle and `` while
|
|
37
|
+
settled, waiting, blocked, listening, or aborted. Internal lifecycle states render as
|
|
38
|
+
short readable verbs. The Ramona loop remains exact: `understand`, `work`, `assure`,
|
|
39
|
+
`learn`, and `answer`. Runtime boundaries are `wait`, `blocked`, `ready`, or `stopped`.
|
|
40
|
+
The title has four styled groups: lifecycle, a structured active ancestry, focus, and
|
|
41
|
+
optional focus detail. Each activity-path segment receives the blue minor `›`; the
|
|
42
|
+
violet-pink major `⟩` divides runtime ancestry from the green narrative focus. Human
|
|
43
|
+
prompt text is never converted into focus automatically. OpenSpec may add its exact Task
|
|
44
|
+
after another green `›`. Focus selection prefers an exact OpenSpec Task, then Goal, then
|
|
45
|
+
Session Work. Active turns without a selection use the explicit `OpenSpec › no focus`,
|
|
46
|
+
`no OpenSpec`, or `no focus` state supplied by the status producer; `Direct response` is
|
|
47
|
+
never presented as a focus. Empty states are dim while real focus remains bold semantic
|
|
48
|
+
green.
|
|
49
|
+
|
|
50
|
+
Wide rows render lifecycle → active ancestry → optional focus → optional focus detail,
|
|
51
|
+
for example `assure › checking quality › assuring tests ⟩ Implement global flow`. The
|
|
52
|
+
renderer generates every separator. Empty or rejected paths leave no separator or
|
|
53
|
+
placeholder whitespace. As width falls it tries full labels, compact labels, a collapsed
|
|
54
|
+
`… › deepest` ancestry, and finally compact lifecycle plus the deepest active segment.
|
|
55
|
+
Narrative focus disappears before meaningful active ancestry. Terminal display-cell
|
|
56
|
+
measurement and final truncation remain Unicode- and Nerd-Font-aware.
|
|
57
|
+
|
|
58
|
+
Runtime anchor and lifecycle are bold blue `#076678`; activity ancestry and its minor
|
|
59
|
+
`›` are regular blue; the major `⟩` is purple; focus is bold semantic success green; and
|
|
60
|
+
optional focus hierarchy is regular success green. Dim is reserved for the two empty
|
|
61
|
+
focus fallbacks; muted, text, cyan, yellow, and gray are never used in the title.
|
|
62
|
+
|
|
63
|
+
The extension hides Pi's separate built-in `Working...` loader row with
|
|
64
|
+
`ctx.ui.setWorkingVisible(false)`, including its vertical space. The header therefore
|
|
65
|
+
uses its complete width for lifecycle, bounded activity, and focus. Runtime age is
|
|
66
|
+
published for the bottom prompt rail as an eight-cell chronometer measuring time since
|
|
67
|
+
the most recent observed foreground provider, assistant, or tool event. It stays
|
|
68
|
+
fixed-width as it advances: `00:00'00`, `00:00'45`, `00:12'30`, or `12:34'56`; hour/day
|
|
69
|
+
fallbacks also remain eight cells. Active timing uses the high-contrast deep-cyan
|
|
70
|
+
`accent` color; settled states display dim `00:00'00` rather than repeating the header's
|
|
71
|
+
`wait`, `ready`, or other lifecycle word. A session-scoped ticker publishes only when
|
|
72
|
+
the formatted chronometer changes and is cleared on shutdown. No message content,
|
|
73
|
+
thinking, reasoning, prompts, tool arguments, or child-agent output enters the widget.
|
|
74
|
+
|
|
75
|
+
The additive header protocol projects selection independently from live work:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
selection: {
|
|
79
|
+
source: 'openspec' | 'goal' | 'session-work';
|
|
80
|
+
titles: string[]; // one or two bounded, normalized titles
|
|
81
|
+
color: HeaderColor;
|
|
82
|
+
} | null;
|
|
83
|
+
suggestion: HeaderSuggestion | null; // controlled enum, never generated prose
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Selection priority remains exact OpenSpec Task, Goal, then Session Work. Older
|
|
87
|
+
protocol-1 events may use sanitized non-placeholder work titles as a legacy selection;
|
|
88
|
+
explicit malformed selection is rejected rather than borrowing a fallback. The
|
|
89
|
+
suggestion vocabulary is producer-owned, appears only in waiting, blocked, approval,
|
|
90
|
+
or aborted states, and is bounded to reporting cues such as
|
|
91
|
+
`requesting validation or redirection`, `awaiting continuation`, and
|
|
92
|
+
`requesting redirection`. While work is active, the same slot reports the current
|
|
93
|
+
WHAT: the focused task title with live step progress.
|
|
94
|
+
|
|
95
|
+
The preferred activity contract is a structured path that never stores separators:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
work.activityPath?: Array<{
|
|
99
|
+
id: string; // lowercase letters, digits, and hyphens; at most 48 code points
|
|
100
|
+
label: string; // one line; normalized to at most 48 code points
|
|
101
|
+
compact?: string; // normalized to at most 24 code points
|
|
102
|
+
current?: number;
|
|
103
|
+
total?: number;
|
|
104
|
+
}>; // at most six unique segments; lifecycle is not duplicated here
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
An explicit valid array, including `[]`, takes precedence. A malformed array is rejected
|
|
108
|
+
as a whole and falls back to the legacy allowlisted `work.activity` field. Legacy
|
|
109
|
+
activity is normalized to one internal segment, preserving existing producers until they
|
|
110
|
+
migrate. Progress is bounded to `0..9,999` and appended as `current/total` only when
|
|
111
|
+
both values are valid.
|
|
112
|
+
|
|
113
|
+
The consumer removes ANSI escapes and control characters, normalizes whitespace, rejects
|
|
114
|
+
empty labels and malformed or duplicate IDs, and fails soft. Semantic safety remains a
|
|
115
|
+
producer obligation: labels must be bounded display vocabulary, never prompts, commands,
|
|
116
|
+
paths, secrets, generated paragraphs, or tool output. The consumer cannot infer those
|
|
117
|
+
semantics from arbitrary text. The future global-flow producer selects one primary
|
|
118
|
+
active path; completed, skipped, sibling, and parallel aggregation remain out of scope.
|
|
119
|
+
|
|
120
|
+
The legacy `work.activity` enum remains temporarily supported. Its renderer-owned labels
|
|
121
|
+
convert operation starts such as `inspection` and `mutation` into
|
|
122
|
+
`inspecting project context` and `updating selected files`. Their bounded completion
|
|
123
|
+
transitions render as `finalizing current request`, `reviewing applied changes`, or
|
|
124
|
+
`reviewing check results`; unknown kinds and raw activity text remain omitted.
|
|
125
|
+
|
|
126
|
+
The extension refreshes bounded local Git branch and status data at session start, after
|
|
127
|
+
successful mutating tools, and when the agent settles. Footer row one keeps project
|
|
128
|
+
identity on the left and work scope on the right. The bold PWD is shown in full relative
|
|
129
|
+
to home (`/home/aemonge/projects/example` → `~/projects/example`) and yields ANSI-safely
|
|
130
|
+
only when the terminal cannot preserve both aligned groups. Git branch and state follow
|
|
131
|
+
a blue minor `›`. Outside a repository, one dim responsive `› (no Git)` state fills
|
|
132
|
+
the otherwise missing project slot and yields before core project/work information on
|
|
133
|
+
narrow terminals. Slash-prefixed Git branches collapse the prefix to its initial and cap
|
|
134
|
+
display at 30 cells with `...` (`feat/persist-versioned-prompts-in-duckdb` →
|
|
135
|
+
`f/persist-versioned-prompts...`). The right side shows compact OpenSpec Plan/Task
|
|
136
|
+
progress, or the simple Goal, Focus, or `no OpenSpec` fallback when no OpenSpec
|
|
137
|
+
portfolio is available. The footer reserves a four-cell visual split between alignment
|
|
138
|
+
groups. OpenSpec uses icons instead of repeated labels: task-list `` for Plans and
|
|
139
|
+
notebook `` for Tasks, with minor `›` between them. Their Nerd Font glyphs exactly
|
|
140
|
+
match Starship: ``, ``, ``, ``, and ``. When `DEVBOX_PROJECT_ROOT` is present, the
|
|
141
|
+
PWD begins `[] ~/project`; the boxed Devbox marker precedes the folder icon without
|
|
142
|
+
exposing the variable's value. The top prompt rail may prepend MCP availability to work
|
|
143
|
+
progress as ` healthy/total ⟩ Plans › Tasks`. MCP status comes only from the live
|
|
144
|
+
`pi-mcp-adapter/status/v1` feed; lazy cached or not-yet-connected servers remain
|
|
145
|
+
available, while failed or authentication-blocked servers reduce the numerator. This is
|
|
146
|
+
availability telemetry, not a tool-call counter. Before the first valid snapshot, or
|
|
147
|
+
when status is empty or malformed, the segment remains visible as dim ` 0/0`. The
|
|
148
|
+
entire segment yields before right-aligned work progress is truncated. Neovim LSP
|
|
149
|
+
inventory is intentionally neither collected nor shown because it does not establish an
|
|
150
|
+
agent-usable LSP capability. Plugin and extension inventory is not shown without a
|
|
151
|
+
trustworthy runtime health source. Adjacent left and right segments use the same Gruvbox
|
|
152
|
+
colors as the configured Starship prompts: blue `#076678`, green `#79740e`, bright green
|
|
153
|
+
`#689d6a`, aqua `#427b58`, yellow `#b57614`, orange `#af3a03`, major-separator Gruvbox
|
|
154
|
+
purple `#b16286`, blue-violet `#5c5ca8`, and red `#9d0006`. Project, Git, work-scope,
|
|
155
|
+
model, quota, and local telemetry avoid generic gray; only missing Git and the runtime
|
|
156
|
+
chronometer are deliberately dim. PWD and model identity share the same blue anchor
|
|
157
|
+
color; reasoning retains its own level color. The single footer row keeps PWD and Git on
|
|
158
|
+
the left and model, reasoning, and quota on the right. Other bounded widget snapshots
|
|
159
|
+
feed the prompt rails: mode alone on the left and optional MCP availability plus work
|
|
160
|
+
progress on the right of the top rail; chronometer left and context, compactions,
|
|
161
|
+
memory, CPU, and cost right on the bottom rail. A major separator distinguishes model
|
|
162
|
+
context from process resources. On wide terminals the footer ends with the dedicated
|
|
163
|
+
five-cell avatar tail `⟨ `: a purple left-pointing boundary, violet ladybug, and one
|
|
164
|
+
blank cell to her right. Each rail aligns its own groups independently, so the header is
|
|
165
|
+
never padded for cross-row alignment. CPU is normalized against Pi's available logical
|
|
166
|
+
CPU capacity and capped at 100%, so the number reads as whole-machine share rather than
|
|
167
|
+
one-core units. CPU and RAM are blue normally and independently turn yellow only for
|
|
168
|
+
sustained CPU pressure above 80% or unusually high session memory; one resource warning
|
|
169
|
+
does not recolor its neighbors. Pink and red are never used in these three telemetry
|
|
170
|
+
groups. In the title, lifecycle and bounded activity are blue; the major boundary is
|
|
171
|
+
purple; real focus and optional Task hierarchy are green; empty focus fallbacks and the
|
|
172
|
+
footer runtime age/state are dim. Persistent cache-hit presentation is disabled and no
|
|
173
|
+
module keeps cache accounting since the footer fork's replacement. Compaction count is hidden at zero.
|
|
174
|
+
The first deck row always begins with the compact runtime capsule
|
|
175
|
+
`( children › subagents · 00:00'00)`. Each zero count and the settled timer remain
|
|
176
|
+
dim in place; a live count or timer changes color without moving neighboring content.
|
|
177
|
+
The active-run aggregate no longer appears in the lower resource row. Final
|
|
178
|
+
narrow-width fitting remains ANSI-aware and removes exposed trailing separators. The
|
|
179
|
+
renderer never shows the main Pi process, historical worker totals, completed-file
|
|
180
|
+
progress, queued subagent placeholders, or runtime identity and output fields.
|
|
181
|
+
Quota is reduced to the most constrained provider-reported percentage with no weekly
|
|
182
|
+
analysis. Cost is Pi's native estimated session cost in USD, rendered without trailing
|
|
183
|
+
fractional zeros; its preceding `⟩` retains the shared purple major-boundary color while
|
|
184
|
+
the value remains blue. No local daily aggregation or currency conversion is performed.
|
|
185
|
+
No center group or gauges are used. The deck's widget protocol (historically the
|
|
186
|
+
Fancy Footer protocol, `pi-fancy-footer:widget`) accepts these native theme tokens. The narrative lifecycle header begins with
|
|
187
|
+
its active/passive terminal icon; the prompt border keeps only its right-aligned vi-mode
|
|
188
|
+
glyph. Concurrent Git refreshes coalesce without polling.
|
|
189
|
+
|
|
190
|
+
Agent-environment, context, CPU, RAM, and compaction widgets use the same widget
|
|
191
|
+
protocol, including producer-requested bold text for the PWD. Every three seconds on
|
|
192
|
+
Linux, the extension discovers only Pi descendants through bounded
|
|
193
|
+
`/proc/<pid>/task/<pid>/children` reads, then reads their `stat` and `status` records
|
|
194
|
+
without shelling out or retaining process names, arguments, or paths. Memory sums
|
|
195
|
+
private anonymous resident pages (`RssAnon`) instead of counting shared file-backed
|
|
196
|
+
pages once per process. Individual status-read failures fall back to that process's RSS;
|
|
197
|
+
unavailable `/proc` falls back honestly to current-process CPU and RSS. Sampling,
|
|
198
|
+
smoothing, publication, timers, and resource widgets are session-scoped and cleaned up
|
|
199
|
+
on shutdown. Session-cost collection lives in `packages/footer`; cache and quota
|
|
200
|
+
collection were removed with the fork, leaving quota as an empty seam.
|
|
201
|
+
When the footer reports quota windows, the compact telemetry row paints icon-native
|
|
202
|
+
tiles in cost, long-window, coding-window order — ` $5.21 ⟩ 12% ⟩ 62% › 1:45`.
|
|
203
|
+
The flame is the coding window's permanent icon and always carries the clock
|
|
204
|
+
countdown; 80% used turns a tile's accent color into a warning. Without windows the
|
|
205
|
+
slot degrades to the single footer-reported percent, and without telemetry it stays a
|
|
206
|
+
dim dash.
|
|
207
|
+
Unavailable temperature and unsupported environment claims are hidden; environment
|
|
208
|
+
classification defaults honestly to `host` unless an explicit supported marker exists.
|
|
209
|
+
|
|
210
|
+
## Test
|
|
211
|
+
|
|
212
|
+
```sh
|
|
213
|
+
npm install
|
|
214
|
+
npm run check
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Remove
|
|
218
|
+
|
|
219
|
+
Delete `./packages/pi-stardeck` from `.pi/agent/settings.json`, remove this
|
|
220
|
+
directory, and run `/reload`.
|