@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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +240 -2
  3. package/demo/pi-stardeck.tape +59 -0
  4. package/docs/architecture.md +106 -0
  5. package/docs/assets/pi-stardeck.gif +0 -0
  6. package/docs/development.md +128 -0
  7. package/licenses/README.md +19 -0
  8. package/licenses/pi-vim-LICENSE +21 -0
  9. package/package.json +81 -4
  10. package/packages/footer/README.md +15 -0
  11. package/packages/footer/index.ts +37 -0
  12. package/packages/footer/package.json +32 -0
  13. package/packages/footer/src/protocol.ts +10 -0
  14. package/packages/footer/src/quota.ts +26 -0
  15. package/packages/footer/src/telemetry.ts +59 -0
  16. package/packages/pi-stardeck/README.md +220 -0
  17. package/packages/pi-stardeck/index.ts +691 -0
  18. package/packages/pi-stardeck/package.json +32 -0
  19. package/packages/pi-stardeck/src/activity-age.ts +184 -0
  20. package/packages/pi-stardeck/src/capabilities.ts +105 -0
  21. package/packages/pi-stardeck/src/deck.ts +807 -0
  22. package/packages/pi-stardeck/src/gauge.ts +1323 -0
  23. package/packages/pi-stardeck/src/git-refresh.ts +5 -0
  24. package/packages/pi-stardeck/src/process-resources.ts +428 -0
  25. package/packages/pi-stardeck/src/title-state.ts +195 -0
  26. package/packages/pi-stardeck/src/title.ts +443 -0
  27. package/packages/pi-vim-top-border/LICENSE +21 -0
  28. package/packages/pi-vim-top-border/LOCAL-CHANGES.md +71 -0
  29. package/packages/pi-vim-top-border/README.md +577 -0
  30. package/packages/pi-vim-top-border/clipboard-mirror.ts +451 -0
  31. package/packages/pi-vim-top-border/clipboard-policy.ts +23 -0
  32. package/packages/pi-vim-top-border/cursor-shape.ts +131 -0
  33. package/packages/pi-vim-top-border/index.ts +4195 -0
  34. package/packages/pi-vim-top-border/input-keys.ts +49 -0
  35. package/packages/pi-vim-top-border/mode-change-command.ts +120 -0
  36. package/packages/pi-vim-top-border/mode-colors.ts +97 -0
  37. package/packages/pi-vim-top-border/mode-label.ts +62 -0
  38. package/packages/pi-vim-top-border/motions.ts +297 -0
  39. package/packages/pi-vim-top-border/package.json +47 -0
  40. package/packages/pi-vim-top-border/prompt-external-editor.ts +152 -0
  41. package/packages/pi-vim-top-border/prompt-rail.ts +334 -0
  42. package/packages/pi-vim-top-border/prompt-status.ts +131 -0
  43. package/packages/pi-vim-top-border/settings.ts +287 -0
  44. package/packages/pi-vim-top-border/text-objects.ts +389 -0
  45. package/packages/pi-vim-top-border/types.ts +51 -0
  46. package/packages/pi-vim-top-border/visual.ts +65 -0
  47. package/packages/pi-vim-top-border/word-boundary-cache.ts +196 -0
  48. package/packages/status/README.md +398 -0
  49. package/packages/status/index.ts +1717 -0
  50. package/packages/status/package.json +33 -0
  51. package/packages/status/src/active-work.ts +235 -0
  52. package/packages/status/src/config.ts +218 -0
  53. package/packages/status/src/diagnostics.ts +238 -0
  54. package/packages/status/src/goal-block.ts +377 -0
  55. package/packages/status/src/goal.ts +170 -0
  56. package/packages/status/src/ledger.ts +101 -0
  57. package/packages/status/src/openspec.ts +394 -0
  58. package/packages/status/src/orchestration.ts +237 -0
  59. package/packages/status/src/publisher.ts +1294 -0
  60. package/packages/status/src/reducer.ts +190 -0
  61. package/packages/status/src/runtime-runs.ts +267 -0
  62. package/packages/status/src/state.ts +178 -0
  63. package/packages/status/src/title-activity.ts +224 -0
  64. package/packages/status/src/title-state.ts +218 -0
  65. package/packages/status/src/turn-cost.ts +63 -0
  66. package/packages/status/src/types.ts +228 -0
  67. package/src/deck-surface.ts +46 -0
  68. package/src/health.ts +227 -0
  69. package/src/index.ts +27 -0
  70. package/src/pi-status.ts +334 -0
@@ -0,0 +1,398 @@
1
+ # status
2
+
3
+ A small local [Pi](https://pi.dev) extension that gathers project status and publishes
4
+ structured widgets to `pi-fancy-footer`. It never renders a footer, status block, or
5
+ editor widget itself.
6
+
7
+ ## Architecture
8
+
9
+ ```text
10
+ Pi tool lifecycle + session/Goal metadata + OpenSpec CLI + aggregate JSON state
11
+ │
12
+ ▼
13
+ normalized immutable state
14
+ │
15
+ ▼
16
+ bounded header events + pi-fancy-footer snapshots
17
+ ```
18
+
19
+ The five stable widget IDs are:
20
+
21
+ - `galactica.work`
22
+ - `galactica.openspec-progress`
23
+ - `galactica.openspec`
24
+ - `galactica.diagnostics`
25
+ - `galactica.orchestration`
26
+
27
+ `pi-fancy-footer` remains the only visible footer renderer and supplies native model,
28
+ thinking, context, quota, location, Git, pull-request, and diff information.
29
+
30
+ ## Installation
31
+
32
+ This repository loads the package from Pi settings:
33
+
34
+ ```json
35
+ {
36
+ "packages": ["npm:pi-fancy-footer", "./packages/status"]
37
+ }
38
+ ```
39
+
40
+ The relative package path is resolved from `~/.pi/agent/settings.json`, so the managed
41
+ source lives at `.pi/agent/packages/status` in Galactica.
42
+
43
+ ## OpenSpec detection
44
+
45
+ The provider uses the installed OpenSpec CLI and reads only each listed change's bounded
46
+ `.openspec.yaml` schema marker to distinguish Ramona Ideas from Plans:
47
+
48
+ 1. `openspec list --json` finds changes, aggregate progress, state, and the OpenSpec
49
+ root. The always-visible footer totals completed/total Plans and Tasks. Changes
50
+ marked `schema: ramona-idea` are excluded from Plan and Task totals; generic OpenSpec
51
+ changes count as Plans.
52
+ 2. An explicitly selected change is matched by exact ID. With no explicit selection,
53
+ focused collection stops; repository recency never chooses a change.
54
+ 3. `openspec show <change> --json` supplies the concise title.
55
+ 4. `openspec instructions apply --change <change> --json` supplies task state and the
56
+ schema-resolved task artifact path.
57
+ 5. The task artifact is read once per refresh to recover labels such as `3.8` and kinds
58
+ such as Story, Spike, Lab, or Hack.
59
+
60
+ The session focus takes precedence over configured `openspec.change`, which takes
61
+ precedence over `PI_OPENSPEC_CHANGE`. Configuration and environment values can select a
62
+ change for collection/debugging, but they do not create session task focus or expose an
63
+ active OpenSpec footer by themselves.
64
+
65
+ ## Session focus and terminal title
66
+
67
+ A Pi session has zero or one explicit OpenSpec task focus, optional read-only Pi Goal
68
+ state, and an independent ephemeral Session Work state. Display priority is OpenSpec
69
+ Task → Goal → Session Work → safe direct fallback. OpenSpec focus clears active Session
70
+ Work when acquired, preventing hidden or stale work from resurfacing later.
71
+
72
+ Session Work is stored only as versioned custom entries on the active Pi session branch;
73
+ it never creates a repository status file. It supports this lifecycle:
74
+
75
+ ```text
76
+ /work
77
+ /work set Implement session metadata
78
+ /work validate
79
+ /work clear
80
+ ```
81
+
82
+ When no OpenSpec portfolio is available, the matching row-one footer fallback is the
83
+ compact `▶ Focus` or `◇ Validate`; an active Goal uses `󰊕 Goal`, and an entirely empty
84
+ project uses ` no OpenSpec`. The detailed Session Work intent remains in the header
85
+ instead of being duplicated in the footer. Active and validation states also replace the
86
+ fallback terminal title. Reload, resume, and tree navigation restore the newest valid
87
+ branch entry. The agent-callable `work_focus` tool exposes the same `status`, `set`,
88
+ `validate`, and `clear` actions. The agent-callable `openspec_focus` tool likewise
89
+ shares the command's `status`, safeguarded `set`, deliberate `replace`, and persistent
90
+ `clear` transitions.
91
+
92
+ Global routing belongs to native Ramona policy rather than this status package. This
93
+ package does not inject parent routing prompts, block parent tools, create Session Work
94
+ for a global route, or promote a raw request into OpenSpec focus. It continues to own
95
+ branch-local OpenSpec and Session Work state, commands, restoration, titles, and footer
96
+ publication.
97
+
98
+ The header projection uses the rich lifecycle `understanding`, `working`, `waiting`,
99
+ `assuring`, `learning`, `answering`, `blocked`, `listening`, or `aborted`. It separately
100
+ publishes one explicit selection with OpenSpec Task → Goal → Session Work precedence,
101
+ or `null` when no work is selected. A controlled suggestion enum communicates only
102
+ Human-facing reporting cues such as `requesting-validation`,
103
+ `awaiting-continuation`, or `requesting-redirection`; it never carries generated
104
+ prose. Direct tool
105
+ categories, read-only Pi Goal state, and known Taskflow phases move the lifecycle
106
+ without exposing arguments or raw node prose. Goal state is restored from Pi's canonical
107
+ `goal-state` session entries without enabling Goal RPC. An active Goal renders bounded
108
+ automatic-turn progress from `pi-goal.json`; paused, blocked, limited, external-wait,
109
+ and complete states render controlled labels. Complete projects as
110
+ `awaiting Human validation` rather than Human acceptance. Raw Goal objectives, wait
111
+ reasons, completion summaries, paths, and Goal IDs never enter the visible header. While
112
+ a Goal exists, a session-scoped 500 ms in-memory branch monitor catches idle
113
+ `/goal pause`, clear, and menu transitions; it performs no filesystem, process,
114
+ provider, or network work and stops when Goal state is cleared. A stopped Goal remains
115
+ visible while Pi is idle so its terminal reason is honest, but the next ordinary
116
+ non-Goal turn automatically returns the title to current Ramona, OpenSpec, Session Work,
117
+ or direct activity. This display transition never mutates Goal or focus state; a later
118
+ Goal resume automatically reclaims the title. During a Taskflow tool run, structured
119
+ streaming state temporarily replaces the normal focus with the running top-level phase
120
+ ID, such as `working ⟩ work-fragment`; promoted child IDs and generated output remain
121
+ hidden. Visible assistant text moves it to `answering`; settling with selected work
122
+ moves it to `waiting`; no selected work returns to `listening`. Direct tool starts
123
+ describe the bounded operation, while tool completion immediately moves to a bounded
124
+ transition: read-only inspection → `finalizing current request`, mutation →
125
+ `reviewing applied changes`, and verification → `reviewing check results`. Parallel
126
+ tools keep the newest remaining operation visible until all complete; any failed
127
+ operation transitions to bounded recovery. This prevents a completed command from
128
+ looking permanently active while Pi prepares its next action. A Ctrl-C assistant stop
129
+ reason moves it to `aborted` and remains visible until the next turn resets it to
130
+ `understanding`. Legacy work titles still preserve the existing explicit fallback states for compatible
131
+ consumers. The additive selection field contains only an exact OpenSpec Task, Goal, or
132
+ Session Work; otherwise it is `null`, so the deck renders a stable dim `󰓾 —`. `Direct
133
+ response` is never presented as selection and Human prompt text is never copied into it.
134
+
135
+ The header protocol also carries a bounded `activeRuns` aggregate. Bash is active from
136
+ Pi's `tool_execution_start` through `tool_execution_end` and contributes only one child
137
+ run. Subagent progress is read structurally from `pi-subagent` result details: a call is
138
+ active only after `sawAgentStart`, before `sawAgentSettled`, and while its `exitCode`
139
+ remains `-1`. Placeholder headline totals are never used because they include queued
140
+ calls. The aggregate contains only child-run and subagent integers; prompts, agent names,
141
+ PIDs, arguments, and child output are discarded. The main Pi process is intentionally
142
+ absent. The deck always renders both counts in its first-row runtime capsule and changes
143
+ zero/live color instead of publishing state-dependent display visibility.
144
+
145
+ The generic outer request has no `change` or `task` arguments and therefore creates no
146
+ durable focus side effect here. Existing specialized Taskflow calls may still acquire
147
+ exact OpenSpec focus from non-placeholder `change` and `task` arguments. Legacy `none`
148
+ placeholders are ignored. Human slash commands remain Human-owned manual controls; the
149
+ agent tool may act only on explicit Human direction or an exact declared Task handoff,
150
+ never inferred material Plan selection or Human validation.
151
+
152
+ A Pi session acquires OpenSpec focus before an OpenSpec-aware Taskflow tool run executes
153
+ when its input contains string `change` and `task` arguments:
154
+
155
+ ```json
156
+ {
157
+ "action": "run",
158
+ "args": {
159
+ "change": "show-active-openspec-in-pi-title",
160
+ "task": "1.1"
161
+ }
162
+ }
163
+ ```
164
+
165
+ Taskflow slash-command invocations do not pass through Pi's `tool_call` hook. Set focus
166
+ manually before those runs:
167
+
168
+ ```text
169
+ /openspec-focus
170
+ /openspec-focus show-active-openspec-in-pi-title 1.1
171
+ /openspec-focus replace another-change 2.1
172
+ /openspec-focus clear
173
+ ```
174
+
175
+ Aggregate portfolio progress is independent of focus and remains visible as:
176
+
177
+ ```text
178
+  7/15 ›  36/45
179
+ ```
180
+
181
+ It occupies the right side of footer row one, opposite the full PWD and Git state. It
182
+ updates from `openspec list --json`; task-list `` replaces `Plans` and notebook ``
183
+ replaces `Tasks`. If no OpenSpec portfolio exists, the slot prefers compact active scope
184
+ (`󰊕 Goal`, then `▶ Focus` or `◇ Validate`) before falling back to dim ` no OpenSpec`.
185
+ Focused-change metadata remains separate and does not replace these totals. The
186
+ no-argument form reports the current focus without changing it. The matching tool calls
187
+ are:
188
+
189
+ ```json
190
+ {"action":"status"}
191
+ {"action":"set","change":"show-active-openspec-in-pi-title","task":"1.1"}
192
+ {"action":"replace","change":"another-change","task":"2.1"}
193
+ {"action":"clear"}
194
+ ```
195
+
196
+ `set` acquires empty focus but refuses a different active Task. `replace` makes that
197
+ switch deliberate. Both acquisition paths clear conflicting Session Work before the
198
+ OpenSpec branch entry is appended. Missing or empty identifiers fail without writing an
199
+ entry, while `clear` appends the branch-local no-focus state.
200
+
201
+ A conflicting Taskflow tool run is blocked until focus is cleared or deliberately
202
+ replaced. Focus is stored as a versioned custom entry on the active Pi session branch,
203
+ so reload/resume restores the newest valid task or clear entry. Unrelated turns and
204
+ Taskflow success, failure, blocking, pause, or approval states do not clear focus or
205
+ modify OpenSpec task checkboxes.
206
+
207
+ The extension emits a fallback title immediately at session start, then updates it after
208
+ asynchronous OpenSpec metadata resolves:
209
+
210
+ - focused metadata: `π  {change} › {task-id} {short task}`
211
+ - focused IDs only: `π  {change} › {task-id}`
212
+ - Session Work validation: `π  Validate › {intent}`
213
+ - active Session Work: `π  {intent}`
214
+ - no focus: `π  {session name || repository basename || cwd basename}`
215
+
216
+ Control characters and repeated whitespace are removed, titles are bounded, and an
217
+ unchanged refresh does not emit a duplicate title update. Without explicit focus, the
218
+ aggregate footer still reports Plan and Task totals without selecting a change. The
219
+ legacy focused-status producer also gives quiet state feedback:
220
+
221
+ - actionable task work exists: dim `○ OpenSpec · no focus`
222
+ - no actionable task work exists: dim `○ OpenSpec · clear`
223
+ - no `openspec/` project exists: row one shows Goal or Focus when active, otherwise dim
224
+ ` no OpenSpec`
225
+ - OpenSpec status cannot be read: the last aggregate remains while focused status
226
+ reports unavailable
227
+
228
+ While focused, it prioritizes the active action over duplicated metadata:
229
+
230
+ ```text
231
+ [-] 1/2 ▶ 1.1 Create root-level `greeting.txt` with exact contents
232
+ ```
233
+
234
+ The ratio replaces a redundant percentage, the focused task replaces the change title,
235
+ and regular output is published without a producer-side character cap. Fancy Footer
236
+ therefore truncates only when the allocated terminal width is genuinely exhausted.
237
+
238
+ ## Diagnostics sources
239
+
240
+ The first existing aggregate JSON file is used:
241
+
242
+ - `.pi/status/diagnostics.json`
243
+ - `.pi/diagnostics.json`
244
+ - `.galactica/diagnostics.json`
245
+ - `.taskflow/diagnostics.json`
246
+ - `status/diagnostics.json`
247
+
248
+ A configured `sourceFile` takes precedence. Accepted aggregate fields include `errors`,
249
+ `blockers`, `warnings`, `diagnostics`/`findings`, `tests`, `typecheck`, and `updatedAt`;
250
+ nested `summary` and `checks` objects are also accepted. Filenames and word counts are
251
+ never displayed.
252
+
253
+ An optional diagnostics command must be expressed without a shell:
254
+
255
+ ```json
256
+ {
257
+ "diagnostics": {
258
+ "command": ["my-status-command", "--json"],
259
+ "runAutomatically": false
260
+ }
261
+ }
262
+ ```
263
+
264
+ The equivalent `{ "executable": "...", "args": ["..."] }` form is accepted. It runs only
265
+ on `/galactica-status-refresh`, or at the conservative configured interval when
266
+ `runAutomatically` is true. Pi's argument-array process API enforces the timeout and
267
+ terminates timed-out children.
268
+
269
+ ## Orchestration sources
270
+
271
+ The first existing JSON state file is used:
272
+
273
+ - `.pi/status/orchestration.json`
274
+ - `.pi/orchestration.json`
275
+ - `.taskflow/state.json`
276
+ - `.taskflow/run-state.json`
277
+ - `.galactica/orchestration.json`
278
+
279
+ A configured `orchestration.stateFile` takes precedence. Useful fields include `phase`,
280
+ `state`/`status`, `activeWorkers`, `workers`/`agents`, `currentTask`, `openspecTaskId`,
281
+ `startedAt`, `elapsedMs`, and `updatedAt`. While orchestration is running, known phase
282
+ or node vocabulary is classified into an allowlisted title activity—understanding,
283
+ inspection, synthesis, planning, skill proposal, mutation, implementation, change
284
+ review, regression, verification, result review, recovery, waiting reason, or abort—and
285
+ attached to the current OpenSpec, Goal, Session Work, or explicit empty focus. Task
286
+ text, paths, generated prose, and promoted child IDs never enter the lifecycle title;
287
+ unknown vocabulary produces no activity. The bounded top-level phase ID is the only
288
+ exact Taskflow identifier shown. Named Taskflow runs invoked by the assistant through
289
+ Pi's tool API publish controlled activity immediately, then consume structured
290
+ `tool_execution_update` state for the live phase and restore normal focus when tool
291
+ execution ends, so validation is driven by ordinary conversation rather than Human slash
292
+ commands. The legacy `openspecTaskId` field is parsed for compatibility but never
293
+ establishes active task focus. No arbitrary process inspection occurs.
294
+
295
+ ## Configuration
296
+
297
+ The optional configuration file is `~/.pi/agent/galactica-status.json` (the actual Pi
298
+ configuration directory in this installation). This repository keeps the source at
299
+ `.pi/agent/galactica-status.json` and links the runtime path to it; the same applies to
300
+ `.pi/agent/fancy-footer.json`. Defaults work without the status-provider file.
301
+
302
+ ```json
303
+ {
304
+ "version": 1,
305
+ "enabled": true,
306
+ "openspec": {
307
+ "enabled": true,
308
+ "change": null
309
+ },
310
+ "diagnostics": {
311
+ "enabled": true,
312
+ "sourceFile": null,
313
+ "command": null,
314
+ "intervalSeconds": 300,
315
+ "runAutomatically": false,
316
+ "timeoutSeconds": 15
317
+ },
318
+ "orchestration": {
319
+ "enabled": true,
320
+ "stateFile": null
321
+ }
322
+ }
323
+ ```
324
+
325
+ Unknown or invalid fields are ignored and reported by the debug command; they do not
326
+ prevent Pi from starting. `openspec.change` is an explicit collection target, not a
327
+ session-scoped task focus.
328
+
329
+ ## Commands
330
+
331
+ - `/work` — show Session Work; use `set <intent>`, `validate`, or `clear` to transition
332
+ explicit ephemeral focus metadata.
333
+ - `/openspec-focus` — show the current focus; pass `<change> <task>` or
334
+ `set <change> <task>` to focus, `replace <change> <task>` to deliberately replace, or
335
+ `clear` to persist no focus.
336
+ - `/galactica-status` — concise integration and normalized-state summary.
337
+ - `/galactica-status-refresh` — reload configuration and refresh all enabled sources;
338
+ this is the only default trigger for an optional command.
339
+ - `/galactica-status-debug` — project root, detected paths, source metadata, refresh
340
+ timestamps, bounded errors, and currently published snapshots. Raw source files and
341
+ command output are not dumped.
342
+
343
+ ## Refresh and failure behavior
344
+
345
+ Initialization is fire-and-forget, so prompt rendering never waits for filesystem or
346
+ subprocess work. The fallback title is emitted synchronously before initialization.
347
+ Results are cached. Only small, directly relevant directories are watched
348
+ non-recursively; events are debounced. Footer snapshots and terminal titles are emitted
349
+ only when changed. Watchers and timers are closed on shutdown/reload.
350
+
351
+ Missing optional state hides its widget. Malformed or transiently unreadable state
352
+ preserves the last good value and marks it stale. Errors remain bounded and visible only
353
+ through debug output. Expensive test suites are never started unless explicitly
354
+ configured.
355
+
356
+ ## Widget protocol
357
+
358
+ This package implements the import-free `pi-fancy-footer` **protocol 1** shipped by
359
+ `pi-fancy-footer` **3.0.1**:
360
+
361
+ - channel `pi-fancy-footer:widget`: complete `upsert` and `remove` messages;
362
+ - channel `pi-fancy-footer:ready`: republish current snapshots after footer startup;
363
+ - supported content is sanitized plain text with icon/color, optional theme-native bold,
364
+ and row/position/alignment/fill/minimum-width defaults.
365
+
366
+ Protocol 1 does not expose semantic-status, priority, tooltip, or width-callback fields.
367
+ The producer therefore orders text by semantic priority and publishes complete regular
368
+ content; Fancy Footer owns final width allocation and truncation.
369
+
370
+ Pi 0.84.2 and `pi-fancy-footer` 3.0.1 run on the pnpm-managed Node 24.19.0 runtime,
371
+ satisfying both packages' declared engine requirements.
372
+
373
+ ## Adding another provider
374
+
375
+ 1. Add a module that returns a normalized domain value without rendering UI.
376
+ 2. Add the value to `StatusSnapshot` and the atomic refresh batch.
377
+ 3. Convert it to one stable namespaced widget in `publisher.ts`.
378
+ 4. Add only direct candidate paths to the non-recursive watcher set.
379
+ 5. Test malformed input, stale preservation, unchanged suppression, and removal.
380
+
381
+ ## Uninstall
382
+
383
+ From the Galactica repository:
384
+
385
+ ```sh
386
+ # Remove the two package entries from .pi/agent/settings.json.
387
+ rm -f ~/.pi/agent/galactica-status.json ~/.pi/agent/fancy-footer.json
388
+ rm -rf .pi/agent/packages/status
389
+ rm -f .pi/agent/galactica-status.json .pi/agent/fancy-footer.json
390
+ mv .pi/agent/extensions/openspec-footer.ts.disabled \
391
+ .pi/agent/extensions/openspec-footer.ts
392
+ mv .pi/agent/extensions/pi-footer.json.disabled \
393
+ .pi/agent/extensions/pi-footer.json
394
+ npm --prefix ~/.pi/agent/npm uninstall pi-fancy-footer
395
+ npm --prefix ~/.pi/agent/npm install pi-footer
396
+ ```
397
+
398
+ Then restore `npm:pi-footer` in settings and run `/reload` or restart Pi.