eklavya 1.90.0 → 1.91.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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eklavya",
|
|
3
3
|
"displayName": "Eklavya",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.91.0",
|
|
5
5
|
"description": "Learn while your agent works. Turns coding-agent generation time into adaptive, Socratic learning grounded in the code being written.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Ajay Kumar"
|
|
@@ -56,7 +56,7 @@ returns `{disabled: true}` and the mod opens and draws nothing. A question that
|
|
|
56
56
|
the Stop sweep and the delegation lines (`delegation-lib.ts`) read it, and an
|
|
57
57
|
open panel question silences the checkpoint and the Stop sweep. Read
|
|
58
58
|
`docs/verified-schemas.md` (mods section) before changing anything it records.
|
|
59
|
-
`/eklavya-panel-demo` (`DEMO_COMMAND`) is the maintainer's shortcut for trying the pane: it seeds `PanelState.demo` with a mock question and never calls the server, so it works with `quiz.panel` off. While `demo` is set the heartbeat (`beat`) does not sync, `submit` grades locally with `demoResult` (no `panel_answer`, grader or explainer) and Next restarts the mock; `/eklavya-panel` and a real `present_question` sync replace it. Only the desktop draws `Client` regions (VS Code has `Svg` but no `Client`, and a tree holding an element its surface lacks is replaced whole): there every answer is a numbered whole-card region from `card.tsx`, Teach me is a compact `pill.tsx` region, the Other header is a `bare` card over an `Input` of three rows, and the header mark is `MASCOT_FACE` as an `Svg
|
|
59
|
+
`/eklavya-panel-demo` (`DEMO_COMMAND`) is the maintainer's shortcut for trying the pane: it seeds `PanelState.demo` with a mock question and never calls the server, so it works with `quiz.panel` off. While `demo` is set the heartbeat (`beat`) does not sync, `submit` grades locally with `demoResult` (no `panel_answer`, grader or explainer) and Next restarts the mock; `/eklavya-panel` and a real `present_question` sync replace it. Only the desktop draws `Client` regions (VS Code has `Svg` but no `Client`, and a tree holding an element its surface lacks is replaced whole): there every answer is a numbered whole-card region from `card.tsx`, Teach me is a compact `pill.tsx` region, the Other header is a `bare` card over an `Input` of three rows, and the header mark is `MASCOT_FACE` as an `Svg` (the terminal cannot draw one, so it draws the same head from `LOGO_GRID`: symmetric, eyes only, square pixels that `logoRows` doubles sideways into half-block characters); they post `{pick}` or `{press}` to the `ui.message` hook, which accepts only an id of the open question, `other`, or `teach`. VS Code and the terminal keep native `Button`s (VS Code numbers the labels itself, the terminal's plain Button draws `1:`). The demo's Answer cards switch (desktop only) compares the whole-card default with the native buttons. Under the Teach me button the demo also draws an experiment box (`experiment` in `register.tsx`): `Image` with `DEMO_PNG` on the terminal, two `Svg` (`demoSvg`, `demoSvgWithPng`) on every other surface, never both, because a tree holding an element its surface lacks is replaced whole by the host's own pane.
|
|
60
60
|
Run `scripts/test-panel-mod.sh`: it stages the mod as its own plugin, validates
|
|
61
61
|
it, runs `claude plugin test`, then validates the distributed plugin.
|
|
62
62
|
|
|
@@ -26,25 +26,25 @@ export const PICKED = { bg: '#1E5A52', text: '#FFFFFF', note: '#C9E8E3' }
|
|
|
26
26
|
export const TEACH = { bg: '#D97757', text: '#1F1E1D', hover: '#E48866' }
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* The logo: the
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* The logo: the Eklavya mascot's head, drawn from the same artwork as the brand's SVG (the terminal cannot draw an SVG).
|
|
30
|
+
* Square pixels, one letter each (a dot is empty, `LOGO_PALETTE` names the colours): symmetric, eyes only. A terminal
|
|
31
|
+
* cell is twice as tall as it is wide, so `logoRows` draws each pixel one character wide and half a row tall.
|
|
32
32
|
*/
|
|
33
33
|
export const LOGO_GRID = [
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
34
|
+
'.....ia.....',
|
|
35
|
+
'...iiaaaa...',
|
|
36
|
+
'.aaaaaaaaaa.',
|
|
37
|
+
'.aaaaaaaaaa.',
|
|
38
|
+
'aaaaaaaaaaaa',
|
|
39
|
+
'aaacaaaacaaa',
|
|
40
|
+
'aaacaaaacaaa',
|
|
41
|
+
'aaaaaaaaaaaa',
|
|
42
|
+
'.aaaaaaaaaa.',
|
|
43
|
+
'.aaaaaaaaaa.',
|
|
44
|
+
'..bbbbbbbb..',
|
|
45
|
+
'...ddkkdd...',
|
|
46
46
|
] as const
|
|
47
|
-
export const LOGO_PALETTE: Record<string, string> = { a: '#
|
|
47
|
+
export const LOGO_PALETTE: Record<string, string> = { a: '#EF9875', i: '#FFC8A6', c: '#302C2A', b: '#C96D51', d: '#0E6E66', k: '#5B7C73' }
|
|
48
48
|
|
|
49
49
|
/** One stretch of text cells that share a colour pair. A text character holds two colours at most. */
|
|
50
50
|
export type LogoRun = { text: string; fg?: string; bg?: string }
|
|
@@ -53,11 +53,13 @@ export type LogoRun = { text: string; fg?: string; bg?: string }
|
|
|
53
53
|
const QUADRANT = ' ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█'
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* The grid as rows of text runs: each 2 by 2 block becomes one quadrant character, the
|
|
56
|
+
* The pixel grid as rows of text runs: each 2 by 2 block becomes one quadrant character, the
|
|
57
57
|
* first colour as its foreground and a second, if any, as its background. A third colour
|
|
58
|
-
* in one block is dropped, so
|
|
58
|
+
* in one block is dropped, so a hand-made grid degrades and never throws (the logo never has one).
|
|
59
59
|
*/
|
|
60
|
-
export function logoRows(
|
|
60
|
+
export function logoRows(pixels: readonly string[] = LOGO_GRID, palette: Record<string, string> = LOGO_PALETTE): LogoRun[][] {
|
|
61
|
+
// Each pixel is two grid cells wide, so a character holds two stacked pixels: never more than two colours.
|
|
62
|
+
const grid = pixels.map(row => [...row].map(c => c + c).join(''))
|
|
61
63
|
const out: LogoRun[][] = []
|
|
62
64
|
for (let cy = 0; cy < Math.ceil(grid.length / 2); cy++) {
|
|
63
65
|
const runs: LogoRun[] = []
|
|
@@ -178,7 +180,7 @@ export const STR = {
|
|
|
178
180
|
mascotAlt: 'The Eklavya mascot',
|
|
179
181
|
experimentBanner: 'EXPERIMENT · picture test. Nothing here is part of a real question.',
|
|
180
182
|
experimentTerminal: 'Image: a PNG, drawn as a picture in kitty, Ghostty and iTerm2 (fullscreen); other terminals show the dim text instead. SVG is not drawn in the terminal.',
|
|
181
|
-
experimentImageAlt: 'The Eklavya
|
|
183
|
+
experimentImageAlt: 'The Eklavya mascot, head only',
|
|
182
184
|
experimentNone: 'This surface draws neither an SVG nor an image, so there is nothing to test here.',
|
|
183
185
|
experimentSvg: 'SVG: the mark drawn as vector rectangles.',
|
|
184
186
|
experimentSvgPng: 'Image: the same mark as a PNG inside an SVG (Desktop has no Image element).',
|
|
@@ -260,9 +262,9 @@ export function demoResult(kind: 'choice' | 'text' | 'skip' | 'dont_know', optio
|
|
|
260
262
|
export const MASCOT_FACE =
|
|
261
263
|
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"30 12 126 128\" width=\"200\" height=\"200\" class=\"ek-mascot\" data-expression=\"neutral\" data-variant=\"face\" role=\"img\"><style>:root {/* ---------- MASCOT IDENTITY ----------\n Archer Cloak artwork keeps its approved identity across both grounds.\n UI chrome continues to use the surface, text and accent roles below. */\n --mascot-coral: #ef9875;\n --mascot-coral-dark: #c96d51;\n --mascot-highlight: #ffc8a6;\n --mascot-cloak: #0E6E66;\n --mascot-cloak-dark: #07403C;\n --mascot-thread: #C4EDE4;\n --mascot-ink: #302c2a;\n --mascot-metal: #5b7c73;\n --mascot-wood: #bc9569;\n --mascot-wood-dark: #74583f;\n --mascot-string: #EAE7E1;\n --mascot-shadow: #26262a;\n /* ---------- END MASCOT IDENTITY ---------- */}/* Identity colours and UI surfaces use Eklavya's semantic tokens. */\neklavya-mascot { display:inline-flex; flex-shrink:0; vertical-align:middle; line-height:0; }\n.ek-mascot { display:block; overflow:visible; flex-shrink:0; }\n.ek-mascot[data-variant=\"face\"] { overflow:hidden; }\n.ek-mascot__face path:not([fill]) { fill:none; }\n.ek-companion { display:flex;align-items:center;gap:12px;color:var(--dim);font:13px/1.5 var(--font-body); }\n.ek-companion--hero { margin-bottom:16px; }\n.ek-companion--hero b { display:block;color:var(--ink);font-weight:600; }\n.ek-mascot-message { display:flex;align-items:center;gap:16px;padding:16px 0; }\n.ek-mascot-message p {margin:0}\n/* Secondary loader: draws and releases an arrow over 2.8 s. */\n.ek-mascot--draw .ek-mascot__character { animation:ek-breathe 2.8s ease-in-out infinite; transform-origin:95px 185px; }\n.ek-mascot--draw .ek-mascot__arrow { animation:ek-release 2.8s cubic-bezier(.3,0,.3,1) infinite; }\n.ek-mascot--draw .ek-mascot__string { animation:ek-draw 2.8s ease-in-out infinite; transform-origin:157px 125px; }\n.ek-mascot--draw .ek-mascot__ripple { animation:ek-hit 2.8s ease-out infinite; transform-origin:196px 128px; opacity:0; }\n@keyframes ek-breathe { 0%,100% {transform:translateY(0)} 35%,55% {transform:translateY(-3px) rotate(-1deg)} }\n@keyframes ek-release { 0%,12% {transform:translateX(0);opacity:1} 40%,52% {transform:translateX(-10px);opacity:1} 68% {transform:translateX(22px);opacity:0} 69%,85% {transform:translateX(0);opacity:0} 100% {transform:translateX(0);opacity:1} }\n@keyframes ek-draw { 0%,100% {transform:scaleX(1)} 40%,52% {transform:scaleX(1.11)} 60% {transform:scaleX(.08)} 68% {transform:scaleX(1)} }\n@keyframes ek-hit { 0%,60%,100% {opacity:0;transform:scale(.4)} 68% {opacity:.8;transform:scale(.7)} 88% {opacity:0;transform:scale(1.7)} }\n/* Loader: walking toward the viewer, a 1.2 s stride (two steps). Ground rungs recede on an ease-out, as perspective slows them near the horizon. */\n.ek-mascot--loading .ek-mascot__walker { animation:ek-sway 1.2s ease-in-out infinite; transform-origin:95px 185px; }\n.ek-mascot--loading .ek-mascot__torso { animation:ek-bob .6s ease-in-out infinite; }\n.ek-mascot--loading .ek-mascot__leg, .ek-mascot--loading .ek-mascot__hand { animation:ek-step 1.2s ease-in-out infinite; }\n.ek-mascot--loading .ek-mascot__leg--r, .ek-mascot--loading .ek-mascot__hand--arrow { animation-delay:-.6s; }\n.ek-mascot--loading .ek-mascot__shadow { animation:ek-shadow .6s ease-in-out infinite; transform-origin:95px 186px; }\n.ek-mascot--loading .ek-mascot__rung { animation:ek-recede 1.2s cubic-bezier(.15,.6,.4,1) infinite; transform-origin:95px 162px; }\n.ek-mascot--loading .ek-mascot__rung:nth-child(3) { animation-delay:-.3s; }\n.ek-mascot--loading .ek-mascot__rung:nth-child(4) { animation-delay:-.6s; }\n.ek-mascot--loading .ek-mascot__rung:nth-child(5) { animation-delay:-.9s; }\n@keyframes ek-sway { 0%,100% {transform:rotate(-1.5deg)} 50% {transform:rotate(1.5deg)} }\n@keyframes ek-bob { 0%,100% {transform:translateY(1px)} 50% {transform:translateY(-3px)} }\n@keyframes ek-step { 0%,50%,100% {transform:translateY(0)} 25% {transform:translateY(-6px)} }\n@keyframes ek-shadow { 0%,100% {transform:scaleX(1)} 50% {transform:scaleX(.92)} }\n@keyframes ek-recede { 0% {transform:translateY(56px) scaleX(1);opacity:0} 10% {opacity:1} 85% {opacity:1} 100% {transform:translateY(0) scaleX(.4);opacity:0} }\n@media (prefers-reduced-motion:reduce) { .ek-mascot * {animation:none !important;transition:none !important} }\n\n.ek-companion--rail { padding:12px 20px; text-decoration:none; }\n.ek-mascot-message > :last-child { min-width:0; overflow-wrap:break-word; }\n.ek-mascot-status { display:inline-flex; align-items:center; gap:6px; }\n</style><title>Eklavya, calm and attentive</title><path d=\"M48 109q44-19 88 0l-8 30-33-15-31 15Z\" fill=\"var(--mascot-cloak)\"/><g class=\"ek-mascot__head\">\n <path d=\"M46 43h18V29h19V18h23v12h20v14h17v19h8v29h-8v16q0 12-16 15H62q-18-3-18-17V91h-9V63h11Z\" fill=\"var(--mascot-coral)\" stroke=\"var(--mascot-coral-dark)\" stroke-width=\"2\" stroke-linejoin=\"round\"/>\n <path d=\"M48 45h18V32h18V21h19\" fill=\"none\" stroke=\"var(--mascot-highlight)\" stroke-width=\"3\" stroke-linejoin=\"round\"/>\n <g class=\"ek-mascot__face\" fill=\"var(--mascot-ink)\" stroke=\"var(--mascot-ink)\" stroke-width=\"3.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"64\" y=\"64\" width=\"10\" height=\"19\" rx=\"5\"/><rect x=\"110\" y=\"64\" width=\"10\" height=\"19\" rx=\"5\"/><path d=\"M84 89q8 6 16 0\"/></g>\n </g><circle cx=\"95\" cy=\"126\" r=\"6\" fill=\"var(--mascot-metal)\"/></svg>"
|
|
262
264
|
|
|
263
|
-
/** The logo as a PNG (
|
|
265
|
+
/** The logo as a PNG (96 by 96, one grid cell is 8 pixels), for the demo's picture test: the mod has no filesystem. */
|
|
264
266
|
export const DEMO_PNG =
|
|
265
|
-
'
|
|
267
|
+
'iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAABCklEQVR42u3dsQ2CUBSG0TeBgxgLV7G2t9OG0DmIEzgLTuMIzwFMHsULuRc4f/LVkHtqQinJV6d3bfV9jc2KAQBgAAAYAAAGIN0Bew+8dAAAAAAAAAAAAAAA7BEg+gBrDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMgA4EjBQI4AAIAAABAAANogwPl0bBZ9gOj3AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALYAIAAABACAAAAQAN8POLAvZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQD6AuX3ul7rnwn/wAAAAAAAAAAAAAABAwh0et9rT9Tl01fv81f9JDwAAAAAAAAAAAACA//0Aant80y9+4G8AAAAASUVORK5CYII='
|
|
266
268
|
|
|
267
269
|
/** The logo as SVG, one rect per run of a colour, so the demo can test a vector picture without an asset file. */
|
|
268
270
|
export function demoSvg(): string {
|
|
@@ -281,7 +283,7 @@ export function demoSvg(): string {
|
|
|
281
283
|
|
|
282
284
|
/** The same picture as a PNG inside an SVG: Desktop has no Image element, so this is how a raster is tried there. */
|
|
283
285
|
export const demoSvgWithPng = (): string =>
|
|
284
|
-
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
|
|
286
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96"><image href="data:image/png;base64,${DEMO_PNG}" width="96" height="96"/></svg>`
|
|
285
287
|
|
|
286
288
|
/** The tester's line of what was just clicked, and how the click arrived. */
|
|
287
289
|
export const demoLast = (what: string): string => `Last click: ${what}`
|
|
@@ -663,9 +663,9 @@ export const register: Register = on => {
|
|
|
663
663
|
{vector ? (
|
|
664
664
|
<Box flexDirection="column" gap={1}>
|
|
665
665
|
<Text dimColor>{STR.experimentSvg}</Text>
|
|
666
|
-
<Svg alt={STR.experimentImageAlt} width={
|
|
666
|
+
<Svg alt={STR.experimentImageAlt} width={96} height={96} source={demoSvg()} />
|
|
667
667
|
<Text dimColor>{STR.experimentSvgPng}</Text>
|
|
668
|
-
<Svg alt={STR.experimentImageAlt} width={
|
|
668
|
+
<Svg alt={STR.experimentImageAlt} width={96} height={96} source={demoSvgWithPng()} />
|
|
669
669
|
</Box>
|
|
670
670
|
) : app ? (
|
|
671
671
|
<Text dimColor wrap="wrap">
|
|
@@ -676,7 +676,7 @@ export const register: Register = on => {
|
|
|
676
676
|
<Text dimColor wrap="wrap">
|
|
677
677
|
{STR.experimentTerminal}
|
|
678
678
|
</Text>
|
|
679
|
-
<Image key="experiment-image" source={{ png: DEMO_PNG }} columns={
|
|
679
|
+
<Image key="experiment-image" source={{ png: DEMO_PNG }} columns={12} rows={6} alt={STR.experimentImageAlt} />
|
|
680
680
|
</Box>
|
|
681
681
|
)}
|
|
682
682
|
</Box>
|
package/package.json
CHANGED