@pi-archimedes/core 2.5.0 → 2.6.2
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 +34 -25
- package/package.json +3 -3
- package/src/chrome.test.ts +53 -0
- package/src/chrome.ts +2 -0
- package/src/config.test.ts +29 -0
- package/src/config.ts +28 -0
- package/src/editor/index.test.ts +619 -0
- package/src/editor/index.ts +134 -2
- package/src/editor/spin.test.ts +702 -0
- package/src/editor/spin.ts +471 -0
- package/src/index.test.ts +362 -0
- package/src/index.ts +91 -5
package/README.md
CHANGED
|
@@ -1,51 +1,60 @@
|
|
|
1
1
|
# @pi-archimedes/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**A terminal worth spending your day in — plus the plumbing that keeps the rest of the suite talking.**
|
|
4
4
|
|
|
5
|
-
Core is
|
|
6
|
-
|
|
7
|
-
## What you get
|
|
8
|
-
|
|
9
|
-
- **Animated splash screen** — configurable reveal animations (9 styles) that set the tone when Pi starts
|
|
10
|
-
- **Framed editor** — custom editor component with double-press quit guard
|
|
11
|
-
- **Styled thinking blocks** — configurable label text, color, and muted theme option; optional code block unindenting
|
|
12
|
-
- **Event bus** — shared pub/sub channel that lets packages communicate (subagent costs → footer, subagent questions → ask, etc.)
|
|
13
|
-
- **Shared utilities** — text truncation/width calculation, color helpers, config loading, settings I/O, and startup profiling
|
|
5
|
+
Core is the face of every session: the animated splash screen on launch, the framed editor where you type, the border spinner that works while the agent works, and clean, labelled thinking blocks. Under the surface it runs the shared event bus through which the other components pass costs, todos, and questions — and the text, colour, and settings utilities they all build on.
|
|
14
6
|
|
|
15
7
|
## Install
|
|
16
8
|
|
|
9
|
+
Standalone:
|
|
10
|
+
|
|
17
11
|
```bash
|
|
18
12
|
pi install npm:@pi-archimedes/core
|
|
19
13
|
```
|
|
20
14
|
|
|
21
|
-
Or
|
|
15
|
+
Or the full suite instead (which includes core and the ten optional components):
|
|
22
16
|
|
|
23
17
|
```bash
|
|
24
18
|
pi install npm:pi-archimedes
|
|
25
19
|
```
|
|
26
20
|
|
|
27
|
-
|
|
21
|
+
New to Pi? Pi itself is a one-time global install and needs Node.js ≥ 22.19.0:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
After installing Pi, choose one installation command above, then `cd` into your project and run `pi`. Inside the session, `/login` signs you into a supported provider and `/model` picks a model; the full walkthrough, including API-key setup, is in the repo's [setup section](https://github.com/danielcherubini/pi-archimedes#setup) and Pi's own [quickstart](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/quickstart.md). A session that's already running picks up the extension with `/reload`.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
## What you get
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
31
|
+
- **Splash screen** — an animated greeting when the session launches, in one of nine reveal styles (`diagonal`, `top-right`, `bottom-left`, `bottom-right`, `center-out`, `wave`, `horizontal`, `vertical`, `vertical-up`).
|
|
32
|
+
- **Framed editor** — your input in a clean bordered frame, with a double-press guard on the quit key (`Ctrl+C` by default) so a stray keystroke doesn't end the session.
|
|
33
|
+
- **Working spinner on the border** — one of ten animating styles (`pendulum`, `typing`, `pulse`, `marquee`, `wave-rows`, `columns`, `cascade`, `diagonal-swipe`, `rain`, `sparkle`) traces the editor frame while the agent works, replacing Pi's native "Working" line.
|
|
34
|
+
- **Thinking blocks** — chain-of-thought output gets a consistent label, colour, and layout; `codeUnindent` strips the common indentation so code in reasoning reads flush.
|
|
35
|
+
- **The bus** (`@pi-archimedes/core/bus`) — a global pub/sub event system: `COST_UPDATE`, `ASK_REQUEST`, `TODOS_UPDATE`, `TODOS_CLEAR`… subagent costs flow to the footer through it, subagent todos to the task board, subagent questions to the ask UI.
|
|
36
|
+
- **Shared utilities** — text truncation and width measurement, colour formatting, settings I/O, and startup profiling.
|
|
34
37
|
|
|
35
38
|
## Settings
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
Settings live in `~/.pi/agent/settings.json` under `archimedes.core`. The file is **strict JSON — no comments or trailing commas** (unlike the MCP server config files, which accept both).
|
|
38
41
|
|
|
39
42
|
| Setting | Type | Default | Description |
|
|
40
43
|
|---------|------|---------|-------------|
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `animationStyle` | string | `vertical-up` | Splash
|
|
44
|
+
| `editorSpinBorder` | bool | `true` | Show the animated border spinner while the agent works |
|
|
45
|
+
| `editorSpinStyle` | string | `pendulum` | One of the ten spinner styles |
|
|
46
|
+
| `editorSpinSpeed` | string | `normal` | `slow`, `normal`, or `fast` |
|
|
47
|
+
| `editorSpinLabel` | string | `Working` | Label shown alongside the border spinner |
|
|
48
|
+
| `animationStyle` | string | `vertical-up` | Splash-screen reveal style (the nine styles above) |
|
|
49
|
+
| `labelText` | string | `Thinking...` | Prefix before thinking blocks |
|
|
50
|
+
| `labelColor` | string | `255,215,0` | RGB string for the thinking label |
|
|
51
|
+
| `codeUnindent` | bool | `true` | Strip common indentation from code blocks in thinking sections |
|
|
52
|
+
| `mutedTheme` | bool | `false` | Stored, but **not yet effective** — the current thinking renderer doesn't consult it, so treat it as a pending toggle |
|
|
53
|
+
|
|
54
|
+
In the suite, `/archimedes` offers panel controls for the settings that have them; `/reload` applies any that are read at startup.
|
|
46
55
|
|
|
47
|
-
##
|
|
56
|
+
## Part of the suite
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
In [pi-archimedes](https://github.com/danielcherubini/pi-archimedes), core is always registered — it isn't one of the `/plugins` toggles — and it underpins what the other components share: the bus that feeds subagent costs to the footer, subagent todos to the task board, and subagent questions to the ask UI, plus the chrome and colour utilities the TUIs use. The diff renderer is standalone and does not depend on core's chrome — it only shares the suite when it loads.
|
|
50
59
|
|
|
51
|
-
← Back to
|
|
60
|
+
← [Back to pi-archimedes](https://github.com/danielcherubini/pi-archimedes)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-archimedes/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"@earendil-works/pi-tui": ">=0.1.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
31
|
-
"@earendil-works/pi-tui": "^0.
|
|
30
|
+
"@earendil-works/pi-coding-agent": "^0.85.1",
|
|
31
|
+
"@earendil-works/pi-tui": "^0.85.1",
|
|
32
32
|
"typescript": "^6.0.0"
|
|
33
33
|
},
|
|
34
34
|
"pi": {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { resolvePalette } from "./chrome.js";
|
|
4
|
+
|
|
5
|
+
// ── Mock theme (mirrors the stubTheme / safeThemeColor assertion style) ──
|
|
6
|
+
|
|
7
|
+
// theme.fg per safeThemeColor: a key missing from `defined` resolves to
|
|
8
|
+
// undefined, exercising the fallback chain. Each available key answers
|
|
9
|
+
// with its own marker so tests can tell which key won.
|
|
10
|
+
function makeTheme(defined: Record<string, string>): Theme {
|
|
11
|
+
return {
|
|
12
|
+
fg: (key: string, text: string) => {
|
|
13
|
+
const marker = defined[key];
|
|
14
|
+
return marker === undefined ? undefined : `\x1b[${marker}m${text}\x1b[0m`;
|
|
15
|
+
},
|
|
16
|
+
} as unknown as Theme;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ── spin palette entry ───────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
describe("resolvePalette spin", () => {
|
|
22
|
+
it("wraps in the accent fg when the theme provides accent (and differs from prefix)", () => {
|
|
23
|
+
const p = resolvePalette(
|
|
24
|
+
makeTheme({
|
|
25
|
+
accent: "38;2;1;2;3",
|
|
26
|
+
borderMuted: "38;2;9;9;9",
|
|
27
|
+
border: "38;2;9;9;99",
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
// accent wins for spin ...
|
|
31
|
+
expect(p.spin("t")).toBe("\x1b[38;2;1;2;3mt\x1b[0m");
|
|
32
|
+
// ... while prefix stays on borderMuted
|
|
33
|
+
expect(p.prefix("t")).toBe("\x1b[38;2;9;9;9mt\x1b[0m");
|
|
34
|
+
expect(p.spin("t")).not.toBe(p.prefix("t"));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("falls back to borderMuted when accent is unavailable", () => {
|
|
38
|
+
const p = resolvePalette(
|
|
39
|
+
makeTheme({
|
|
40
|
+
borderMuted: "38;2;9;9;9",
|
|
41
|
+
border: "38;2;9;9;99",
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
expect(p.spin("t")).toBe("\x1b[38;2;9;9;9mt\x1b[0m");
|
|
45
|
+
expect(p.spin("t")).toBe(p.prefix("t"));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("falls back to the neutral gray when neither is available", () => {
|
|
49
|
+
const p = resolvePalette(makeTheme({}));
|
|
50
|
+
// Same fallback the safeThemeColor chain already uses for borderMuted/border
|
|
51
|
+
expect(p.spin("t")).toBe("\x1b[38;2;74;74;74mt\x1b[0m");
|
|
52
|
+
});
|
|
53
|
+
});
|
package/src/chrome.ts
CHANGED
|
@@ -29,6 +29,7 @@ export interface PanePalette {
|
|
|
29
29
|
panelEdge: string;
|
|
30
30
|
frame(text: string): string;
|
|
31
31
|
prefix(text: string): string;
|
|
32
|
+
spin(text: string): string;
|
|
32
33
|
time(text: string): string;
|
|
33
34
|
hint(text: string): string;
|
|
34
35
|
}
|
|
@@ -80,6 +81,7 @@ export function resolvePalette(theme: Theme): PanePalette {
|
|
|
80
81
|
panelEdge: bgToFgAnsi(panelBg),
|
|
81
82
|
frame: (t) => safeThemeColor(theme, ["borderMuted", "border"], t),
|
|
82
83
|
prefix: (t) => safeThemeColor(theme, ["borderMuted", "border"], t),
|
|
84
|
+
spin: (t) => safeThemeColor(theme, ["accent", "borderMuted"], t),
|
|
83
85
|
time: (t) => safeThemeColor(theme, ["muted", "accent"], t),
|
|
84
86
|
hint: (t) => safeThemeColor(theme, ["dim", "muted"], t),
|
|
85
87
|
};
|
package/src/config.test.ts
CHANGED
|
@@ -29,6 +29,10 @@ describe("loadCoreConfig", () => {
|
|
|
29
29
|
labelText: "Thinking...",
|
|
30
30
|
labelColor: "255,215,0",
|
|
31
31
|
animationStyle: "vertical-up",
|
|
32
|
+
editorSpinBorder: true,
|
|
33
|
+
editorSpinSpeed: "normal",
|
|
34
|
+
editorSpinLabel: "Working",
|
|
35
|
+
editorSpinStyle: "pendulum",
|
|
32
36
|
});
|
|
33
37
|
});
|
|
34
38
|
|
|
@@ -65,8 +69,33 @@ describe("DEFAULT_CORE_CONFIG", () => {
|
|
|
65
69
|
labelText: "Thinking...",
|
|
66
70
|
labelColor: "255,215,0",
|
|
67
71
|
animationStyle: "vertical-up",
|
|
72
|
+
editorSpinBorder: true,
|
|
73
|
+
editorSpinSpeed: "normal",
|
|
74
|
+
editorSpinLabel: "Working",
|
|
75
|
+
editorSpinStyle: "pendulum",
|
|
68
76
|
});
|
|
69
77
|
});
|
|
78
|
+
|
|
79
|
+
it("exposes a speed→multiplier map (slow/normal/fast = 1.5/1/0.6 × native tempo)", async () => {
|
|
80
|
+
const { SPIN_SPEED_MULT } = await import("./config.js");
|
|
81
|
+
expect(SPIN_SPEED_MULT).toEqual({ slow: 1.5, normal: 1, fast: 0.6 });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("exposes the SPIN_INTERVALS key set (ten gallery-derived styles)", async () => {
|
|
85
|
+
const { SPIN_INTERVALS } = await import("./editor/spin.js");
|
|
86
|
+
expect(Object.keys(SPIN_INTERVALS)).toEqual([
|
|
87
|
+
"typing",
|
|
88
|
+
"pulse",
|
|
89
|
+
"rain",
|
|
90
|
+
"cascade",
|
|
91
|
+
"columns",
|
|
92
|
+
"wave-rows",
|
|
93
|
+
"diagonal-swipe",
|
|
94
|
+
"sparkle",
|
|
95
|
+
"pendulum",
|
|
96
|
+
"marquee",
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
70
99
|
});
|
|
71
100
|
|
|
72
101
|
describe("ANIMATION_STYLES", () => {
|
package/src/config.ts
CHANGED
|
@@ -19,14 +19,42 @@ export interface CoreConfig {
|
|
|
19
19
|
labelText: string;
|
|
20
20
|
labelColor: string;
|
|
21
21
|
animationStyle: AnimationStyle;
|
|
22
|
+
editorSpinBorder: boolean;
|
|
23
|
+
editorSpinSpeed: "slow" | "normal" | "fast";
|
|
24
|
+
editorSpinLabel: string;
|
|
25
|
+
editorSpinStyle: SpinnerStyle;
|
|
22
26
|
}
|
|
23
27
|
|
|
28
|
+
/** Border-spinner style (the ten gallery-derived variants, all registered in `SPIN_VARIANTS` (`editor/spin.ts`); unknown values normalize to `typing` via `normalizeSpinnerStyle`). */
|
|
29
|
+
export type SpinnerStyle =
|
|
30
|
+
| "typing"
|
|
31
|
+
| "pulse"
|
|
32
|
+
| "rain"
|
|
33
|
+
| "cascade"
|
|
34
|
+
| "columns"
|
|
35
|
+
| "wave-rows"
|
|
36
|
+
| "diagonal-swipe"
|
|
37
|
+
| "sparkle"
|
|
38
|
+
| "pendulum"
|
|
39
|
+
| "marquee";
|
|
40
|
+
|
|
24
41
|
export const DEFAULT_CORE_CONFIG: CoreConfig = {
|
|
25
42
|
mutedTheme: false,
|
|
26
43
|
codeUnindent: true,
|
|
27
44
|
labelText: "Thinking...",
|
|
28
45
|
labelColor: "255,215,0",
|
|
29
46
|
animationStyle: "vertical-up",
|
|
47
|
+
editorSpinBorder: true,
|
|
48
|
+
editorSpinSpeed: "normal",
|
|
49
|
+
editorSpinLabel: "Working",
|
|
50
|
+
editorSpinStyle: "pendulum",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** Border-spinner speed setting → the multipliers the `editorSpinSpeed` setting applies to the style's native per-tick interval (SPIN_INTERVALS, `editor/spin.ts` — typing: 120 / 80 / 48 ms). */
|
|
54
|
+
export const SPIN_SPEED_MULT: Record<CoreConfig["editorSpinSpeed"], number> = {
|
|
55
|
+
slow: 1.5,
|
|
56
|
+
normal: 1,
|
|
57
|
+
fast: 0.6,
|
|
30
58
|
};
|
|
31
59
|
|
|
32
60
|
const NAMESPACE = "archimedes.core";
|