@xynogen/pix-core 0.1.4 → 0.1.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-core",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Pi extension — core UI/UX bundle (welcome banner, footer, model picker, self-update)",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/index.ts CHANGED
@@ -16,6 +16,7 @@
16
16
  */
17
17
 
18
18
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
19
+ import registerSkillLoader from "@xynogen/pix-skills";
19
20
  import registerAgentSop from "./commands/agent-sop/agent-sop.ts";
20
21
  import registerClear from "./commands/clear/clear.ts";
21
22
  import registerCopyAll from "./commands/copy-all/copy-all.ts";
@@ -34,6 +35,7 @@ import registerWelcome from "./ui/welcome.ts";
34
35
 
35
36
  export default function (pi: ExtensionAPI): void {
36
37
  registerAgentSop(pi);
38
+ registerSkillLoader(pi);
37
39
  registerWelcome(pi);
38
40
  registerFooter(pi);
39
41
  registerDiagnostics(pi);
package/src/lib/data.ts CHANGED
@@ -11,6 +11,11 @@
11
11
  * models.ts — lookupModelsDev, lookupBenchmark
12
12
  */
13
13
 
14
+ export type {
15
+ BenchmarkEntry,
16
+ ModelsDevApi,
17
+ ModelsDevModel,
18
+ } from "../../../pix-data/src/index.ts";
14
19
  export {
15
20
  benchmark,
16
21
  buildModelsDevIndex,
@@ -21,12 +26,7 @@ export {
21
26
  lookupInIndex,
22
27
  lookupModelsDev,
23
28
  modelsDev,
24
- } from "@xynogen/pix-data";
25
- export type {
26
- BenchmarkEntry,
27
- ModelsDevApi,
28
- ModelsDevModel,
29
- } from "@xynogen/pix-data";
29
+ } from "../../../pix-data/src/index.ts";
30
30
 
31
31
  export default function (_pi: unknown): void {
32
32
  // pix-data warms this cache on startup — nothing to do here.
@@ -1,6 +1,6 @@
1
+ import { describe, expect, test } from "bun:test";
1
2
  import { mkdirSync, rmSync, writeFileSync } from "node:fs";
2
3
  import { join } from "node:path";
3
- import { describe, expect, test } from "bun:test";
4
4
  import {
5
5
  buildOrientation,
6
6
  CAPABILITY_REMINDER,
@@ -48,8 +48,8 @@ describe("CAPABILITY_REMINDER", () => {
48
48
  expect(CAPABILITY_REMINDER.toLowerCase()).toContain("improvis");
49
49
  });
50
50
 
51
- test("nudges model to call skill() when a skill matches", () => {
52
- expect(CAPABILITY_REMINDER).toContain("skill()");
51
+ test("nudges model to call read_skills() when a skill matches", () => {
52
+ expect(CAPABILITY_REMINDER).toContain("read_skills()");
53
53
  });
54
54
 
55
55
  test("points at /toolbox slash command for discovery (not a function call)", () => {
@@ -132,9 +132,9 @@ describe("buildOrientation", () => {
132
132
  expect(out).toContain("2 skills");
133
133
  });
134
134
 
135
- test("explains how to use skill() and /toolbox for discovery", () => {
135
+ test("explains how to use read_skills() and /toolbox for discovery", () => {
136
136
  const out = buildOrientation([tool("read", "builtin")], []);
137
- expect(out).toContain("skill()");
137
+ expect(out).toContain("read_skills()");
138
138
  expect(out).toContain("/toolbox");
139
139
  // toolbox must NOT appear as a function call
140
140
  expect(out).not.toContain("toolbox(");
@@ -10,14 +10,14 @@
10
10
  * 1. FIRST prompt of the session — an orientation block: a high-level
11
11
  * description of WHAT is available (counts of tools / MCP tools / skills)
12
12
  * and HOW to explore it. We deliberately do NOT dump the whole inventory
13
- * every turn — the model should call skill() for skills and use /toolbox
13
+ * every turn — the model should call read_skills() for skills and use /toolbox
14
14
  * (slash command, user-facing) to discover/enable gated tools.
15
15
  * 2. EVERY subsequent prompt — the terse one-line CAPABILITY_REMINDER, a
16
- * cheap (~40 tok) reinforcement that steers toward skill() and /toolbox.
16
+ * cheap (~40 tok) reinforcement that steers toward read_skills() and /toolbox.
17
17
  *
18
18
  * NOTE: `toolbox` is a slash command only (/toolbox) — NOT a model-callable
19
19
  * function tool. The model cannot call toolbox() in function definitions.
20
- * The `skill` tool IS model-callable: skill() lists/loads bundled skills.
20
+ * The `read_skills` tool IS model-callable: read_skills() lists/loads bundled skills.
21
21
  */
22
22
 
23
23
  import { existsSync } from "node:fs";
@@ -34,7 +34,7 @@ type LoadedSkill = NonNullable<BuildSystemPromptOptions["skills"]>[number];
34
34
  export const CAPABILITY_REMINDER =
35
35
  "Reminder — check knowledge resources " +
36
36
  "(skills/tools/MCP/web/user) before improvising. " +
37
- "Matching skill? Call skill() first. " +
37
+ "Matching skill? Call read_skills() first. " +
38
38
  "Use /toolbox to discover/enable gated tools. " +
39
39
  "All tools callable via function definitions.";
40
40
 
@@ -128,7 +128,7 @@ export function buildOrientation(
128
128
  if (gateLine) lines.push(gateLine);
129
129
  lines.push(
130
130
  "Don't improvise what a capability covers — ask the user, search the web, or pull docs first.",
131
- "`skill()` lists/loads bundled skills — call it when a skill matches your task. " +
131
+ "`read_skills()` lists/loads bundled skills — call it when a skill matches your task. " +
132
132
  "/toolbox (slash command) discovers and enables gated tools.",
133
133
  );
134
134
  if (skillNames.length) {
@@ -1,5 +1,12 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import registerTodo from "./todo.ts";
2
+ import registerTodo, { renderTodoLines, type TodoItem } from "./todo.ts";
3
+
4
+ // Stub theme tags each fragment with its color/bold so assertions can verify
5
+ // which status got which tint, without depending on real ANSI codes.
6
+ const tagTheme = {
7
+ fg: (color: string, text: string) => `[${color}]${text}[/]`,
8
+ bold: (text: string) => `<b>${text}</b>`,
9
+ };
3
10
 
4
11
  // ─── Helpers ────────────────────────────────────────────────────────────────
5
12
 
@@ -600,3 +607,40 @@ describe("restore", () => {
600
607
  expect(text(result)).toBe("(no todos)");
601
608
  });
602
609
  });
610
+
611
+ describe("renderTodoLines (colored TUI render)", () => {
612
+ const items: TodoItem[] = [
613
+ { id: 1, text: "alpha", status: "done" },
614
+ { id: 2, text: "bravo", status: "in_progress" },
615
+ { id: 3, text: "charlie", status: "pending" },
616
+ { id: 4, text: "delta", status: "blocked" },
617
+ ];
618
+
619
+ test("empty list renders muted placeholder", () => {
620
+ expect(renderTodoLines([], tagTheme)).toBe("[muted](no todos)[/]");
621
+ });
622
+
623
+ test("tints each glyph by status", () => {
624
+ const out = renderTodoLines(items, tagTheme);
625
+ expect(out).toContain("[success]●[/]"); // done
626
+ expect(out).toContain("[accent]◐[/]"); // in_progress
627
+ expect(out).toContain("[muted]○[/]"); // pending
628
+ expect(out).toContain("[error]⊘[/]"); // blocked
629
+ });
630
+
631
+ test("highlights the in-progress row bold + accent", () => {
632
+ const out = renderTodoLines(items, tagTheme);
633
+ expect(out).toContain("<b>[accent]2. bravo[/]</b>");
634
+ });
635
+
636
+ test("dims completed rows and uses text color for active-but-not-running", () => {
637
+ const out = renderTodoLines(items, tagTheme);
638
+ expect(out).toContain("[muted]1. alpha[/]"); // done body muted
639
+ expect(out).toContain("[text]3. charlie[/]"); // pending body text
640
+ });
641
+
642
+ test("shows the done/total count header", () => {
643
+ const out = renderTodoLines(items, tagTheme);
644
+ expect(out).toContain("[muted]Todos 1/4 done:[/]");
645
+ });
646
+ });
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
+ import { Text } from "@earendil-works/pi-tui";
13
14
  import { Type } from "typebox";
14
15
 
15
16
  export type TodoStatus = "pending" | "in_progress" | "done" | "blocked";
@@ -27,6 +28,38 @@ const TODO_GLYPH: Record<TodoStatus, string> = {
27
28
  blocked: "⊘",
28
29
  };
29
30
 
31
+ /** Theme color key per status — drives both glyph and (for active) row tint. */
32
+ const TODO_COLOR: Record<TodoStatus, string> = {
33
+ pending: "muted",
34
+ in_progress: "accent",
35
+ done: "success",
36
+ blocked: "error",
37
+ };
38
+
39
+ export type TodoTheme = {
40
+ fg: (color: string, text: string) => string;
41
+ bold: (text: string) => string;
42
+ };
43
+
44
+ /** Colored checklist for the TUI: glyphs tinted by status, active row bold. */
45
+ export function renderTodoLines(items: TodoItem[], theme: TodoTheme): string {
46
+ if (!items.length) return theme.fg("muted", "(no todos)");
47
+ const done = items.filter((t) => t.status === "done").length;
48
+ const head = theme.fg("muted", `Todos ${done}/${items.length} done:`);
49
+ const lines = items.map((t) => {
50
+ const color = TODO_COLOR[t.status];
51
+ const glyph = theme.fg(color, TODO_GLYPH[t.status]);
52
+ const body = `${t.id}. ${t.text}`;
53
+ // Highlight the in-flight task so the eye lands on it first.
54
+ const label =
55
+ t.status === "in_progress"
56
+ ? theme.bold(theme.fg("accent", body))
57
+ : theme.fg(t.status === "done" ? "muted" : "text", body);
58
+ return `${glyph} ${label}`;
59
+ });
60
+ return `${head}\n${lines.join("\n")}`;
61
+ }
62
+
30
63
  const parseItems = (raw: string): string[] =>
31
64
  raw
32
65
  .split("\n")
@@ -102,6 +135,10 @@ export default function registerTodo(pi: ExtensionAPI): void {
102
135
  }),
103
136
  ),
104
137
  }),
138
+ renderResult(_result, _options, theme) {
139
+ return new Text(renderTodoLines(todos, theme as TodoTheme), 0, 0);
140
+ },
141
+
105
142
  async execute(_id, params) {
106
143
  // AgentToolResult now requires a `details` field. These todo results have
107
144
  // no structured details, so emit `undefined` via small local helpers.