@xynogen/pix-core 0.2.4 → 0.3.1

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 (38) hide show
  1. package/README.md +24 -21
  2. package/package.json +11 -17
  3. package/skills/ask-user/SKILL.md +0 -48
  4. package/src/commands/agent-sop/agent-sop.ts +0 -58
  5. package/src/commands/clear/clear.ts +0 -32
  6. package/src/commands/diff/diff.ts +0 -32
  7. package/src/commands/models/models.test.ts +0 -95
  8. package/src/commands/models/models.ts +0 -367
  9. package/src/commands/models/patch-builtin.test.ts +0 -66
  10. package/src/commands/models/patch-builtin.ts +0 -120
  11. package/src/commands/tools.test.ts +0 -15
  12. package/src/commands/update/update.test.ts +0 -112
  13. package/src/commands/update/update.ts +0 -271
  14. package/src/index.ts +0 -45
  15. package/src/lib/data.ts +0 -33
  16. package/src/nudge/capability.test.ts +0 -258
  17. package/src/nudge/capability.ts +0 -189
  18. package/src/nudge/index.ts +0 -17
  19. package/src/nudge/tools.test.ts +0 -157
  20. package/src/nudge/tools.ts +0 -212
  21. package/src/tool/ask/ask.test.ts +0 -243
  22. package/src/tool/ask/components.ts +0 -55
  23. package/src/tool/ask/helpers.ts +0 -77
  24. package/src/tool/ask/index.ts +0 -130
  25. package/src/tool/ask/questionnaire.ts +0 -693
  26. package/src/tool/ask/rpc.ts +0 -84
  27. package/src/tool/ask/schema.ts +0 -69
  28. package/src/tool/ask/single-select-layout.test.ts +0 -124
  29. package/src/tool/ask/single-select-layout.ts +0 -237
  30. package/src/tool/ask/types.ts +0 -17
  31. package/src/tool/todo/todo.test.ts +0 -646
  32. package/src/tool/todo/todo.ts +0 -218
  33. package/src/tool/toolbox/toolbox.test.ts +0 -314
  34. package/src/tool/toolbox/toolbox.ts +0 -570
  35. package/src/ui/diagnostics.ts +0 -145
  36. package/src/ui/footer.ts +0 -513
  37. package/src/ui/welcome.test.ts +0 -124
  38. package/src/ui/welcome.ts +0 -369
package/README.md CHANGED
@@ -1,35 +1,38 @@
1
1
  # pix-core
2
2
 
3
- Pi coding agent extension — core UI/UX bundle.
3
+ Pi coding agent extension — core UI/UX meta-package.
4
+
5
+ Installing `pix-core` pulls in all of the packages below as npm dependencies — no source code of its own.
4
6
 
5
7
  ## What's included
6
8
 
7
- | Extension | Type | Description |
8
- |---|---|---|
9
- | `welcome` | lifecycle | ASCII π banner + startup health checks (version, auth, models, gitignore) |
10
- | `footer` | UI | Status bar: mode / git branch / model / cost / tps |
11
- | `models` | command | `/models` — enhanced model picker with BenchLM rank, context, cost |
12
- | `update` | command | `/update` — self-update Pi + refresh extensions from dotfiles |
13
- | `lg` | command | `/lg` summarize unstaged git changes with per-file +/- counts |
14
- | `yeet` | command | `/yeet` stage all, commit, and push current changes |
15
- | `copy-all` | command | `/copy-all` copy the whole conversation to the clipboard |
16
- | `diff` | command | `/diff` list/open files changed during the last agent run |
17
- | `todo` | tool | durable execution checklist (survives compaction) |
18
- | `toolbox` | tool | `search` (fuzzy-find every tool / MCP tool / skill / command), `enable` / `disable` (turn a gated tool on/off by name) |
19
- | `nudges` | hooks | model-steering reminders (tools / skill / capability) |
9
+ | Package | Description |
10
+ |---|---|
11
+ | `pix-welcome` | ASCII π banner + startup health checks (version, auth, models, gitignore) |
12
+ | `pix-footer` | Status bar: mode / git branch / model / cost / live TPS |
13
+ | `pix-models` | `/models` — enhanced model picker with BenchLM rank, context, cost |
14
+ | `pix-update` | `/update` — self-update Pi + refresh extensions |
15
+ | `pix-commands` | `/diff` and `/clear` slash commands |
16
+ | `pix-diagnostics` | Compact LSP diagnostic widget |
17
+ | `pix-prompts` | System-prompt injection (AGENTS.md + repo directive files) |
18
+ | `pix-skills` | Agent skill loader (`read_skills` tool + 21 bundled skills) |
19
+ | `pix-nudge` | Tool + capability nudge hooks |
20
20
 
21
21
  ## Install
22
22
 
23
23
  ```bash
24
- pi install git:github.com/xynogen/pix-core
24
+ pi install npm:@xynogen/pix-core
25
25
  ```
26
26
 
27
- > **Requires** [pix-data](https://github.com/xynogen/pix-data) for shared models.dev + BenchLM cache.
28
- > Install both:
29
- > ```bash
30
- > pi install git:github.com/xynogen/pix-data
31
- > pi install git:github.com/xynogen/pix-core
32
- > ```
27
+ > Includes the core pix UI/UX packages and installs their dependencies.
28
+
29
+ ## Full distro
30
+
31
+ To install the complete pix suite (all packages + Pi itself):
32
+
33
+ ```bash
34
+ curl -fsSL https://raw.githubusercontent.com/xynogen/pix-mono/main/scripts/install.sh | sh
35
+ ```
33
36
 
34
37
  ## License
35
38
 
package/package.json CHANGED
@@ -1,26 +1,15 @@
1
1
  {
2
2
  "name": "@xynogen/pix-core",
3
- "version": "0.2.4",
4
- "description": "Pi extension — core UI/UX bundle (welcome banner, footer, model picker, self-update)",
3
+ "version": "0.3.1",
4
+ "description": "Pi extension bundle installs all core pix-* packages",
5
5
  "type": "module",
6
- "main": "src/index.ts",
7
6
  "scripts": {
8
7
  "test": "bun test"
9
8
  },
10
9
  "files": [
11
- "src",
12
- "skills",
13
10
  "README.md",
14
11
  "LICENSE"
15
12
  ],
16
- "pi": {
17
- "extensions": [
18
- "src/index.ts"
19
- ],
20
- "skills": [
21
- "./skills"
22
- ]
23
- },
24
13
  "keywords": [
25
14
  "pi",
26
15
  "pi-package",
@@ -42,12 +31,17 @@
42
31
  "access": "public"
43
32
  },
44
33
  "dependencies": {
45
- "@xynogen/pix-data": "*",
34
+ "@xynogen/pix-welcome": "*",
35
+ "@xynogen/pix-footer": "*",
36
+ "@xynogen/pix-diagnostics": "*",
37
+ "@xynogen/pix-prompts": "*",
46
38
  "@xynogen/pix-skills": "*",
47
- "typebox": "^1.1.38"
39
+ "@xynogen/pix-models": "*",
40
+ "@xynogen/pix-update": "*",
41
+ "@xynogen/pix-commands": "*",
42
+ "@xynogen/pix-nudge": "*"
48
43
  },
49
44
  "peerDependencies": {
50
- "@earendil-works/pi-coding-agent": "*",
51
- "@earendil-works/pi-tui": "*"
45
+ "@earendil-works/pi-coding-agent": "*"
52
46
  }
53
47
  }
@@ -1,48 +0,0 @@
1
- ---
2
- name: ask-user
3
- description: "MUST use before high-stakes/irreversible decisions or when requirements are ambiguous. Gather context, present 2-5 options via ask_user, get explicit choice, then proceed."
4
- metadata:
5
- short-description: Decision gate for ambiguity and high-stakes choices
6
- ---
7
-
8
- # ask_user decision gate
9
-
10
- Decision control, not chit-chat.
11
-
12
- ## Gate (call ask_user before proceeding if ANY true)
13
- - changes architecture/schema/API/deploy/security
14
- - costly to undo (big refactor, migration, destructive edit, prod behavior)
15
- - requirements unclear/conflicting/missing
16
- - multiple valid options, trade-off is preference-dependent
17
- - about to assume something that changes implementation
18
-
19
- Skip only if user already gave an explicit decision for THIS exact trade-off.
20
-
21
- ## Handshake
22
- 1. classify step: high_stakes | ambiguous | both | clear. clear → no gate.
23
- 2. gather evidence first (read/bash/web/ref). don't ask blind.
24
- 3. synthesize: 3-7 bullets — state, constraints, trade-offs, recommendation.
25
- 4. ask ONE focused question: `question`, `context`(summary), `options`(2-5),
26
- `allowMultiple:false` unless truly independent, `allowFreeform:true`.
27
- `inline` displayMode when the preceding summary must stay visible.
28
- 5. commit: restate decision, say next step, proceed.
29
- 6. re-ask only on materially new ambiguity. no confirm loops.
30
-
31
- ## Budget (anti-overasking)
32
- - max 1 call per boundary normally; max 2 if first is unclear/cancelled.
33
- - never re-ask same trade-off without new evidence.
34
- - attempt 2 = narrower question: [Proceed w/ recommended] [Choose other (freeform)] [Stop].
35
- - after attempt 2: high_stakes/both → STOP, mark blocked.
36
- ambiguous-only + user says "your call" → take most reversible default, state assumptions.
37
-
38
- ## Quality
39
- - question: concrete decision boundary, one decision only.
40
- - options: short, outcome-oriented, explicit trade-offs; add description when non-obvious.
41
-
42
- ## Anti-patterns
43
- asking without context · trivial formatting choices · forcing options when freeform fits ·
44
- repeat questions w/o new info · proceeding high-stakes after unclear/cancelled answer.
45
-
46
- ## On cancel / unclear
47
- Pause, explain what's blocked. At most one narrower follow-up.
48
- Then: high-stakes → stay blocked until explicit decision; ambiguity-only → proceed only if user delegated.
@@ -1,58 +0,0 @@
1
- /**
2
- * agent-sop — inject AGENT.md (from pix-skills) into system prompt
3
- *
4
- * Reads AGENT.md from the @xynogen/pix-skills package and appends it to the
5
- * system prompt on every agent start via `before_agent_start`.
6
- *
7
- * This is the "register skill" mechanism for the agent operating spec — no
8
- * static SKILL.md file needed. The content becomes part of the model's
9
- * standing instructions.
10
- */
11
-
12
- import { existsSync, readFileSync } from "node:fs";
13
- import { createRequire } from "node:module";
14
- import { resolve } from "node:path";
15
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
16
-
17
- /** Resolve the absolute path to AGENT.md inside @xynogen/pix-skills. */
18
- function resolveAgentMdPath(): string | null {
19
- try {
20
- const require = createRequire(import.meta.url);
21
- const pkgJson = require.resolve("@xynogen/pix-skills/package.json");
22
- return resolve(pkgJson, "..", "AGENT.md");
23
- } catch {
24
- return null;
25
- }
26
- }
27
-
28
- /** Read and return AGENT.md content, or null if unavailable. */
29
- function loadAgentMd(): string | null {
30
- const p = resolveAgentMdPath();
31
- if (!p || !existsSync(p)) return null;
32
- try {
33
- return readFileSync(p, "utf-8");
34
- } catch {
35
- return null;
36
- }
37
- }
38
-
39
- export default function registerAgentSop(pi: ExtensionAPI): void {
40
- // Load once at startup (content is static per session).
41
- const agentMdContent = loadAgentMd();
42
-
43
- if (!agentMdContent) {
44
- // Silent skip — pix-skills might not be installed.
45
- return;
46
- }
47
-
48
- pi.on("before_agent_start", async (event) => {
49
- // Skip if already injected (idempotent check via a simple marker).
50
- if (event.systemPrompt.includes("pix-agent-sop")) {
51
- return;
52
- }
53
-
54
- return {
55
- systemPrompt: `${event.systemPrompt}\n\n<pix-agent-sop>\n${agentMdContent}\n</pix-agent-sop>`,
56
- };
57
- });
58
- }
@@ -1,32 +0,0 @@
1
- import type {
2
- ExtensionAPI,
3
- ExtensionCommandContext,
4
- } from "@earendil-works/pi-coding-agent";
5
-
6
- async function clearCache(pi: ExtensionAPI, ctx: ExtensionCommandContext) {
7
- ctx.ui.notify("Clearing ~/.cache/pi", "info");
8
- const result = await pi.exec("/bin/sh", ["-lc", 'rm -rf "$HOME/.cache/pi"'], {
9
- timeout: 10_000,
10
- });
11
- const output = [result.stdout, result.stderr]
12
- .filter(Boolean)
13
- .join("\n")
14
- .trim();
15
- if ((result.code ?? 0) !== 0) {
16
- ctx.ui.notify(`Cache clear failed. ${output || "No output."}`, "error");
17
- return;
18
- }
19
- ctx.ui.notify(
20
- "~/.cache/pi cleared. Run /reload to apply changes.",
21
- "warning",
22
- );
23
- }
24
-
25
- export default function (pi: ExtensionAPI) {
26
- pi.registerCommand("clear", {
27
- description: "Remove ~/.cache/pi and reload",
28
- handler: async (_args, ctx) => {
29
- await clearCache(pi, ctx);
30
- },
31
- });
32
- }
@@ -1,32 +0,0 @@
1
- /**
2
- * /diff — explain unstaged git diff with per-file +/- counts.
3
- *
4
- * The agent runs `git status` + `git diff`, then replies with:
5
- * 1. 1–2 sentence explanation of what changed
6
- * 2. Per-file +/- line counts
7
- * 3. Total +/- line count
8
- */
9
-
10
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
11
-
12
- const DIFF_PROMPT = `Run git status and inspect the unstaged git diff, then respond with only:
13
-
14
- 1. A short 1-2 sentence explanation of what changed and why it matters.
15
- 2. A list of changed unstaged files with their +/- line counts.
16
- 3. A total +/- line count at the bottom.
17
-
18
- Keep it concise. Use git commands to calculate the line counts. Base the summary on the actual diff, not only filenames. Do not include staged changes unless they also have unstaged modifications.`;
19
-
20
- export default function (pi: ExtensionAPI) {
21
- pi.registerCommand("diff", {
22
- description: "Explain unstaged git diff with per-file +/- counts",
23
- handler: async (_args, ctx) => {
24
- if (!ctx.isIdle()) {
25
- pi.sendUserMessage(DIFF_PROMPT, { deliverAs: "followUp" });
26
- ctx.ui.notify("Queued /diff after the current turn finishes.", "info");
27
- return;
28
- }
29
- pi.sendUserMessage(DIFF_PROMPT);
30
- },
31
- });
32
- }
@@ -1,95 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { benchStars, fmtCost, fmtCtx, sortModels } from "./models.ts";
3
-
4
- describe("fmtCtx", () => {
5
- it("formats 0 as 0", () => expect(fmtCtx(0)).toBe("0"));
6
- it("formats small numbers as-is", () => expect(fmtCtx(512)).toBe("512"));
7
- it("formats thousands as Nk", () => {
8
- expect(fmtCtx(128_000)).toBe("128k");
9
- expect(fmtCtx(8_192)).toBe("8k");
10
- });
11
- it("formats millions as NM", () => {
12
- expect(fmtCtx(1_000_000)).toBe("1M");
13
- expect(fmtCtx(2_000_000)).toBe("2M");
14
- expect(fmtCtx(1_500_000)).toBe("1.5M");
15
- });
16
- });
17
-
18
- describe("fmtCost", () => {
19
- it("returns — for undefined entry", () =>
20
- expect(fmtCost(undefined)).toBe("—"));
21
- it("returns — when no cost field", () => expect(fmtCost({})).toBe("—"));
22
- it("returns free when both 0", () => {
23
- expect(fmtCost({ cost: { input: 0, output: 0 } })).toBe("free");
24
- });
25
- it("formats input/output costs", () => {
26
- expect(fmtCost({ cost: { input: 3, output: 15 } })).toBe("3.00/15.00");
27
- });
28
- it("handles missing input/output as 0", () => {
29
- expect(fmtCost({ cost: {} })).toBe("free");
30
- });
31
- });
32
-
33
- describe("benchStars", () => {
34
- it("gives 5 stars for score >= 90", () => {
35
- expect(benchStars(95).filled).toBe(5);
36
- expect(benchStars(90).filled).toBe(5);
37
- });
38
- it("gives 4 stars for 80-89", () => {
39
- expect(benchStars(85).filled).toBe(4);
40
- expect(benchStars(80).filled).toBe(4);
41
- });
42
- it("gives 3 stars for 70-79", () => {
43
- expect(benchStars(75).filled).toBe(3);
44
- });
45
- it("gives 2 stars for 50-69", () => {
46
- expect(benchStars(60).filled).toBe(2);
47
- expect(benchStars(50).filled).toBe(2);
48
- });
49
- it("gives 1 star for score < 50", () => {
50
- expect(benchStars(30).filled).toBe(1);
51
- expect(benchStars(0).filled).toBe(1);
52
- });
53
- it("gives 1 star for null/undefined", () => {
54
- expect(benchStars(null).filled).toBe(1);
55
- expect(benchStars(undefined).filled).toBe(1);
56
- });
57
- it("filled + empty always = 5", () => {
58
- for (const s of [0, 50, 70, 80, 90, 100]) {
59
- const { filled, empty } = benchStars(s);
60
- expect(filled + empty).toBe(5);
61
- }
62
- });
63
- });
64
-
65
- describe("sortModels", () => {
66
- const models = [
67
- { provider: "a", id: "m1", name: "Zebra", score: 80 },
68
- { provider: "a", id: "m2", name: "Alpha", score: 95 },
69
- { provider: "a", id: "m3", name: "Middle", score: null },
70
- { provider: "a", id: "m4", name: "Beta", score: 80 },
71
- ];
72
-
73
- it("sorts by score descending", () => {
74
- const sorted = sortModels(models);
75
- expect(sorted[0].name).toBe("Alpha"); // score 95
76
- });
77
-
78
- it("breaks score ties alphabetically by name", () => {
79
- const sorted = sortModels(models);
80
- const tiedIdx = sorted.findIndex((m) => m.name === "Beta");
81
- const zebraIdx = sorted.findIndex((m) => m.name === "Zebra");
82
- expect(tiedIdx).toBeLessThan(zebraIdx); // Beta before Zebra, both score 80
83
- });
84
-
85
- it("puts null score models last", () => {
86
- const sorted = sortModels(models);
87
- expect(sorted[sorted.length - 1].name).toBe("Middle");
88
- });
89
-
90
- it("does not mutate the original array", () => {
91
- const original = [...models];
92
- sortModels(models);
93
- expect(models).toEqual(original);
94
- });
95
- });