pi-umbra 0.2.0 → 0.3.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 (60) hide show
  1. package/README.md +7 -5
  2. package/node_modules/pi-umbra-help/README.md +1 -0
  3. package/node_modules/pi-umbra-help/checks/umbra-help.check.ts +7 -0
  4. package/node_modules/pi-umbra-help/extensions/umbra-help.ts +12 -1
  5. package/node_modules/pi-umbra-help/package.json +1 -1
  6. package/node_modules/pi-umbra-inputbar/extensions/umbra-inputbar.ts +2 -1
  7. package/node_modules/pi-umbra-inputbar/package.json +1 -1
  8. package/node_modules/pi-umbra-shimmer/package.json +1 -1
  9. package/node_modules/pi-umbra-shimmer/patch.mjs +21 -7
  10. package/node_modules/pi-umbra-skill-matcher/package.json +1 -1
  11. package/node_modules/pi-umbra-skill-matcher/patch.mjs +21 -7
  12. package/node_modules/pi-umbra-subagents/LICENSE +21 -0
  13. package/node_modules/pi-umbra-subagents/README.md +101 -0
  14. package/node_modules/pi-umbra-subagents/extensions/umbra-loop.ts +102 -0
  15. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/bar/bar-line.ts +262 -0
  16. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/bar.check.ts +198 -0
  17. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/bar.ts +229 -0
  18. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/fan/index.ts +141 -0
  19. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/fan/models.ts +137 -0
  20. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/fan/spec.ts +88 -0
  21. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/fan/store.check.ts +140 -0
  22. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/fan/store.ts +490 -0
  23. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/panel.check.ts +237 -0
  24. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/panel.ts +378 -0
  25. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/skills/delegate/SKILL.md +95 -0
  26. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/skills/delegate/beacon.ts +210 -0
  27. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/skills/delegate/delegate.env +12 -0
  28. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/skills/delegate/report.md +15 -0
  29. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/skills/delegate/run.check.sh +120 -0
  30. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/skills/delegate/run.sh +170 -0
  31. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/skills/delegate/state.check.ts +137 -0
  32. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/skills/delegate/state.ts +295 -0
  33. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents/skills/fan/SKILL.md +52 -0
  34. package/node_modules/pi-umbra-subagents/extensions/umbra-subagents.ts +4 -0
  35. package/node_modules/pi-umbra-subagents/package.json +43 -0
  36. package/node_modules/pi-umbra-subagents/patch.mjs +97 -0
  37. package/node_modules/pi-umbra-theme/README.md +39 -16
  38. package/node_modules/pi-umbra-theme/checks/umbra-background.check.ts +44 -1
  39. package/node_modules/pi-umbra-theme/checks/umbra-image-viewer.check.ts +85 -0
  40. package/node_modules/pi-umbra-theme/checks/umbra-toolbox.check.ts +20 -1
  41. package/node_modules/pi-umbra-theme/checks/umbra-working.check.ts +17 -10
  42. package/node_modules/pi-umbra-theme/extensions/umbra-background.ts +52 -1
  43. package/node_modules/pi-umbra-theme/extensions/umbra-image-viewer.ts +230 -0
  44. package/node_modules/pi-umbra-theme/extensions/umbra-toolbox/renderer/compact-mode.ts +3 -3
  45. package/node_modules/pi-umbra-theme/extensions/umbra-toolbox/renderer/mouse/hover.ts +0 -4
  46. package/node_modules/pi-umbra-theme/extensions/umbra-toolbox/renderer/mouse/interaction.ts +20 -68
  47. package/node_modules/pi-umbra-theme/extensions/umbra-toolbox/renderer/mouse/layout.ts +0 -4
  48. package/node_modules/pi-umbra-theme/extensions/umbra-toolbox/renderer/mouse/scroll.ts +6 -190
  49. package/node_modules/pi-umbra-theme/extensions/umbra-toolbox/renderer/tool/diff/diff-palette.ts +44 -3
  50. package/node_modules/pi-umbra-theme/extensions/umbra-toolbox/renderer/tool/diff/shiki-highlight.ts +5 -3
  51. package/node_modules/pi-umbra-theme/extensions/umbra-toolbox/renderer/tool/names.ts +1 -0
  52. package/node_modules/pi-umbra-theme/extensions/umbra-working.ts +16 -6
  53. package/node_modules/pi-umbra-theme/package.json +1 -1
  54. package/node_modules/pi-umbra-theme/themes/umbra-astral-veil.json +1 -1
  55. package/node_modules/pi-umbra-theme/themes/umbra-deep-current.json +4 -4
  56. package/node_modules/pi-umbra-theme/themes/umbra-ember-ash.json +10 -10
  57. package/node_modules/pi-umbra-theme/themes/umbra-onyx-slate.json +5 -5
  58. package/node_modules/pi-umbra-theme/themes/umbra-tidal-drift.json +20 -20
  59. package/node_modules/pi-umbra-theme/themes/umbra-venom-dusk.json +4 -4
  60. package/package.json +16 -9
@@ -1,23 +1,6 @@
1
- import {
2
- getKeybindings,
3
- matchesKey,
4
- truncateToWidth,
5
- visibleWidth,
6
- type Keybinding,
7
- } from "@earendil-works/pi-tui";
8
1
  import { config } from "../../config/config.ts";
9
2
  import { isLazyProxyTui } from "../../utils/fullscreen-detect.ts";
10
- import { parseSgrMousePackets } from "./packets.ts";
11
- import {
12
- OFFICIAL_SCROLL_TO_END_KEY,
13
- patchRegistry,
14
- SCROLL_BUTTON_STATE_SLOT,
15
- TOOL_MOUSE_TUI_SLOT,
16
- } from "../../utils/patch-keys.ts";
17
-
18
- const ZENTUI_PAGE_UP_INPUT = /^\x1b\[5;9(?::[12])?~$|^\x1b\[57421;9(?::[12])?u$|^\x1b\[1;6A$/;
19
- const ZENTUI_PAGE_DOWN_INPUT = /^\x1b\[6;9(?::[12])?~$|^\x1b\[57422;9(?::[12])?u$|^\x1b\[1;6B$/;
20
- const SCROLL_BOTTOM_SHORTCUT = "ctrl+end";
3
+ import { OFFICIAL_SCROLL_TO_END_KEY, patchRegistry, TOOL_MOUSE_TUI_SLOT } from "../../utils/patch-keys.ts";
21
4
 
22
5
  patchRegistry.ensure(TOOL_MOUSE_TUI_SLOT, () => null);
23
6
  export function getToolMouseTui(): any {
@@ -27,48 +10,6 @@ export function setToolMouseTui(tui: any): void {
27
10
  patchRegistry.install(TOOL_MOUSE_TUI_SLOT, tui);
28
11
  }
29
12
 
30
- type ScrollButtonState = { visible: boolean; hovered: boolean; widget: any };
31
- function scrollButtonState(): ScrollButtonState {
32
- return patchRegistry.ensure(SCROLL_BUTTON_STATE_SLOT, () => ({
33
- visible: false,
34
- hovered: false,
35
- widget: null,
36
- }));
37
- }
38
- export function getScrollButtonVisible(): boolean {
39
- return scrollButtonState().visible;
40
- }
41
- export function getScrollButtonHovered(): boolean {
42
- return scrollButtonState().hovered;
43
- }
44
- export function getScrollButtonWidget(): any {
45
- return scrollButtonState().widget;
46
- }
47
- export function setScrollButtonVisible(visible: boolean): void {
48
- scrollButtonState().visible = visible;
49
- }
50
-
51
- /** 返回是否发生变化(调用方据此决定是否需要重渲染)。 */
52
- export function setScrollButtonHovered(hovered: boolean): boolean {
53
- if (hovered === scrollButtonState().hovered) return false;
54
- scrollButtonState().hovered = hovered;
55
- return true;
56
- }
57
-
58
- export function setScrollButtonWidget(widget: any): void {
59
- scrollButtonState().widget = widget;
60
- }
61
-
62
- /** teardown 全量清零(visible/hovered/widget/sync 调度)。 */
63
- export function resetScrollButtonState(): void {
64
- scrollButtonState().visible = false;
65
- scrollButtonState().hovered = false;
66
- scrollButtonState().widget = null;
67
- scrollButtonSyncScheduled = false;
68
- }
69
-
70
- let scrollButtonSyncScheduled = false;
71
-
72
13
  // 交互开关只取决于配置模式:原实现按 isLazyProxyTui(toolMouseTui) 分两分支,
73
14
  // 两分支恒真(0.84+ 惰性 Proxy 下判定不再影响开关),折叠为单条件。
74
15
  export function toolMouseInteractionActive(): boolean {
@@ -80,139 +21,14 @@ export function fullscreenLazyTui(tui: any): boolean {
80
21
  return isLazyProxyTui(tui) && tui.mode === "fullscreen";
81
22
  }
82
23
 
83
- /** 关掉 pi 0.85 Jump to latest overlay,避免和本仓库 dock 按钮叠两层。 */
84
- export function disableOfficialScrollToEnd(tui: any): void {
85
- if (!tui) return;
86
- const current = tui.scrollToEndIndicator;
87
- if (typeof current !== "function") return;
88
- // 原函数放 object 里,避免惰性 Proxy 对 function 属性再包一层。
89
- if (!tui[OFFICIAL_SCROLL_TO_END_KEY]) {
90
- tui[OFFICIAL_SCROLL_TO_END_KEY] = { original: current };
91
- }
92
- tui.scrollToEndIndicator = undefined;
93
- }
94
-
95
- /** /ccstyle off 或 teardown 时还回官方 overlay。 */
24
+ /**
25
+ * Gives pi back its own jump-to-latest indicator. An older toolbox hid it behind a
26
+ * "Back to bottom" button and kept the original under OFFICIAL_SCROLL_TO_END_KEY; a /reload
27
+ * inside that same pi would otherwise leave the indicator gone.
28
+ */
96
29
  export function restoreOfficialScrollToEnd(tui: any): void {
97
30
  if (!tui) return;
98
31
  const original = tui[OFFICIAL_SCROLL_TO_END_KEY]?.original;
99
32
  if (typeof original === "function") tui.scrollToEndIndicator = original;
100
33
  tui[OFFICIAL_SCROLL_TO_END_KEY] = undefined;
101
34
  }
102
-
103
- /** on/compact 用 dock 按钮;off 还回 pi 原生 Jump to latest。 */
104
- export function syncOfficialScrollToEnd(tui: any): void {
105
- if (toolMouseInteractionActive()) disableOfficialScrollToEnd(tui);
106
- else restoreOfficialScrollToEnd(tui);
107
- }
108
-
109
- /** 官方 fullscreen:是否已跟随 transcript 底部(按钮隐藏条件)。 */
110
- export function isFullscreenAtBottom(tui: any): boolean {
111
- const following = tui.isFollowingOutput ?? tui.getPrimaryScrollView?.()?.isFollowingEnd ?? true;
112
- return Boolean(following);
113
- }
114
-
115
- function formatShortcut(shortcut: string): string {
116
- return shortcut
117
- .split("+")
118
- .map((part) =>
119
- part.length <= 1 ? part.toUpperCase() : `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`,
120
- )
121
- .join("+");
122
- }
123
-
124
- export function isScrollBottomInput(data: string): boolean {
125
- return matchesKey(data, SCROLL_BOTTOM_SHORTCUT);
126
- }
127
-
128
- function isScrollNavigationInput(data: string): boolean {
129
- if (
130
- matchesKey(data, "pageUp") ||
131
- matchesKey(data, "pageDown") ||
132
- ZENTUI_PAGE_UP_INPUT.test(data) ||
133
- ZENTUI_PAGE_DOWN_INPUT.test(data) ||
134
- // 官方 fullscreen viewport 的可滚动键(half-page/prompt/top/bottom)。
135
- [
136
- "tui.altScreen.pageUp",
137
- "tui.altScreen.pageDown",
138
- "tui.altScreen.halfPageUp",
139
- "tui.altScreen.halfPageDown",
140
- "tui.altScreen.previousPrompt",
141
- "tui.altScreen.nextPrompt",
142
- "tui.altScreen.top",
143
- "tui.altScreen.bottom",
144
- ].some((key) => getKeybindings().matches(data, key as Keybinding))
145
- ) {
146
- return true;
147
- }
148
- const packets = parseSgrMousePackets(data);
149
- return Boolean(
150
- packets?.some((packet) => {
151
- const baseButton = packet.code & ~(4 | 8 | 16 | 32);
152
- return packet.final === "M" && (baseButton === 64 || baseButton === 65);
153
- }),
154
- );
155
- }
156
-
157
- function isAtTranscriptBottom(tui: any): boolean {
158
- // 惰性 Proxy fullscreen:官方 viewport 以 isFollowingOutput 判定是否在底部。
159
- if (fullscreenLazyTui(tui)) return isFullscreenAtBottom(tui);
160
- return true;
161
- }
162
-
163
- export function hideScrollButton(tui: any): void {
164
- const changed = getScrollButtonVisible() || getScrollButtonHovered();
165
- setScrollButtonVisible(false);
166
- setScrollButtonHovered(false);
167
- if (changed) tui.requestRender?.();
168
- }
169
-
170
- export function scheduleScrollButtonSync(tui: any, data: string): void {
171
- if (
172
- !fullscreenLazyTui(tui) ||
173
- !toolMouseInteractionActive() ||
174
- !isScrollNavigationInput(data) ||
175
- scrollButtonSyncScheduled
176
- )
177
- return;
178
- scrollButtonSyncScheduled = true;
179
- const previousLines = tui.previousLines;
180
- const check = (attempt: number) => {
181
- scrollButtonSyncScheduled = false;
182
- if (getToolMouseTui() !== tui) return;
183
- // Pi renders on its own frame timer. Inspect the resulting viewport before
184
- // showing the button so empty or non-scrollable transcripts never flash it.
185
- const rendered = tui.previousLines !== previousLines;
186
- // fullscreen 下 isFollowingOutput 是即时状态,无需等待官方帧渲染。
187
- if (!rendered && attempt < 4 && !fullscreenLazyTui(tui)) {
188
- scrollButtonSyncScheduled = true;
189
- const timer = setTimeout(() => check(attempt + 1), 16);
190
- if (typeof timer === "object" && timer !== null && "unref" in timer) {
191
- (timer as { unref: () => void }).unref();
192
- }
193
- return;
194
- }
195
- const nextVisible = !isAtTranscriptBottom(tui);
196
- if (nextVisible !== getScrollButtonVisible()) {
197
- setScrollButtonVisible(nextVisible);
198
- tui.requestRender?.();
199
- }
200
- };
201
- process.nextTick(() => check(0));
202
- }
203
-
204
- export function updateScrollButtonFromInput(tui: any, data: string): void {
205
- if (!fullscreenLazyTui(tui) || !toolMouseInteractionActive()) return;
206
- if (matchesKey(data, "enter") || matchesKey(data, "return")) hideScrollButton(tui);
207
- }
208
-
209
- export function renderScrollButton(width: number, theme: any): string[] {
210
- if (!getScrollButtonVisible() || !fullscreenLazyTui(getToolMouseTui())) return [];
211
- const shortcut = formatShortcut(SCROLL_BOTTOM_SHORTCUT);
212
- const label = theme.fg(
213
- getScrollButtonHovered() ? "text" : "accent",
214
- `[ ↓ Back to bottom · ${shortcut} ]`,
215
- );
216
- const leftPad = Math.max(0, Math.floor((width - visibleWidth(label)) / 2));
217
- return [`${" ".repeat(leftPad)}${truncateToWidth(label, width, "…")}`];
218
- }
@@ -398,13 +398,54 @@ export function applyInlineSpanHighlight(
398
398
  return highlighted;
399
399
  }
400
400
 
401
- export function resolveShikiTheme(theme: DiffTheme): string {
401
+ /** A bundled shiki theme by name, or one built from the pi theme's own colours. */
402
+ export type ShikiTheme =
403
+ | string
404
+ | {
405
+ name: string;
406
+ type: "dark" | "light";
407
+ colors: Record<string, string>;
408
+ tokenColors: { scope: string[]; settings: { foreground: string } }[];
409
+ };
410
+
411
+ // pi's nine syntax slots and the TextMate scopes each one colours. The most specific scope wins,
412
+ // as in VS Code, so keyword.operator beats keyword and a string's quotes stay string-coloured.
413
+ const SYNTAX_SCOPES: Record<string, string[]> = {
414
+ syntaxComment: ["comment", "punctuation.definition.comment"],
415
+ syntaxKeyword: ["keyword", "storage", "storage.type", "storage.modifier"],
416
+ syntaxFunction: ["entity.name.function", "support.function", "meta.function-call"],
417
+ syntaxVariable: ["variable", "variable.other", "variable.parameter", "meta.object-literal.key"],
418
+ syntaxString: ["string", "string.template", "punctuation.definition.string"],
419
+ syntaxNumber: ["constant.numeric", "constant.language", "constant.character"],
420
+ syntaxType: ["entity.name.type", "entity.name.class", "support.type", "support.class", "entity.other.inherited-class"],
421
+ syntaxOperator: ["keyword.operator"],
422
+ syntaxPunctuation: ["punctuation", "meta.brace"],
423
+ };
424
+
425
+ const toHex = ({ r, g, b }: RgbColor): string => `#${[r, g, b].map((channel) => channel.toString(16).padStart(2, "0")).join("")}`;
426
+
427
+ /**
428
+ * Code in a diff is coloured with the active pi theme's syntax colours, the same ones pi uses for
429
+ * a code block, so each theme keeps its own look there too. When pi reports no colour for a slot,
430
+ * github-dark or github-light is used instead, picked by the background.
431
+ */
432
+ export function resolveShikiTheme(theme: DiffTheme): ShikiTheme {
402
433
  if (process.env.DIFF_THEME) return process.env.DIFF_THEME;
403
434
  const background =
404
435
  parseAnsiColorCode(readThemeAnsi(theme, "bg", "toolSuccessBg")) ??
405
436
  parseAnsiColorCode(readThemeAnsi(theme, "bg", "toolPendingBg")) ??
406
437
  parseAnsiColorCode(readThemeAnsi(theme, "bg", "userMessageBg"));
407
438
  if (!background) return "github-dark";
408
- const luminance = (background.r * 299 + background.g * 587 + background.b * 114) / 1000;
409
- return luminance >= 128 ? "github-light" : "github-dark";
439
+ const light = (background.r * 299 + background.g * 587 + background.b * 114) / 1000 >= 128;
440
+ const text = parseAnsiColorCode(readThemeAnsi(theme, "fg", "text"));
441
+ const slots = Object.keys(SYNTAX_SCOPES).map((slot) => [slot, parseAnsiColorCode(readThemeAnsi(theme, "fg", slot))] as const);
442
+ if (!text || slots.some(([, rgb]) => !rgb)) return light ? "github-light" : "github-dark";
443
+ return {
444
+ // The name is the cache key, so it spells out every colour: a theme switch never reuses
445
+ // lines coloured by the previous theme.
446
+ name: `pi-${[text, ...slots.map(([, rgb]) => rgb!)].map(toHex).join("").replace(/#/g, "")}`,
447
+ type: light ? "light" : "dark",
448
+ colors: { "editor.foreground": toHex(text) },
449
+ tokenColors: slots.map(([slot, rgb]) => ({ scope: SYNTAX_SCOPES[slot]!, settings: { foreground: toHex(rgb!) } })),
450
+ };
410
451
  }
@@ -1,7 +1,9 @@
1
+ import type { ShikiTheme } from "./diff-palette.ts";
2
+
1
3
  export const MAX_HL_CHARS = 32_000;
2
4
  const MAX_CACHE_ENTRIES = 48;
3
5
 
4
- type CodeToAnsi = (code: string, lang: any, theme: any) => Promise<string>;
6
+ type CodeToAnsi = (code: string, lang: any, theme: ShikiTheme) => Promise<string>;
5
7
  type Loader = () => Promise<CodeToAnsi>;
6
8
  type Subscriber = { fallback: readonly string[]; invalidate: () => void };
7
9
  type Pending = { work: Promise<void>; subscribers: Set<Subscriber> };
@@ -35,12 +37,12 @@ export class ShikiHighlightCache {
35
37
  get(
36
38
  code: string,
37
39
  lang: string | undefined,
38
- theme: string,
40
+ theme: ShikiTheme,
39
41
  fallback: readonly string[],
40
42
  invalidate?: () => void,
41
43
  ): string[] | undefined {
42
44
  if (!lang || code.length > MAX_HL_CHARS) return undefined;
43
- const key = `${theme}\0${lang}\0${code}`;
45
+ const key = `${typeof theme === "string" ? theme : theme.name}\0${lang}\0${code}`;
44
46
  const cached = this.cache.get(key);
45
47
  if (cached) {
46
48
  this.cache.delete(key);
@@ -15,6 +15,7 @@ const MIN_PAYLOAD_WIDTH = 10;
15
15
  /** 品牌大小写固定写法;humanize 推导不出来的工具名写在这里。 */
16
16
  const TOOL_LABEL_OVERRIDES: Readonly<Record<string, string>> = {
17
17
  powershell: "PowerShell",
18
+ ls: "List",
18
19
  // MCP 入口两个没有具体工具名,跟普通工具一样人性化;缩写固定大写
19
20
  mcp: "MCP",
20
21
  mcpscript: "MCP Script",
@@ -57,12 +57,13 @@ const CYCLE_MS = 480;
57
57
  const DEFAULTS: Record<string, Omit<Entry, "cycleMs"> & { cycleMs?: number }> = {
58
58
  read: { label: "Reading", frames: ARC },
59
59
  ls: { label: "Listing", frames: SCAN },
60
- find: { label: "Listing", frames: SCAN },
61
- grep: { label: "Listing", frames: SCAN },
62
- write: { label: "Editing", frames: CORNER },
60
+ find: { label: "Searching", frames: SCAN },
61
+ grep: { label: "Searching", frames: SCAN },
62
+ write: { label: "Writing", frames: CORNER },
63
63
  edit: { label: "Editing", frames: CORNER },
64
- bash: { label: "Shell", frames: CLOCK },
65
- powershell: { label: "Shell", frames: CLOCK },
64
+ bash: { label: "Running", frames: CLOCK },
65
+ powershell: { label: "Running", frames: CLOCK },
66
+ ask_user_question: { label: "Asking", frames: DIAMOND },
66
67
  thinking: { label: "Thinking", frames: STAR },
67
68
  idle: { label: "Working", frames: BRAILLE },
68
69
  custom: { label: "Tool call", frames: DIAMOND },
@@ -110,11 +111,20 @@ export const newTurn = (now: number): Turn => ({
110
111
  * the fallback animation. A `frames` array replaces outright rather than merging, which is what
111
112
  * lets a three-glyph override stay three; an empty one is not an override.
112
113
  */
114
+ /** The title the tool card gives a tool it has no word for: `web_search` → `Web Search`. */
115
+ export const humanize = (name: string): string =>
116
+ name
117
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
118
+ .replace(/[_-]+/g, " ")
119
+ .replace(/\b\w/g, (char) => char.toUpperCase());
120
+
113
121
  export const entryFor = (key: string, settings: Settings = {}, cycleMs = CYCLE_MS): Entry => {
114
122
  const base = DEFAULTS[key] ?? DEFAULTS[CUSTOM]!;
115
123
  const named = settings.tools?.[key] ?? {};
124
+ // A tool umbra has no verb for shows the name its card shows, unless `custom` was renamed.
125
+ const fallback = DEFAULTS[key]?.label ?? settings.tools?.[CUSTOM]?.label ?? humanize(key);
116
126
  return {
117
- label: named.label ?? base.label,
127
+ label: named.label ?? fallback,
118
128
  cycleMs: named.cycleMs ?? base.cycleMs ?? cycleMs,
119
129
  frames: named.frames?.length ? named.frames : base.frames,
120
130
  };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-umbra-theme",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "pi's look in one package: seven umbra themes and the terminal background they set, a status footer, a gutter beside model text, every tool call drawn as a card with rich diffs, and a working line that names the running tool.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -56,7 +56,7 @@
56
56
  "mdCodeBlock": "#7fc9d6",
57
57
  "mdCodeBlockBorder": "gray",
58
58
  "mdQuote": "#82828c",
59
- "mdQuoteBorder": "#82828c",
59
+ "mdQuoteBorder": "accent",
60
60
  "mdHr": "#82828c",
61
61
  "mdListBullet": "#7fc9d6",
62
62
  "toolDiffAdded": "diffAdded",
@@ -52,13 +52,13 @@
52
52
  "mdHeading": "#ffcb6b",
53
53
  "mdLink": "#82aaff",
54
54
  "mdLinkUrl": "#f78c6c",
55
- "mdCode": "#575a67",
55
+ "mdCode": "#c3e88d",
56
56
  "mdCodeBlock": "#575a67",
57
57
  "mdCodeBlockBorder": "gray",
58
- "mdQuote": "#8f93a2",
59
- "mdQuoteBorder": "#8f93a2",
58
+ "mdQuote": "#b2ccd6",
59
+ "mdQuoteBorder": "accent",
60
60
  "mdHr": "#323643",
61
- "mdListBullet": "#8f93a2",
61
+ "mdListBullet": "accent",
62
62
  "toolDiffAdded": "diffAdded",
63
63
  "toolDiffRemoved": "diffRemoved",
64
64
  "toolDiffContext": "gray",
@@ -3,18 +3,18 @@
3
3
  "name": "umbra-ember-ash",
4
4
  "vars": {
5
5
  "text": "#d6deeb",
6
- "gray": "#8b9099",
7
- "dimGray": "#575656",
6
+ "gray": "#7d888d",
7
+ "dimGray": "#5e686c",
8
8
  "darkGray": "#333333",
9
9
  "accent": "#7e57c2",
10
- "selectedBg": "#1f1f1f",
10
+ "selectedBg": "#2b2f32",
11
11
  "userMsgBg": "#121212",
12
12
  "toolPendingBg": "#000000",
13
13
  "toolSuccessBg": "#000000",
14
14
  "toolErrorBg": "#000000",
15
15
  "customMsgBg": "#121824",
16
16
  "errorFg": "#ef5350",
17
- "warningFg": "#b39554",
17
+ "warningFg": "#ffca28",
18
18
  "successFg": "#22da6e",
19
19
  "diffAdded": "#addb67",
20
20
  "diffRemoved": "#ef5350",
@@ -47,17 +47,17 @@
47
47
  "toolPendingBg": "toolPendingBg",
48
48
  "toolSuccessBg": "toolSuccessBg",
49
49
  "toolErrorBg": "toolErrorBg",
50
- "toolTitle": "#7e57c2",
50
+ "toolTitle": "#ff7edb",
51
51
  "toolOutput": "gray",
52
52
  "mdHeading": "#7ed0ff",
53
53
  "mdLink": "#fede5d",
54
- "mdLinkUrl": "#72f1b8",
54
+ "mdLinkUrl": "#848bbd",
55
55
  "mdCode": "#72f1b8",
56
- "mdCodeBlock": "#eeeeee",
56
+ "mdCodeBlock": "#d6deeb",
57
57
  "mdCodeBlockBorder": "gray",
58
- "mdQuote": "#72f1b8",
59
- "mdQuoteBorder": "#72f1b8",
60
- "mdHr": "#eeeeee",
58
+ "mdQuote": "#ededed",
59
+ "mdQuoteBorder": "#7e57c2",
60
+ "mdHr": "#5e686c",
61
61
  "mdListBullet": "#7ed0ff",
62
62
  "toolDiffAdded": "diffAdded",
63
63
  "toolDiffRemoved": "diffRemoved",
@@ -47,18 +47,18 @@
47
47
  "toolPendingBg": "toolPendingBg",
48
48
  "toolSuccessBg": "toolSuccessBg",
49
49
  "toolErrorBg": "toolErrorBg",
50
- "toolTitle": "#50bdfb",
50
+ "toolTitle": "#82aaff",
51
51
  "toolOutput": "gray",
52
52
  "mdHeading": "#ffcb6b",
53
53
  "mdLink": "#82aaff",
54
- "mdLinkUrl": "#f78c6c",
54
+ "mdLinkUrl": "#676e95",
55
55
  "mdCode": "#e8ecf2",
56
- "mdCodeBlock": "#5e6174",
56
+ "mdCodeBlock": "#a6accd",
57
57
  "mdCodeBlockBorder": "gray",
58
58
  "mdQuote": "#9aa0ac",
59
- "mdQuoteBorder": "#9aa0ac",
59
+ "mdQuoteBorder": "#4e5579",
60
60
  "mdHr": "#4e5579",
61
- "mdListBullet": "#9aa0ac",
61
+ "mdListBullet": "#89ddff",
62
62
  "toolDiffAdded": "diffAdded",
63
63
  "toolDiffRemoved": "diffRemoved",
64
64
  "toolDiffContext": "gray",
@@ -3,11 +3,11 @@
3
3
  "name": "umbra-tidal-drift",
4
4
  "vars": {
5
5
  "text": "#d5ced9",
6
- "gray": "#908c96",
6
+ "gray": "#747077",
7
7
  "dimGray": "#6e7178",
8
8
  "darkGray": "#282d44",
9
- "accent": "#00e8c5",
10
- "selectedBg": "#454b5e",
9
+ "accent": "#01a892",
10
+ "selectedBg": "#105750",
11
11
  "userMsgBg": "#1d2133",
12
12
  "toolPendingBg": "#0f111a",
13
13
  "toolSuccessBg": "#0f111a",
@@ -18,13 +18,13 @@
18
18
  "successFg": "#45c758",
19
19
  "diffAdded": "#45c758",
20
20
  "diffRemoved": "#fc644d",
21
- "accentLine": "#00e8c5",
22
- "accentBright": "#4cefd6",
23
- "accentFaint": "#068e7d"
21
+ "accentLine": "#01a892",
22
+ "accentBright": "#00d4b5",
23
+ "accentFaint": "#105750"
24
24
  },
25
25
  "colors": {
26
26
  "accent": "accent",
27
- "border": "#00e8c5",
27
+ "border": "#01a892",
28
28
  "borderAccent": "accentBright",
29
29
  "borderMuted": "accentFaint",
30
30
  "success": "successFg",
@@ -47,18 +47,18 @@
47
47
  "toolPendingBg": "toolPendingBg",
48
48
  "toolSuccessBg": "toolSuccessBg",
49
49
  "toolErrorBg": "toolErrorBg",
50
- "toolTitle": "#00e8c5",
50
+ "toolTitle": "#87d3f8",
51
51
  "toolOutput": "gray",
52
52
  "mdHeading": "#ff00aa",
53
53
  "mdLink": "#14b37d",
54
54
  "mdLinkUrl": "#c74ded",
55
55
  "mdCode": "#14b37d",
56
- "mdCodeBlock": "#45c758",
56
+ "mdCodeBlock": "#d5ced9",
57
57
  "mdCodeBlockBorder": "gray",
58
- "mdQuote": "#6e7178",
59
- "mdQuoteBorder": "#6e7178",
60
- "mdHr": "#908c96",
61
- "mdListBullet": "#87d3f8",
58
+ "mdQuote": "text",
59
+ "mdQuoteBorder": "#117e6d",
60
+ "mdHr": "#746f77",
61
+ "mdListBullet": "#f2f27a",
62
62
  "toolDiffAdded": "diffAdded",
63
63
  "toolDiffRemoved": "diffRemoved",
64
64
  "toolDiffContext": "gray",
@@ -71,13 +71,13 @@
71
71
  "syntaxType": "#f39c12",
72
72
  "syntaxOperator": "#e25822",
73
73
  "syntaxPunctuation": "#d5ced9",
74
- "thinkingOff": "#00e8c5",
75
- "thinkingMinimal": "#1a6e71",
76
- "thinkingLow": "#0cb09e",
77
- "thinkingMedium": "#00e8c5",
78
- "thinkingHigh": "#33edd1",
79
- "thinkingXhigh": "#61f1db",
80
- "thinkingMax": "#8ff5e5",
74
+ "thinkingOff": "#01a892",
75
+ "thinkingMinimal": "#105750",
76
+ "thinkingLow": "#0b7f70",
77
+ "thinkingMedium": "#01a892",
78
+ "thinkingHigh": "#00bfa3",
79
+ "thinkingXhigh": "#00d4b5",
80
+ "thinkingMax": "#00e8c5",
81
81
  "bashMode": "#45c758"
82
82
  },
83
83
  "export": {
@@ -50,13 +50,13 @@
50
50
  "toolTitle": "#e6b450",
51
51
  "toolOutput": "gray",
52
52
  "mdHeading": "#aad94c",
53
- "mdLink": "#39bae6",
54
- "mdLinkUrl": "#39bae6",
53
+ "mdLink": "#ffb454",
54
+ "mdLinkUrl": "#ffb454",
55
55
  "mdCode": "#f29668",
56
56
  "mdCodeBlock": "#7fd962",
57
57
  "mdCodeBlockBorder": "gray",
58
- "mdQuote": "#95e6cb",
59
- "mdQuoteBorder": "#95e6cb",
58
+ "mdQuote": "#d9c7a4",
59
+ "mdQuoteBorder": "accent",
60
60
  "mdHr": "#636a72",
61
61
  "mdListBullet": "#e6b450",
62
62
  "toolDiffAdded": "diffAdded",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-umbra",
3
- "version": "0.2.0",
4
- "description": "Every small thing pi does not build in, in one install: seven themes and the chrome around them, the input bar, session rename, a shimmer through the working indicator, mid-prompt skill completion, ask, copy-chat, preview and help.",
3
+ "version": "0.3.0",
4
+ "description": "Every small thing pi does not build in, in one install: seven themes and the chrome around them, the input bar, session rename, a shimmer through the working indicator, mid-prompt skill completion, ask, copy-chat, preview, help, and parallel read-only branches with a live panel.",
5
5
  "keywords": [
6
6
  "pi-package",
7
7
  "pi-theme",
@@ -33,22 +33,28 @@
33
33
  "./node_modules/pi-umbra-preview/extensions",
34
34
  "./node_modules/pi-umbra-rename/extensions",
35
35
  "./node_modules/pi-umbra-shimmer/extensions",
36
- "./node_modules/pi-umbra-skill-matcher/extensions"
36
+ "./node_modules/pi-umbra-skill-matcher/extensions",
37
+ "./node_modules/pi-umbra-subagents/extensions/umbra-subagents.ts",
38
+ "./node_modules/pi-umbra-subagents/extensions/umbra-loop.ts"
39
+ ],
40
+ "skills": [
41
+ "./node_modules/pi-umbra-subagents/extensions/umbra-subagents/skills"
37
42
  ],
38
43
  "themes": [
39
44
  "./node_modules/pi-umbra-theme/themes"
40
45
  ]
41
46
  },
42
47
  "dependencies": {
43
- "pi-umbra-theme": "0.2.0",
48
+ "pi-umbra-theme": "0.2.1",
44
49
  "pi-umbra-ask": "0.1.2",
45
50
  "pi-umbra-copy-chat": "0.1.2",
46
- "pi-umbra-help": "0.1.3",
47
- "pi-umbra-inputbar": "0.1.3",
51
+ "pi-umbra-help": "0.1.4",
52
+ "pi-umbra-inputbar": "0.1.4",
48
53
  "pi-umbra-preview": "0.1.3",
49
54
  "pi-umbra-rename": "0.1.3",
50
- "pi-umbra-shimmer": "0.1.4",
51
- "pi-umbra-skill-matcher": "0.1.3"
55
+ "pi-umbra-shimmer": "0.1.5",
56
+ "pi-umbra-skill-matcher": "0.1.4",
57
+ "pi-umbra-subagents": "0.1.1"
52
58
  },
53
59
  "bundledDependencies": [
54
60
  "pi-umbra-theme",
@@ -59,7 +65,8 @@
59
65
  "pi-umbra-preview",
60
66
  "pi-umbra-rename",
61
67
  "pi-umbra-shimmer",
62
- "pi-umbra-skill-matcher"
68
+ "pi-umbra-skill-matcher",
69
+ "pi-umbra-subagents"
63
70
  ],
64
71
  "peerDependencies": {
65
72
  "@earendil-works/pi-ai": "*",