osdy-pi 1.6.1 → 1.8.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/README.md +19 -5
- package/extensions/osdy-pi/runtime.test.ts +25 -1
- package/extensions/osdy-pi/todo-widget.test.ts +120 -22
- package/extensions/osdy-pi/todo-widget.ts +33 -6
- package/package.json +7 -3
- package/themes/osdy-pi-daniela-cute.json +85 -0
- package/themes/osdy-pi-gruvbox-dark.json +88 -0
- package/themes/osdy-pi-nord.json +86 -0
- package/themes/osdy-pi-rose-pine.json +85 -0
package/README.md
CHANGED
|
@@ -6,8 +6,11 @@ Osdy Pi gives [Pi](https://github.com/earendil-works/pi) a themed, responsive te
|
|
|
6
6
|
|
|
7
7
|
## Release highlights
|
|
8
8
|
|
|
9
|
+
The **1.8.0 release candidate** includes the changes below; npm publication is not yet confirmed.
|
|
10
|
+
|
|
9
11
|
| Area | What ships |
|
|
10
12
|
| --- | --- |
|
|
13
|
+
| New themes (included starting in 1.8.0) | Gruvbox Dark, Nord, Rosé Pine base dark, and Daniela Cute bring the bundle to 18 themes. |
|
|
11
14
|
| Message cards | Pi-native colored user cards without emojis and plain assistant Markdown preceded by a separate `🦝` transcript row. |
|
|
12
15
|
| Header and mascot | A theme-aware `neon` header and selectable `bts` mascot, both responsive and persisted independently. |
|
|
13
16
|
| Accounts | `/osdy-account` switches Codex profiles in place with atomic activation, rollback, bounded auth files, and process-safe locks. |
|
|
@@ -42,7 +45,7 @@ Osdy Pi gives [Pi](https://github.com/earendil-works/pi) a themed, responsive te
|
|
|
42
45
|
pi
|
|
43
46
|
```
|
|
44
47
|
|
|
45
|
-
Osdy Pi's own extension and
|
|
48
|
+
Osdy Pi's own extension and themes are bundled—do not install them separately. The **1.8.0 release candidate** bundles **18 themes**, including Gruvbox Dark, Nord, Rosé Pine base dark, and Daniela Cute starting in 1.8.0. Check your installed npm version; publication of 1.8.0 is not yet confirmed. Launch through `osdy-pi` (or `npm run pi:dev`) to activate a valid Osdy default account into Pi's shared agent directory before Pi starts. With no default, Pi remains unmanaged; run `/osdy-account` to create a profile and establish a default. On session start, Osdy Pi restores its persisted enabled state when a UI is available and preserves your selected Pi theme. When disabled, it leaves Gentle Shell (or Pi's native UI) untouched.
|
|
46
49
|
|
|
47
50
|
### Optional: isolated `osdy` command
|
|
48
51
|
|
|
@@ -132,13 +135,15 @@ The exclusions prevent Gentle's todo, questionnaire, and agents extensions from
|
|
|
132
135
|
|
|
133
136
|
The model-facing `todo` tool supports `create`, `update`, `list`, `get`, `delete`, and `clear`. Tool results carry full task snapshots; Pi's current session branch is the TODO authority. Session switches and compaction replay the latest valid branch snapshot, rather than sharing one project-wide list. `/todos` opens a read-only, grouped modal (pending, in progress, completed) in interactive Pi; scroll with arrow or Page Up/Down keys and close with Esc or q. The refreshed modal adds a completed-task progress meter, theme-aware status groups and a scroll-position footer while keeping every task reachable. It is not an interactive Markdown editor. In non-terminal modes with UI support, it retains notification output. ODD's `odd/tasks/*.md` ledger and Engram are separate orchestration records: there is no automatic sync with these Pi TODOs.
|
|
134
137
|
|
|
135
|
-
In interactive Pi, the persistent widget appears above the editor while visible tasks exist.
|
|
138
|
+
In interactive Pi, the persistent widget appears above the editor while visible tasks exist. Its compact preview shows up to three task rows (excluding heading, overflow hint, and spacer), prioritizes unfinished tasks on overflow, and crosses out completed subjects. In fullscreen Pi, left-click the heading to expand or return to preview; wheel over the expanded widget to move one task at a time beneath the fixed heading. The final task can reach the first task row even in a short dock. A heading-only or hidden dock cannot display tasks; use `/todos` for the full keyboard-accessible list. Regular mode stays compact; Pi's tool-output expansion does not change this widget. `ctrl+shift+t` retains the separate fully collapsed view.
|
|
139
|
+
|
|
140
|
+
Expansion and scroll position survive same-session refresh, tree navigation, and compaction, with offsets clamped as tasks change. Foreground session replacement and shutdown reset the view; background sessions do not affect it. Task data and `/todos` are unchanged.
|
|
136
141
|
|
|
137
142
|
### Try session TODO
|
|
138
143
|
|
|
139
|
-
Use
|
|
144
|
+
Use this checkout (or a package containing the three-task preview), ask the agent to track a task with `todo`, then run `/todos`. The modal shows the entire grouped list with arrow/Page Up/Down scrolling. `maxWidgetLines: 7` permits five task rows in the expanded fullscreen widget; the preview remains capped at three. Smaller budgets also constrain the preview. Fullscreen widget mouse handling uses Pi's public 0.99.1 component APIs.
|
|
140
145
|
|
|
141
|
-
**Verification to date (not complete):** Prior feature checks passed 187 extension tests, 60 script tests, typecheck, and lint. A disposable Pi TUI with seeded tasks showed the modal's progress meter, grouped rows, scroll footer, and close behavior. Authenticated RPC turns exercised all six `todo` actions, and an authenticated TUI smoke observed widget and `/todos` updates. An RPC lifecycle check observed an empty new session and the completed task on switching back. Compaction replay remains unverified because Pi returned “Nothing to compact (session too small).” Language switching in the authenticated TODO flow remains unverified (an isolated TUI with both extensions did switch English to Spanish). Visual strikethrough of completed rows in the authenticated TUI remains unverified. Launch under an installed Osdy account profile remains unverified. These checks do not establish full feature parity or validate a published npm artifact.
|
|
146
|
+
**Verification to date (not complete):** The user confirmed fullscreen appearance and behavior via `pi:dev`. Automated PTY interaction passed 27 assertions, but fixture cleanup failed and the final task snapshot remains unverified; this was not a full end-to-end pass. Prior feature checks passed 187 extension tests, 60 script tests, typecheck, and lint. A disposable Pi TUI with seeded tasks showed the modal's progress meter, grouped rows, scroll footer, and close behavior. Authenticated RPC turns exercised all six `todo` actions, and an authenticated TUI smoke observed widget and `/todos` updates. An RPC lifecycle check observed an empty new session and the completed task on switching back. Compaction replay remains unverified because Pi returned “Nothing to compact (session too small).” Language switching in the authenticated TODO flow remains unverified (an isolated TUI with both extensions did switch English to Spanish). Visual strikethrough of completed rows in the authenticated TUI remains unverified. Launch under an installed Osdy account profile remains unverified. These checks do not establish full feature parity or validate a published npm artifact.
|
|
142
147
|
|
|
143
148
|
### TODO configuration and language
|
|
144
149
|
|
|
@@ -244,7 +249,7 @@ Before rename or removal, close this Pi process when it uses the target and **ma
|
|
|
244
249
|
|
|
245
250
|
| Area | Included behavior |
|
|
246
251
|
| --- | --- |
|
|
247
|
-
| Themes |
|
|
252
|
+
| Themes | 18 built-in themes, including Osdy, Kanagawa, Dracula, Gruvbox, Nord, Rosé Pine, Daniela Cute, Catppuccin, Matrix, and Lucent Orange palettes |
|
|
248
253
|
| Header and mascot | Independently selectable `osdy-theme`/`neon` headers and `current`/`bts` mascots |
|
|
249
254
|
| Messages | Native colored user cards without emojis and plain assistant Markdown with a separate assistant `🦝` row |
|
|
250
255
|
| Input | Responsive auto editor by default, with selectable simple Pi-native or extended framed modes |
|
|
@@ -258,6 +263,11 @@ Before rename or removal, close this Pi process when it uses the target and **ma
|
|
|
258
263
|
|
|
259
264
|
### Themes
|
|
260
265
|
|
|
266
|
+
The bundle contains **18 themes**; Gruvbox Dark, Nord, Rosé Pine base dark, and Daniela Cute are included starting in **1.8.0**. To try the **1.8.0 release candidate** before npm publication is confirmed:
|
|
267
|
+
|
|
268
|
+
1. Run `npm run pi:dev` from the 1.8.0 checkout, or `/reload` if that checkout is already loaded in Pi.
|
|
269
|
+
2. Open `/settings` → **Theme** and select `osdy-pi-gruvbox-dark`, `osdy-pi-nord`, `osdy-pi-rose-pine`, `osdy-pi-daniela-cute`, or another name below.
|
|
270
|
+
|
|
261
271
|
| Theme | Use |
|
|
262
272
|
| --- | --- |
|
|
263
273
|
| `osdy-pi-new` | Landing palette: cyan, violet, silver, and navy. |
|
|
@@ -268,6 +278,10 @@ Before rename or removal, close this Pi process when it uses the target and **ma
|
|
|
268
278
|
| `osdy-pi-kanagawa-dragon` | Kanagawa Dragon dark palette. |
|
|
269
279
|
| `osdy-pi-kanagawa-lotus` | Kanagawa Lotus light palette. |
|
|
270
280
|
| `osdy-pi-dracula` | Dracula Classic dark palette. |
|
|
281
|
+
| `osdy-pi-gruvbox-dark` | [Gruvbox](https://github.com/morhetz/gruvbox#palette) dark palette: warm, retro colors on the medium-contrast background. |
|
|
282
|
+
| `osdy-pi-nord` | [Nord](https://www.nordtheme.com/docs/colors-and-palettes) dark palette: cool, restrained Polar Night, Snow Storm, Frost, and Aurora colors. |
|
|
283
|
+
| `osdy-pi-rose-pine` | [Rosé Pine](https://rosepinetheme.com/palette/ingredients/) base dark palette: soft colors, not Moon or Dawn. |
|
|
284
|
+
| `osdy-pi-daniela-cute` | Daniela Cute: navy panels/export page (`#0B1220`), blue cards (`#111F33`), borders (`#253A55`), blue accents (`#3584E4`), main text (`#D8E2EF`), secondary text (`#93A6BE`), and selection (`#193655`). Distinct status and syntax colors remain; the terminal background remains unchanged. |
|
|
271
285
|
| `osdy-pi-catppuccin-latte` | Catppuccin Latte light palette. |
|
|
272
286
|
| `osdy-pi-catppuccin-frappe` | Catppuccin Frappé dark palette. |
|
|
273
287
|
| `osdy-pi-catppuccin-macchiato` | Catppuccin Macchiato dark palette. |
|
|
@@ -408,7 +408,8 @@ void test("every registered theme defines native user message colors", () => {
|
|
|
408
408
|
readFileSync(new URL("../../package.json", import.meta.url), "utf8"),
|
|
409
409
|
) as { pi: { themes: string[] } };
|
|
410
410
|
|
|
411
|
-
assert.equal(manifest.pi.themes.length,
|
|
411
|
+
assert.equal(manifest.pi.themes.length, 18);
|
|
412
|
+
assert.equal(new Set(manifest.pi.themes).size, 18, "theme registrations must be unique");
|
|
412
413
|
for (const themePath of manifest.pi.themes) {
|
|
413
414
|
const theme = JSON.parse(
|
|
414
415
|
readFileSync(
|
|
@@ -427,6 +428,29 @@ void test("every registered theme defines native user message colors", () => {
|
|
|
427
428
|
}
|
|
428
429
|
});
|
|
429
430
|
|
|
431
|
+
void test("new dark themes are explicitly registered with unique matching names", () => {
|
|
432
|
+
const manifest = JSON.parse(
|
|
433
|
+
readFileSync(new URL("../../package.json", import.meta.url), "utf8"),
|
|
434
|
+
) as { pi: { themes: string[] } };
|
|
435
|
+
const names = manifest.pi.themes.map((path) => {
|
|
436
|
+
const theme = JSON.parse(readFileSync(new URL(`../../${path}`, import.meta.url), "utf8")) as { name: string };
|
|
437
|
+
assert.equal(path, `./themes/${theme.name}.json`);
|
|
438
|
+
return theme.name;
|
|
439
|
+
});
|
|
440
|
+
assert.equal(new Set(names).size, names.length, "theme names must be unique");
|
|
441
|
+
for (const name of ["osdy-pi-gruvbox-dark", "osdy-pi-nord", "osdy-pi-rose-pine", "osdy-pi-daniela-cute"]) {
|
|
442
|
+
assert.equal(manifest.pi.themes.filter((path) => path === `./themes/${name}.json`).length, 1);
|
|
443
|
+
assert.equal(names.filter((registered) => registered === name).length, 1);
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
void test("README documents all 18 themes including Daniela Cute", () => {
|
|
448
|
+
const readme = readFileSync(new URL("../../README.md", import.meta.url), "utf8");
|
|
449
|
+
assert.match(readme, /\*\*18 themes\*\*/);
|
|
450
|
+
assert.match(readme, /18 built-in themes/);
|
|
451
|
+
assert.match(readme, /\| `osdy-pi-daniela-cute` \| Daniela Cute/);
|
|
452
|
+
});
|
|
453
|
+
|
|
430
454
|
void test("Tokyo Night retains its approved native user card colors", () => {
|
|
431
455
|
const theme = JSON.parse(
|
|
432
456
|
readFileSync(
|
|
@@ -5,6 +5,7 @@ import test from "node:test";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import ts from "typescript";
|
|
7
7
|
import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import type { Component, TuiMouseEvent, TuiMode } from "@earendil-works/pi-tui";
|
|
8
9
|
import type { TodoConfig } from "./todo-config.js";
|
|
9
10
|
import type { TodoI18n } from "./todo-i18n.js";
|
|
10
11
|
|
|
@@ -25,11 +26,11 @@ registerHooks({
|
|
|
25
26
|
const { registerTodoWidget } = await import("./todo-widget.js");
|
|
26
27
|
const { createTodoSessionStore } = await import("./todo-session.js");
|
|
27
28
|
const { applyTodo } = await import("./todo-domain.js");
|
|
28
|
-
const { visibleWidth } = await import("@earendil-works/pi-tui");
|
|
29
|
+
const { visibleWidth, Container } = await import("@earendil-works/pi-tui");
|
|
29
30
|
const theme = { fg: (_color: string, text: string) => text, strikethrough: (text: string) => `~${text}~` };
|
|
30
31
|
function harness(config: () => TodoConfig = () => ({}), i18n?: TodoI18n) {
|
|
31
32
|
type Context = { hasUI: boolean; ui: typeof ui; sessionManager: { getSessionId(): string } };
|
|
32
|
-
type Factory = (tui: { requestRender(force?: boolean): void }, theme: Theme) =>
|
|
33
|
+
type Factory = (tui: { mode: TuiMode; requestRender(force?: boolean): void }, theme: Theme) => Component;
|
|
33
34
|
const handlers = new Map<string, ((event: { toolName?: string }, ctx: Context) => void)[]>();
|
|
34
35
|
const shortcuts = new Map<string, (ctx: Context) => void>();
|
|
35
36
|
const widgets: Array<{ key: string; factory: Factory | undefined; options: { placement: string } | undefined }> = [];
|
|
@@ -42,8 +43,27 @@ function harness(config: () => TodoConfig = () => ({}), i18n?: TodoI18n) {
|
|
|
42
43
|
const emit = (name: string, context: Context, event: { toolName?: string } = {}) => { for (const handler of handlers.get(name) ?? []) handler(event, context); };
|
|
43
44
|
const state = (id: string, tasks: { id: number; subject: string; status: "pending" | "in_progress" | "completed" | "deleted"; blockedBy?: number[]; activeForm?: string }[]) => store.set(id, { tasks, nextId: Math.max(1, ...tasks.map((t) => t.id + 1)) });
|
|
44
45
|
const action = (id: string, kind: "create" | "clear", subject?: string) => store.set(id, applyTodo(store.get(id), kind, subject === undefined ? {} : { subject }).state);
|
|
45
|
-
const
|
|
46
|
-
|
|
46
|
+
const host = { mode: "fullscreen" as TuiMode, requestRender(force?: boolean) { renders.push(force === true); } };
|
|
47
|
+
let factory: Factory | undefined;
|
|
48
|
+
let component: Component | undefined;
|
|
49
|
+
const current = () => {
|
|
50
|
+
if (factory !== widgets.at(-1)?.factory) {
|
|
51
|
+
factory = widgets.at(-1)?.factory;
|
|
52
|
+
component = factory?.(host, theme as Theme);
|
|
53
|
+
}
|
|
54
|
+
return component;
|
|
55
|
+
};
|
|
56
|
+
const lines = (width = 80): string[] => current()?.render(width) ?? [];
|
|
57
|
+
const mouse = (change: Partial<TuiMouseEvent> = {}, clippedHeight?: number) => {
|
|
58
|
+
const child = current();
|
|
59
|
+
if (!child) return undefined;
|
|
60
|
+
const container = new Container(); container.addChild(child);
|
|
61
|
+
const rendered = container.render(80);
|
|
62
|
+
return container.handleMouse({ type: "click", button: "left", x: 0, y: 0,
|
|
63
|
+
screenX: 0, screenY: 0, width: 80, height: clippedHeight ?? rendered.length,
|
|
64
|
+
shift: false, alt: false, ctrl: false, ...change });
|
|
65
|
+
};
|
|
66
|
+
return { ui, host, widgets, renders, shortcuts, ctx, emit, state, action, lines, mouse, store };
|
|
47
67
|
}
|
|
48
68
|
const task = (id: number, status: "pending" | "in_progress" | "completed" | "deleted" = "pending", extra: { activeForm?: string; blockedBy?: number[] } = {}) => ({ id, status, subject: `Task ${id}`, ...extra });
|
|
49
69
|
|
|
@@ -102,24 +122,23 @@ void test("deleted tasks remain excluded while completed and pending tasks persi
|
|
|
102
122
|
assert.deepEqual(h.lines(), []);
|
|
103
123
|
});
|
|
104
124
|
|
|
105
|
-
void test("default widget shows
|
|
125
|
+
void test("default widget shows three task rows at the six-task boundary, with a truthful hint", () => {
|
|
106
126
|
const h = harness(); const c = h.ctx("main"); h.emit("session_start", c);
|
|
107
127
|
h.state("main", Array.from({ length: 6 }, (_, i) => task(i + 1)));
|
|
108
128
|
h.emit("tool_execution_end", c, { toolName: "todo" });
|
|
109
|
-
assert.deepEqual(h.lines(), ["● Todos (0/6)", ...Array.from({ length:
|
|
110
|
-
h.ui.expanded = true;
|
|
111
|
-
assert.equal(h.lines().length,
|
|
112
|
-
assert.equal(h.lines()[6], "└─ ○ Task 6");
|
|
129
|
+
assert.deepEqual(h.lines(), ["● Todos (0/6)", ...Array.from({ length: 3 }, (_, i) => `├─ ○ Task ${i + 1}`), "└─ +3 more (3 pending)", ""]);
|
|
130
|
+
h.ui.expanded = true; // Pi's tool expansion is not the widget's display preference.
|
|
131
|
+
assert.equal(h.lines().length, 6);
|
|
113
132
|
});
|
|
114
133
|
|
|
115
|
-
void test("
|
|
134
|
+
void test("three or fewer preview rows retain completed subjects and completed overflow counts", () => {
|
|
116
135
|
const h = harness(); const c = h.ctx("main"); h.emit("session_start", c);
|
|
117
|
-
h.state("main", [task(1, "completed"),
|
|
136
|
+
h.state("main", [task(1, "completed"), task(2), task(3)]);
|
|
118
137
|
h.emit("tool_execution_end", c, { toolName: "todo" });
|
|
119
|
-
assert.deepEqual(h.lines(), ["● Todos (1/
|
|
138
|
+
assert.deepEqual(h.lines(), ["● Todos (1/3)", "├─ ✓ ~Task 1~", "├─ ○ Task 2", "└─ ○ Task 3", ""]);
|
|
120
139
|
h.state("main", [task(1, "completed"), ...Array.from({ length: 5 }, (_, i) => task(i + 2, "completed"))]);
|
|
121
|
-
assert.equal(h.lines().length,
|
|
122
|
-
assert.equal(h.lines().at(-2), "└─ +
|
|
140
|
+
assert.equal(h.lines().length, 6);
|
|
141
|
+
assert.equal(h.lines().at(-2), "└─ +3 more (3 completed)");
|
|
123
142
|
assert.match(h.lines()[1] ?? "", /~Task 1~/);
|
|
124
143
|
});
|
|
125
144
|
|
|
@@ -127,15 +146,16 @@ void test("default widget retains completed strikethrough when overflow contains
|
|
|
127
146
|
const h = harness(); const c = h.ctx("main"); h.emit("session_start", c);
|
|
128
147
|
h.state("main", [task(1, "completed"), ...Array.from({ length: 14 }, (_, i) => task(i + 2))]);
|
|
129
148
|
h.emit("tool_execution_end", c, { toolName: "todo" });
|
|
130
|
-
assert.deepEqual(h.lines(), ["● Todos (1/15)", ...Array.from({ length:
|
|
149
|
+
assert.deepEqual(h.lines(), ["● Todos (1/15)", ...Array.from({ length: 3 }, (_, i) => `├─ ○ Task ${i + 2}`), "└─ +12 more (1 completed, 11 pending)", ""]);
|
|
131
150
|
h.ui.expanded = true;
|
|
132
|
-
assert.equal(h.lines().length,
|
|
151
|
+
assert.equal(h.lines().length, 6);
|
|
152
|
+
h.mouse();
|
|
133
153
|
assert.equal(h.lines()[1], "├─ ✓ ~Task 1~");
|
|
134
154
|
});
|
|
135
155
|
|
|
136
156
|
void test("configured widget budget preserves small and boundary lists without hiding overflow", () => {
|
|
137
157
|
for (const maxWidgetLines of [3, 7.5, 8]) {
|
|
138
|
-
const slots = Math.floor(maxWidgetLines - 2);
|
|
158
|
+
const slots = Math.min(3, Math.floor(maxWidgetLines - 2));
|
|
139
159
|
const h = harness(() => ({ maxWidgetLines })); const c = h.ctx("main"); h.emit("session_start", c);
|
|
140
160
|
h.state("main", Array.from({ length: slots }, (_, i) => task(i + 1)));
|
|
141
161
|
h.emit("tool_execution_end", c, { toolName: "todo" });
|
|
@@ -147,12 +167,12 @@ void test("configured widget budget preserves small and boundary lists without h
|
|
|
147
167
|
}
|
|
148
168
|
});
|
|
149
169
|
|
|
150
|
-
void test("overflow drops completed first and
|
|
170
|
+
void test("overflow drops completed first and ignores Pi tool expansion", () => {
|
|
151
171
|
const h = harness(); const c = h.ctx("main"); h.emit("session_start", c);
|
|
152
172
|
h.state("main", Array.from({ length: 15 }, (_, i) => task(i + 1, i % 3 === 0 ? "completed" : "pending")));
|
|
153
173
|
h.emit("tool_execution_end", c, { toolName: "todo" });
|
|
154
|
-
assert.equal(h.lines().length,
|
|
155
|
-
h.ui.expanded = true; assert.equal(h.lines().length,
|
|
174
|
+
assert.equal(h.lines().length, 6); assert.match(h.lines()[4] ?? "", /\+12 more \(5 completed, 7 pending\)/);
|
|
175
|
+
h.ui.expanded = true; assert.equal(h.lines().length, 6);
|
|
156
176
|
});
|
|
157
177
|
|
|
158
178
|
void test("branch lifecycle retains completed rows and rebinds replacement UI safely", () => {
|
|
@@ -174,7 +194,7 @@ void test("overflow unfinished tail reports pending and hides completed first",
|
|
|
174
194
|
const h = harness(); const c = h.ctx("main"); h.emit("session_start", c);
|
|
175
195
|
h.state("main", [task(1, "completed"), ...Array.from({ length: 14 }, (_, i) => task(i + 2))]);
|
|
176
196
|
h.emit("tool_execution_end", c, { toolName: "todo" });
|
|
177
|
-
assert.match(h.lines()[
|
|
197
|
+
assert.match(h.lines()[4] ?? "", /\+12 more \(1 completed, 11 pending\)/);
|
|
178
198
|
assert.doesNotMatch(h.lines().join(" "), /Task 1(?!\d)/);
|
|
179
199
|
});
|
|
180
200
|
|
|
@@ -185,7 +205,7 @@ void test("configured budget changes each render while key binds once and hint f
|
|
|
185
205
|
h.emit("tool_execution_end", c, { toolName: "todo" });
|
|
186
206
|
assert.equal(h.lines().length, 4);
|
|
187
207
|
config = { maxWidgetLines: 8, collapseKey: "ctrl+g" };
|
|
188
|
-
assert.equal(h.lines().length,
|
|
208
|
+
assert.equal(h.lines().length, 6);
|
|
189
209
|
assert.equal(h.shortcuts.has("ctrl+g"), false);
|
|
190
210
|
h.shortcuts.get("alt+f5")?.(c);
|
|
191
211
|
assert.match(h.lines()[1] ?? "", /ctrl\+g to expand/);
|
|
@@ -224,6 +244,84 @@ void test("widget reads live translations for heading, overflow, statuses and co
|
|
|
224
244
|
assert.equal(h.lines()[1], "└─ ctrl+shift+t to expand");
|
|
225
245
|
});
|
|
226
246
|
|
|
247
|
+
void test("fullscreen header alone toggles bounded expansion; regular mode stays compact", () => {
|
|
248
|
+
const h = harness(); const c = h.ctx("main");
|
|
249
|
+
h.state("main", Array.from({ length: 8 }, (_, i) => task(i + 1)));
|
|
250
|
+
h.emit("session_start", c);
|
|
251
|
+
const original = structuredClone(h.store.get("main"));
|
|
252
|
+
assert.equal(h.mouse({ button: "right" }), undefined);
|
|
253
|
+
assert.equal(h.mouse({ y: 1 }), undefined);
|
|
254
|
+
assert.equal(h.mouse({ type: "press" }), undefined);
|
|
255
|
+
assert.equal(h.mouse({ type: "wheel", wheelDelta: 1 }), undefined);
|
|
256
|
+
h.host.mode = "regular";
|
|
257
|
+
assert.equal(h.mouse(), undefined);
|
|
258
|
+
assert.equal(h.lines().length, 6);
|
|
259
|
+
h.host.mode = "fullscreen";
|
|
260
|
+
assert.equal(h.mouse()?.handled, true);
|
|
261
|
+
assert.equal(h.lines().length, 8); // bounded to five tasks, not all eight
|
|
262
|
+
assert.equal(h.mouse()?.handled, true);
|
|
263
|
+
assert.equal(h.lines().length, 6);
|
|
264
|
+
assert.deepEqual(h.store.get("main"), original);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
void test("real Container intrinsic-height dispatch reaches the last task with only one dock task row", () => {
|
|
268
|
+
const h = harness(); const c = h.ctx("main");
|
|
269
|
+
h.state("main", Array.from({ length: 9 }, (_, i) => task(i + 1)));
|
|
270
|
+
h.emit("session_start", c); h.mouse({}, 2);
|
|
271
|
+
const original = structuredClone(h.store.get("main"));
|
|
272
|
+
for (let id = 1; id <= 9; id++) {
|
|
273
|
+
const clippedDock = h.lines().slice(0, 2);
|
|
274
|
+
assert.equal(clippedDock[0], "● Todos (0/9)");
|
|
275
|
+
assert.match(clippedDock[1] ?? "", new RegExp(`Task ${id}$`));
|
|
276
|
+
const result = h.mouse({ type: "wheel", button: "none", y: 1, wheelDelta: 7 }, 2);
|
|
277
|
+
assert.equal(result?.handled, true);
|
|
278
|
+
assert.ok((result?.target.height ?? 0) > 2); // Container forwards intrinsic height
|
|
279
|
+
}
|
|
280
|
+
assert.match(h.lines()[1] ?? "", /Task 9$/);
|
|
281
|
+
assert.equal(h.mouse({ type: "wheel", wheelDelta: 0 }, 2)?.handled, true);
|
|
282
|
+
assert.equal(h.mouse({ type: "wheel", wheelDelta: Number.NaN }, 2)?.handled, true);
|
|
283
|
+
assert.match(h.lines()[1] ?? "", /Task 9$/);
|
|
284
|
+
assert.equal(h.mouse({ type: "wheel", wheelDelta: -3 }, 2)?.handled, true);
|
|
285
|
+
assert.match(h.lines()[1] ?? "", /Task 8$/);
|
|
286
|
+
h.host.mode = "regular";
|
|
287
|
+
assert.equal(h.mouse({ type: "wheel", wheelDelta: 1 }), undefined);
|
|
288
|
+
assert.equal(h.lines().length, 6);
|
|
289
|
+
h.host.mode = "fullscreen";
|
|
290
|
+
assert.match(h.lines()[1] ?? "", /Task 8$/);
|
|
291
|
+
assert.deepEqual(h.store.get("main"), original);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
void test("expanded offsets survive refresh and tree, clamp on deletion and reset on replacement/shutdown", () => {
|
|
295
|
+
const h = harness(); const c = h.ctx("main");
|
|
296
|
+
h.state("main", Array.from({ length: 7 }, (_, i) => task(i + 1)));
|
|
297
|
+
h.emit("session_start", c); h.mouse();
|
|
298
|
+
for (let i = 0; i < 6; i++) h.mouse({ type: "wheel", wheelDelta: 1 });
|
|
299
|
+
h.renders.length = 0;
|
|
300
|
+
h.emit("tool_execution_end", c, { toolName: "todo" });
|
|
301
|
+
assert.deepEqual(h.renders, [false]);
|
|
302
|
+
assert.match(h.lines()[1] ?? "", /Task 7$/);
|
|
303
|
+
h.emit("session_tree", c); h.emit("session_compact", c);
|
|
304
|
+
assert.match(h.lines()[1] ?? "", /Task 7$/);
|
|
305
|
+
h.state("main", [task(1, "completed"), task(2, "deleted"), task(3, "in_progress")]);
|
|
306
|
+
h.emit("tool_execution_end", c, { toolName: "todo" });
|
|
307
|
+
assert.equal(h.lines()[0], "● Todos (1/2)");
|
|
308
|
+
assert.match(h.lines()[1] ?? "", /Task 3$/);
|
|
309
|
+
assert.match(h.lines().at(-2) ?? "", /\+1 more \(1 completed\)/);
|
|
310
|
+
h.shortcuts.get("ctrl+shift+t")?.(c);
|
|
311
|
+
assert.match(h.lines()[1] ?? "", /to expand/);
|
|
312
|
+
assert.equal(h.mouse({ type: "wheel", wheelDelta: 1 }), undefined);
|
|
313
|
+
h.shortcuts.get("ctrl+shift+t")?.(c);
|
|
314
|
+
assert.match(h.lines()[1] ?? "", /Task 3$/);
|
|
315
|
+
const next = h.ctx("next"); // same foreground UI denotes session replacement
|
|
316
|
+
h.state("next", Array.from({ length: 7 }, (_, i) => task(i + 1)));
|
|
317
|
+
h.emit("session_start", next);
|
|
318
|
+
assert.equal(h.lines().length, 6);
|
|
319
|
+
h.mouse(); assert.match(h.lines()[1] ?? "", /Task 1$/);
|
|
320
|
+
h.emit("session_shutdown", c); assert.notEqual(h.widgets.at(-1)?.factory, undefined);
|
|
321
|
+
h.emit("session_shutdown", next); assert.equal(h.widgets.at(-1)?.factory, undefined);
|
|
322
|
+
h.emit("session_start", next); assert.equal(h.lines().length, 6);
|
|
323
|
+
});
|
|
324
|
+
|
|
227
325
|
void test("headless and child sessions do not bind, refresh, collapse or dispose foreground", () => {
|
|
228
326
|
const h = harness(); const main = h.ctx("main"), child = h.ctx("child", true, { ...h.ui });
|
|
229
327
|
h.emit("session_start", h.ctx("headless", false)); h.state("headless", [task(1)]); h.emit("tool_execution_end", h.ctx("headless", false), { toolName: "todo" }); assert.equal(h.widgets.length, 0);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Widget behavior adapted independently from @juicesharp/rpiv-todo@2.11.0 (MIT); see root LICENSE.
|
|
2
2
|
import type { ExtensionAPI, ExtensionContext, ExtensionUIContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { truncateToWidth, type TUI } from "@earendil-works/pi-tui";
|
|
3
|
+
import { truncateToWidth, type TUI, type TuiMouseEvent, type TuiMouseEventResult } from "@earendil-works/pi-tui";
|
|
4
4
|
import { COLLAPSE_KEY_OFF, getMaxWidgetLines, loadTodoConfig, resolveCollapseKey, type TodoConfig } from "./todo-config.js";
|
|
5
5
|
import type { TodoTask } from "./todo-domain.js";
|
|
6
6
|
import { todoI18n, type TodoI18n } from "./todo-i18n.js";
|
|
@@ -42,6 +42,9 @@ export function registerTodoWidget(pi: ExtensionAPI, store: Store, config: () =>
|
|
|
42
42
|
let mounted = false;
|
|
43
43
|
let tui: TUI | undefined;
|
|
44
44
|
let collapsed = false;
|
|
45
|
+
let expanded = false;
|
|
46
|
+
let offset = 0;
|
|
47
|
+
const clampOffset = (length: number) => { offset = Math.max(0, Math.min(offset, length - 1)); };
|
|
45
48
|
const visible = () => store.get(foreground ?? "").tasks.filter((task) => task.status !== "deleted");
|
|
46
49
|
const render = (theme: Theme, width: number): string[] => {
|
|
47
50
|
const tasks = visible();
|
|
@@ -55,8 +58,14 @@ export function registerTodoWidget(pi: ExtensionAPI, store: Store, config: () =>
|
|
|
55
58
|
const hint = key === COLLAPSE_KEY_OFF ? i18n.t("overlay.collapsed", "collapsed") : i18n.t("overlay.expandHint", "{key} to expand").replace("{key}", key);
|
|
56
59
|
return [heading, trunc(`${theme.fg("dim", "└─")} ${theme.fg("dim", hint)}`), ""];
|
|
57
60
|
}
|
|
58
|
-
|
|
59
|
-
const
|
|
61
|
+
clampOffset(tasks.length);
|
|
62
|
+
const slots = Math.max(1, Math.floor(getMaxWidgetLines(config()) - 2));
|
|
63
|
+
const viewport = expanded && tui?.mode === "fullscreen";
|
|
64
|
+
const rows = viewport ? tasks.slice(offset, offset + slots) : layout(tasks, Math.min(3, slots) + 1).rows;
|
|
65
|
+
const kept = new Set(rows);
|
|
66
|
+
const omitted = tasks.filter((task) => !kept.has(task));
|
|
67
|
+
const completed = omitted.filter((task) => task.status === "completed").length;
|
|
68
|
+
const pending = omitted.length - completed;
|
|
60
69
|
const ids = tasks.some((task) => (task.blockedBy?.length ?? 0) > 0);
|
|
61
70
|
const lines = [heading];
|
|
62
71
|
for (const task of rows) {
|
|
@@ -72,14 +81,29 @@ export function registerTodoWidget(pi: ExtensionAPI, store: Store, config: () =>
|
|
|
72
81
|
lines.push("");
|
|
73
82
|
return lines;
|
|
74
83
|
};
|
|
84
|
+
const handleMouse = (event: TuiMouseEvent): TuiMouseEventResult | undefined => {
|
|
85
|
+
if (tui?.mode !== "fullscreen" || !visible().length) return undefined;
|
|
86
|
+
if (event.type === "click" && event.button === "left" && event.y === 0) {
|
|
87
|
+
collapsed = false; expanded = !expanded; offset = 0;
|
|
88
|
+
} else if (event.type === "wheel" && expanded && !collapsed) {
|
|
89
|
+
// Container supplies intrinsic height, not the clipped dock height. Move one
|
|
90
|
+
// task per event, allowing the final task to reach the first row below heading.
|
|
91
|
+
const delta = event.wheelDelta ?? 0;
|
|
92
|
+
if (Number.isFinite(delta)) offset += Math.sign(delta);
|
|
93
|
+
clampOffset(visible().length);
|
|
94
|
+
} else return undefined;
|
|
95
|
+
tui.requestRender();
|
|
96
|
+
return { handled: true };
|
|
97
|
+
};
|
|
75
98
|
const refresh = () => {
|
|
99
|
+
clampOffset(visible().length);
|
|
76
100
|
if (!ui) return;
|
|
77
101
|
if (!visible().length) {
|
|
78
102
|
if (mounted) { ui.setWidget(KEY, undefined); mounted = false; tui = undefined; }
|
|
79
103
|
} else if (!mounted) {
|
|
80
104
|
ui.setWidget(KEY, (host, theme) => {
|
|
81
105
|
tui = host;
|
|
82
|
-
return { render: (width: number) => render(ui?.theme ?? theme, width), invalidate() {} };
|
|
106
|
+
return { render: (width: number) => render(ui?.theme ?? theme, width), handleMouse, invalidate() {} };
|
|
83
107
|
}, { placement: "aboveEditor" });
|
|
84
108
|
mounted = true;
|
|
85
109
|
} else tui?.requestRender();
|
|
@@ -89,7 +113,10 @@ export function registerTodoWidget(pi: ExtensionAPI, store: Store, config: () =>
|
|
|
89
113
|
pi.on("session_start", (_event, ctx) => {
|
|
90
114
|
if (!ctx.hasUI) return;
|
|
91
115
|
const session = id(ctx);
|
|
92
|
-
|
|
116
|
+
// A different session on another UI is a background child. The foreground
|
|
117
|
+
// UI reused for a new session is replacement; tree/compact keep preference.
|
|
118
|
+
if (foreground !== undefined && foreground !== session && ctx.ui !== ui) return;
|
|
119
|
+
if (foreground !== session) { collapsed = false; expanded = false; offset = 0; }
|
|
93
120
|
if (ui && ui !== ctx.ui && mounted) ui.setWidget(KEY, undefined);
|
|
94
121
|
if (ui !== ctx.ui) { mounted = false; tui = undefined; }
|
|
95
122
|
foreground = session; ui = ctx.ui; refresh();
|
|
@@ -99,7 +126,7 @@ export function registerTodoWidget(pi: ExtensionAPI, store: Store, config: () =>
|
|
|
99
126
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
100
127
|
if (!foregroundEvent(ctx)) return;
|
|
101
128
|
if (mounted) ui?.setWidget(KEY, undefined);
|
|
102
|
-
mounted = false; tui = undefined; ui = undefined; foreground = undefined; collapsed = false;
|
|
129
|
+
mounted = false; tui = undefined; ui = undefined; foreground = undefined; collapsed = false; expanded = false; offset = 0;
|
|
103
130
|
});
|
|
104
131
|
pi.on("tool_execution_end", (event, ctx) => {
|
|
105
132
|
if (event.toolName === "todo" && event.isError !== true && foregroundEvent(ctx)) refresh();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "osdy-pi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Osdy Pi package: themes, header, and custom editor for Pi.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,6 +41,10 @@
|
|
|
41
41
|
"./themes/osdy-pi-kanagawa-dragon.json",
|
|
42
42
|
"./themes/osdy-pi-kanagawa-lotus.json",
|
|
43
43
|
"./themes/osdy-pi-dracula.json",
|
|
44
|
+
"./themes/osdy-pi-gruvbox-dark.json",
|
|
45
|
+
"./themes/osdy-pi-nord.json",
|
|
46
|
+
"./themes/osdy-pi-rose-pine.json",
|
|
47
|
+
"./themes/osdy-pi-daniela-cute.json",
|
|
44
48
|
"./themes/osdy-pi-catppuccin-latte.json",
|
|
45
49
|
"./themes/osdy-pi-catppuccin-frappe.json",
|
|
46
50
|
"./themes/osdy-pi-catppuccin-macchiato.json",
|
|
@@ -65,8 +69,8 @@
|
|
|
65
69
|
}
|
|
66
70
|
},
|
|
67
71
|
"devDependencies": {
|
|
68
|
-
"@earendil-works/pi-coding-agent": "
|
|
69
|
-
"@earendil-works/pi-tui": "
|
|
72
|
+
"@earendil-works/pi-coding-agent": "0.99.1",
|
|
73
|
+
"@earendil-works/pi-tui": "0.99.1",
|
|
70
74
|
"@eslint/js": "^9.39.1",
|
|
71
75
|
"@types/node": "^25.8.0",
|
|
72
76
|
"eslint": "^9.39.1",
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
|
+
"name": "osdy-pi-daniela-cute",
|
|
4
|
+
"appearance": "dark",
|
|
5
|
+
"vars": {
|
|
6
|
+
"navy": "#0B1220",
|
|
7
|
+
"card": "#111F33",
|
|
8
|
+
"border": "#253A55",
|
|
9
|
+
"cobalt": "#3584E4",
|
|
10
|
+
"selection": "#193655",
|
|
11
|
+
"text": "#D8E2EF",
|
|
12
|
+
"muted": "#93A6BE",
|
|
13
|
+
"dim": "#7895BA",
|
|
14
|
+
"green": "#32D583",
|
|
15
|
+
"red": "#FF5364",
|
|
16
|
+
"yellow": "#FFC247",
|
|
17
|
+
"cyan": "#22D3EE",
|
|
18
|
+
"violet": "#A970FF",
|
|
19
|
+
"orange": "#FF963C",
|
|
20
|
+
"pink": "#F54DB8"
|
|
21
|
+
},
|
|
22
|
+
"colors": {
|
|
23
|
+
"accent": "cobalt",
|
|
24
|
+
"border": "border",
|
|
25
|
+
"borderAccent": "cobalt",
|
|
26
|
+
"borderMuted": "border",
|
|
27
|
+
"success": "green",
|
|
28
|
+
"error": "red",
|
|
29
|
+
"warning": "yellow",
|
|
30
|
+
"muted": "muted",
|
|
31
|
+
"dim": "dim",
|
|
32
|
+
"text": "text",
|
|
33
|
+
"thinkingText": "muted",
|
|
34
|
+
"selectedBg": "selection",
|
|
35
|
+
"scrollbarTrack": "card",
|
|
36
|
+
"scrollbarThumb": "cobalt",
|
|
37
|
+
"searchMatchBg": "yellow",
|
|
38
|
+
"searchMatchText": "navy",
|
|
39
|
+
"userMessageBg": "card",
|
|
40
|
+
"userMessageText": "text",
|
|
41
|
+
"customMessageBg": "card",
|
|
42
|
+
"customMessageText": "text",
|
|
43
|
+
"customMessageLabel": "cyan",
|
|
44
|
+
"toolPendingBg": "navy",
|
|
45
|
+
"toolSuccessBg": "navy",
|
|
46
|
+
"toolErrorBg": "navy",
|
|
47
|
+
"toolTitle": "cyan",
|
|
48
|
+
"toolOutput": "text",
|
|
49
|
+
"mdHeading": "cyan",
|
|
50
|
+
"mdLink": "cyan",
|
|
51
|
+
"mdLinkUrl": "muted",
|
|
52
|
+
"mdCode": "green",
|
|
53
|
+
"mdCodeBlock": "text",
|
|
54
|
+
"mdCodeBlockBorder": "border",
|
|
55
|
+
"mdQuote": "muted",
|
|
56
|
+
"mdQuoteBorder": "cobalt",
|
|
57
|
+
"mdHr": "border",
|
|
58
|
+
"mdListBullet": "orange",
|
|
59
|
+
"toolDiffAdded": "green",
|
|
60
|
+
"toolDiffRemoved": "red",
|
|
61
|
+
"toolDiffContext": "muted",
|
|
62
|
+
"syntaxComment": "dim",
|
|
63
|
+
"syntaxKeyword": "violet",
|
|
64
|
+
"syntaxFunction": "cyan",
|
|
65
|
+
"syntaxVariable": "text",
|
|
66
|
+
"syntaxString": "green",
|
|
67
|
+
"syntaxNumber": "orange",
|
|
68
|
+
"syntaxType": "yellow",
|
|
69
|
+
"syntaxOperator": "pink",
|
|
70
|
+
"syntaxPunctuation": "muted",
|
|
71
|
+
"thinkingOff": "dim",
|
|
72
|
+
"thinkingMinimal": "cobalt",
|
|
73
|
+
"thinkingLow": "cyan",
|
|
74
|
+
"thinkingMedium": "green",
|
|
75
|
+
"thinkingHigh": "violet",
|
|
76
|
+
"thinkingXhigh": "orange",
|
|
77
|
+
"thinkingMax": "pink",
|
|
78
|
+
"bashMode": "green"
|
|
79
|
+
},
|
|
80
|
+
"export": {
|
|
81
|
+
"pageBg": "navy",
|
|
82
|
+
"cardBg": "card",
|
|
83
|
+
"infoBg": "navy"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
|
+
"name": "osdy-pi-gruvbox-dark",
|
|
4
|
+
"appearance": "dark",
|
|
5
|
+
"vars": {
|
|
6
|
+
"bg0": "#282828",
|
|
7
|
+
"bg1": "#3c3836",
|
|
8
|
+
"bg2": "#504945",
|
|
9
|
+
"bg3": "#665c54",
|
|
10
|
+
"bg4": "#7c6f64",
|
|
11
|
+
"gray": "#928374",
|
|
12
|
+
"fg0": "#fbf1c7",
|
|
13
|
+
"fg1": "#ebdbb2",
|
|
14
|
+
"fg2": "#d5c4a1",
|
|
15
|
+
"fg3": "#bdae93",
|
|
16
|
+
"fg4": "#a89984",
|
|
17
|
+
"red": "#fb4934",
|
|
18
|
+
"green": "#b8bb26",
|
|
19
|
+
"yellow": "#fabd2f",
|
|
20
|
+
"blue": "#83a598",
|
|
21
|
+
"purple": "#d3869b",
|
|
22
|
+
"aqua": "#8ec07c",
|
|
23
|
+
"orange": "#fe8019"
|
|
24
|
+
},
|
|
25
|
+
"colors": {
|
|
26
|
+
"accent": "yellow",
|
|
27
|
+
"border": "gray",
|
|
28
|
+
"borderAccent": "orange",
|
|
29
|
+
"borderMuted": "bg2",
|
|
30
|
+
"success": "green",
|
|
31
|
+
"error": "red",
|
|
32
|
+
"warning": "yellow",
|
|
33
|
+
"muted": "fg4",
|
|
34
|
+
"dim": "gray",
|
|
35
|
+
"text": "fg1",
|
|
36
|
+
"thinkingText": "fg4",
|
|
37
|
+
"selectedBg": "bg2",
|
|
38
|
+
"scrollbarTrack": "bg1",
|
|
39
|
+
"scrollbarThumb": "gray",
|
|
40
|
+
"searchMatchBg": "yellow",
|
|
41
|
+
"searchMatchText": "bg0",
|
|
42
|
+
"userMessageBg": "bg1",
|
|
43
|
+
"userMessageText": "fg1",
|
|
44
|
+
"customMessageBg": "bg1",
|
|
45
|
+
"customMessageText": "fg1",
|
|
46
|
+
"customMessageLabel": "aqua",
|
|
47
|
+
"toolPendingBg": "bg0",
|
|
48
|
+
"toolSuccessBg": "bg1",
|
|
49
|
+
"toolErrorBg": "bg1",
|
|
50
|
+
"toolTitle": "yellow",
|
|
51
|
+
"toolOutput": "fg1",
|
|
52
|
+
"mdHeading": "yellow",
|
|
53
|
+
"mdLink": "blue",
|
|
54
|
+
"mdLinkUrl": "fg4",
|
|
55
|
+
"mdCode": "aqua",
|
|
56
|
+
"mdCodeBlock": "fg1",
|
|
57
|
+
"mdCodeBlockBorder": "gray",
|
|
58
|
+
"mdQuote": "fg3",
|
|
59
|
+
"mdQuoteBorder": "aqua",
|
|
60
|
+
"mdHr": "bg3",
|
|
61
|
+
"mdListBullet": "orange",
|
|
62
|
+
"toolDiffAdded": "green",
|
|
63
|
+
"toolDiffRemoved": "red",
|
|
64
|
+
"toolDiffContext": "fg3",
|
|
65
|
+
"syntaxComment": "gray",
|
|
66
|
+
"syntaxKeyword": "red",
|
|
67
|
+
"syntaxFunction": "green",
|
|
68
|
+
"syntaxVariable": "fg1",
|
|
69
|
+
"syntaxString": "green",
|
|
70
|
+
"syntaxNumber": "purple",
|
|
71
|
+
"syntaxType": "yellow",
|
|
72
|
+
"syntaxOperator": "orange",
|
|
73
|
+
"syntaxPunctuation": "fg1",
|
|
74
|
+
"thinkingOff": "gray",
|
|
75
|
+
"thinkingMinimal": "blue",
|
|
76
|
+
"thinkingLow": "aqua",
|
|
77
|
+
"thinkingMedium": "yellow",
|
|
78
|
+
"thinkingHigh": "orange",
|
|
79
|
+
"thinkingXhigh": "red",
|
|
80
|
+
"thinkingMax": "purple",
|
|
81
|
+
"bashMode": "green"
|
|
82
|
+
},
|
|
83
|
+
"export": {
|
|
84
|
+
"pageBg": "bg0",
|
|
85
|
+
"cardBg": "bg1",
|
|
86
|
+
"infoBg": "bg2"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
|
+
"name": "osdy-pi-nord",
|
|
4
|
+
"appearance": "dark",
|
|
5
|
+
"vars": {
|
|
6
|
+
"nord0": "#2e3440",
|
|
7
|
+
"nord1": "#3b4252",
|
|
8
|
+
"nord2": "#434c5e",
|
|
9
|
+
"nord3": "#4c566a",
|
|
10
|
+
"nord4": "#d8dee9",
|
|
11
|
+
"nord5": "#e5e9f0",
|
|
12
|
+
"nord6": "#eceff4",
|
|
13
|
+
"nord7": "#8fbcbb",
|
|
14
|
+
"nord8": "#88c0d0",
|
|
15
|
+
"nord9": "#81a1c1",
|
|
16
|
+
"nord10": "#5e81ac",
|
|
17
|
+
"nord11": "#bf616a",
|
|
18
|
+
"nord12": "#d08770",
|
|
19
|
+
"nord13": "#ebcb8b",
|
|
20
|
+
"nord14": "#a3be8c",
|
|
21
|
+
"nord15": "#b48ead"
|
|
22
|
+
},
|
|
23
|
+
"colors": {
|
|
24
|
+
"accent": "nord8",
|
|
25
|
+
"border": "nord3",
|
|
26
|
+
"borderAccent": "nord9",
|
|
27
|
+
"borderMuted": "nord2",
|
|
28
|
+
"success": "nord14",
|
|
29
|
+
"error": "nord11",
|
|
30
|
+
"warning": "nord13",
|
|
31
|
+
"muted": "nord9",
|
|
32
|
+
"dim": "nord3",
|
|
33
|
+
"text": "nord4",
|
|
34
|
+
"thinkingText": "nord9",
|
|
35
|
+
"selectedBg": "nord2",
|
|
36
|
+
"scrollbarTrack": "nord1",
|
|
37
|
+
"scrollbarThumb": "nord9",
|
|
38
|
+
"searchMatchBg": "nord13",
|
|
39
|
+
"searchMatchText": "nord0",
|
|
40
|
+
"userMessageBg": "nord1",
|
|
41
|
+
"userMessageText": "nord6",
|
|
42
|
+
"customMessageBg": "nord1",
|
|
43
|
+
"customMessageText": "nord4",
|
|
44
|
+
"customMessageLabel": "nord7",
|
|
45
|
+
"toolPendingBg": "nord0",
|
|
46
|
+
"toolSuccessBg": "nord1",
|
|
47
|
+
"toolErrorBg": "nord1",
|
|
48
|
+
"toolTitle": "nord8",
|
|
49
|
+
"toolOutput": "nord4",
|
|
50
|
+
"mdHeading": "nord8",
|
|
51
|
+
"mdLink": "nord8",
|
|
52
|
+
"mdLinkUrl": "nord9",
|
|
53
|
+
"mdCode": "nord14",
|
|
54
|
+
"mdCodeBlock": "nord4",
|
|
55
|
+
"mdCodeBlockBorder": "nord3",
|
|
56
|
+
"mdQuote": "nord9",
|
|
57
|
+
"mdQuoteBorder": "nord7",
|
|
58
|
+
"mdHr": "nord3",
|
|
59
|
+
"mdListBullet": "nord9",
|
|
60
|
+
"toolDiffAdded": "nord14",
|
|
61
|
+
"toolDiffRemoved": "nord11",
|
|
62
|
+
"toolDiffContext": "nord4",
|
|
63
|
+
"syntaxComment": "nord3",
|
|
64
|
+
"syntaxKeyword": "nord9",
|
|
65
|
+
"syntaxFunction": "nord8",
|
|
66
|
+
"syntaxVariable": "nord4",
|
|
67
|
+
"syntaxString": "nord14",
|
|
68
|
+
"syntaxNumber": "nord15",
|
|
69
|
+
"syntaxType": "nord7",
|
|
70
|
+
"syntaxOperator": "nord9",
|
|
71
|
+
"syntaxPunctuation": "nord6",
|
|
72
|
+
"thinkingOff": "nord3",
|
|
73
|
+
"thinkingMinimal": "nord10",
|
|
74
|
+
"thinkingLow": "nord7",
|
|
75
|
+
"thinkingMedium": "nord13",
|
|
76
|
+
"thinkingHigh": "nord12",
|
|
77
|
+
"thinkingXhigh": "nord11",
|
|
78
|
+
"thinkingMax": "nord15",
|
|
79
|
+
"bashMode": "nord14"
|
|
80
|
+
},
|
|
81
|
+
"export": {
|
|
82
|
+
"pageBg": "nord0",
|
|
83
|
+
"cardBg": "nord1",
|
|
84
|
+
"infoBg": "nord2"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
|
+
"name": "osdy-pi-rose-pine",
|
|
4
|
+
"appearance": "dark",
|
|
5
|
+
"vars": {
|
|
6
|
+
"base": "#191724",
|
|
7
|
+
"surface": "#1f1d2e",
|
|
8
|
+
"overlay": "#26233a",
|
|
9
|
+
"muted": "#6e6a86",
|
|
10
|
+
"subtle": "#908caa",
|
|
11
|
+
"text": "#e0def4",
|
|
12
|
+
"love": "#eb6f92",
|
|
13
|
+
"gold": "#f6c177",
|
|
14
|
+
"rose": "#ebbcba",
|
|
15
|
+
"pine": "#31748f",
|
|
16
|
+
"foam": "#9ccfd8",
|
|
17
|
+
"iris": "#c4a7e7",
|
|
18
|
+
"highlightLow": "#21202e",
|
|
19
|
+
"highlightMed": "#403d52",
|
|
20
|
+
"highlightHigh": "#524f67"
|
|
21
|
+
},
|
|
22
|
+
"colors": {
|
|
23
|
+
"accent": "iris",
|
|
24
|
+
"border": "subtle",
|
|
25
|
+
"borderAccent": "rose",
|
|
26
|
+
"borderMuted": "highlightMed",
|
|
27
|
+
"success": "foam",
|
|
28
|
+
"error": "love",
|
|
29
|
+
"warning": "gold",
|
|
30
|
+
"muted": "subtle",
|
|
31
|
+
"dim": "muted",
|
|
32
|
+
"text": "text",
|
|
33
|
+
"thinkingText": "subtle",
|
|
34
|
+
"selectedBg": "highlightMed",
|
|
35
|
+
"scrollbarTrack": "highlightLow",
|
|
36
|
+
"scrollbarThumb": "highlightHigh",
|
|
37
|
+
"searchMatchBg": "gold",
|
|
38
|
+
"searchMatchText": "base",
|
|
39
|
+
"userMessageBg": "highlightMed",
|
|
40
|
+
"userMessageText": "text",
|
|
41
|
+
"customMessageBg": "surface",
|
|
42
|
+
"customMessageText": "text",
|
|
43
|
+
"customMessageLabel": "foam",
|
|
44
|
+
"toolPendingBg": "surface",
|
|
45
|
+
"toolSuccessBg": "overlay",
|
|
46
|
+
"toolErrorBg": "overlay",
|
|
47
|
+
"toolTitle": "iris",
|
|
48
|
+
"toolOutput": "text",
|
|
49
|
+
"mdHeading": "iris",
|
|
50
|
+
"mdLink": "foam",
|
|
51
|
+
"mdLinkUrl": "subtle",
|
|
52
|
+
"mdCode": "rose",
|
|
53
|
+
"mdCodeBlock": "text",
|
|
54
|
+
"mdCodeBlockBorder": "muted",
|
|
55
|
+
"mdQuote": "subtle",
|
|
56
|
+
"mdQuoteBorder": "pine",
|
|
57
|
+
"mdHr": "highlightMed",
|
|
58
|
+
"mdListBullet": "love",
|
|
59
|
+
"toolDiffAdded": "foam",
|
|
60
|
+
"toolDiffRemoved": "love",
|
|
61
|
+
"toolDiffContext": "subtle",
|
|
62
|
+
"syntaxComment": "muted",
|
|
63
|
+
"syntaxKeyword": "pine",
|
|
64
|
+
"syntaxFunction": "rose",
|
|
65
|
+
"syntaxVariable": "text",
|
|
66
|
+
"syntaxString": "gold",
|
|
67
|
+
"syntaxNumber": "iris",
|
|
68
|
+
"syntaxType": "foam",
|
|
69
|
+
"syntaxOperator": "subtle",
|
|
70
|
+
"syntaxPunctuation": "subtle",
|
|
71
|
+
"thinkingOff": "muted",
|
|
72
|
+
"thinkingMinimal": "pine",
|
|
73
|
+
"thinkingLow": "foam",
|
|
74
|
+
"thinkingMedium": "gold",
|
|
75
|
+
"thinkingHigh": "rose",
|
|
76
|
+
"thinkingXhigh": "love",
|
|
77
|
+
"thinkingMax": "iris",
|
|
78
|
+
"bashMode": "foam"
|
|
79
|
+
},
|
|
80
|
+
"export": {
|
|
81
|
+
"pageBg": "base",
|
|
82
|
+
"cardBg": "surface",
|
|
83
|
+
"infoBg": "overlay"
|
|
84
|
+
}
|
|
85
|
+
}
|