@preapexis/pi-kit 1.0.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.
@@ -0,0 +1,245 @@
1
+ // cSpell:words Varun Gaikwad preapexis npm PowerShell
2
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
+
4
+ type EventContext = Parameters<Parameters<ExtensionAPI["on"]>[1]>[1];
5
+
6
+ type UpdateOption = {
7
+ id: string;
8
+ label: string;
9
+ shell: string;
10
+ };
11
+
12
+ type ShellCommand = {
13
+ command: string;
14
+ args: string[];
15
+ };
16
+
17
+ const RUN_CHOICE = "▶ Run selected updates";
18
+ const CANCEL_CHOICE = "✕ Cancel";
19
+
20
+ export default function (pi: ExtensionAPI): void {
21
+ const options: UpdateOption[] = [
22
+ {
23
+ id: "pi",
24
+ label: "Update Pi CLI globally",
25
+ shell:
26
+ "npm install -g --ignore-scripts @earendil-works/pi-coding-agent@latest"
27
+ },
28
+ {
29
+ id: "kit-local",
30
+ label: "Re-link this local PreApeXis kit",
31
+ shell: "pi install -l ."
32
+ },
33
+ {
34
+ id: "kit-github",
35
+ label: "Update PreApeXis Pi Kit from GitHub",
36
+ shell: "pi install git:github.com/VarunGaikwad/preapexis-pi-kit@master"
37
+ },
38
+ {
39
+ id: "kit-npm",
40
+ label: "Update PreApeXis Pi Kit from npm",
41
+ shell: "pi install npm:preapexis-pi-kit"
42
+ },
43
+ {
44
+ id: "project-npm",
45
+ label: "Update current project npm packages",
46
+ shell: "npm update"
47
+ }
48
+ ];
49
+
50
+ function shellCommand(command: string): ShellCommand {
51
+ if (process.platform === "win32") {
52
+ return {
53
+ command: "cmd",
54
+ args: ["/d", "/s", "/c", command]
55
+ };
56
+ }
57
+
58
+ return {
59
+ command: "sh",
60
+ args: ["-lc", command]
61
+ };
62
+ }
63
+
64
+ function optionLabel(
65
+ option: UpdateOption,
66
+ selected: Map<string, UpdateOption>
67
+ ): string {
68
+ const marker = selected.has(option.id) ? "✓" : "×";
69
+ return `${marker} ${option.label}`;
70
+ }
71
+
72
+ function selectedSummary(selected: UpdateOption[]): string {
73
+ if (selected.length === 0) {
74
+ return "Selected: none";
75
+ }
76
+
77
+ return ["Selected:", ...selected.map((option) => `- ${option.label}`)].join(
78
+ "\n"
79
+ );
80
+ }
81
+
82
+ function buildMenu(selected: Map<string, UpdateOption>): {
83
+ items: string[];
84
+ optionByMenuItem: Map<string, UpdateOption>;
85
+ } {
86
+ const optionByMenuItem = new Map<string, UpdateOption>();
87
+
88
+ const optionItems = options.map((option) => {
89
+ const label = optionLabel(option, selected);
90
+ optionByMenuItem.set(label, option);
91
+ return label;
92
+ });
93
+
94
+ return {
95
+ items: [...optionItems, RUN_CHOICE, CANCEL_CHOICE],
96
+ optionByMenuItem
97
+ };
98
+ }
99
+
100
+ async function chooseUpdates(ctx: EventContext): Promise<UpdateOption[]> {
101
+ const selected = new Map<string, UpdateOption>();
102
+
103
+ while (true) {
104
+ const { items, optionByMenuItem } = buildMenu(selected);
105
+
106
+ const choice = await ctx.ui.select(
107
+ [
108
+ "What do you want to update?",
109
+ "",
110
+ "Pick an item to toggle it.",
111
+ "✓ = selected, × = not selected",
112
+ "",
113
+ selectedSummary([...selected.values()])
114
+ ].join("\n"),
115
+ items
116
+ );
117
+
118
+ if (!choice || choice === CANCEL_CHOICE) {
119
+ return [];
120
+ }
121
+
122
+ if (choice === RUN_CHOICE) {
123
+ if (selected.size === 0) {
124
+ ctx.ui.notify("Select at least one update first.", "warning");
125
+ continue;
126
+ }
127
+
128
+ return [...selected.values()];
129
+ }
130
+
131
+ const option = optionByMenuItem.get(choice);
132
+
133
+ if (!option) {
134
+ continue;
135
+ }
136
+
137
+ if (selected.has(option.id)) {
138
+ selected.delete(option.id);
139
+ ctx.ui.notify(`Removed: ${option.label}`, "info");
140
+ } else {
141
+ selected.set(option.id, option);
142
+ ctx.ui.notify(`Added: ${option.label}`, "info");
143
+ }
144
+ }
145
+ }
146
+
147
+ async function runUpdate(
148
+ option: UpdateOption,
149
+ ctx: EventContext
150
+ ): Promise<boolean> {
151
+ ctx.ui.notify(`Running update:\n\n${option.shell}`, "info");
152
+
153
+ const shell = shellCommand(option.shell);
154
+
155
+ const result = await pi.exec(shell.command, shell.args, {
156
+ cwd: ctx.cwd
157
+ });
158
+
159
+ const stdout = result.stdout?.trim() ?? "";
160
+ const stderr = result.stderr?.trim() ?? "";
161
+ const output = [stdout, stderr].filter(Boolean).join("\n\n");
162
+
163
+ if (result.code === 0) {
164
+ ctx.ui.notify(
165
+ [`Update completed: ${option.label}`, "", output || "No output."].join(
166
+ "\n"
167
+ ),
168
+ "info"
169
+ );
170
+
171
+ return true;
172
+ }
173
+
174
+ ctx.ui.notify(
175
+ [
176
+ `Update failed: ${option.label}`,
177
+ "",
178
+ `Command: ${option.shell}`,
179
+ `Exit code: ${result.code}`,
180
+ "",
181
+ output ||
182
+ "No output captured. Try running this command manually in PowerShell."
183
+ ].join("\n"),
184
+ "error"
185
+ );
186
+
187
+ return false;
188
+ }
189
+
190
+ pi.registerCommand("update", {
191
+ description: "Update Pi, this Pi kit, or project packages",
192
+ handler: async (_args, ctx) => {
193
+ if (!ctx.hasUI) {
194
+ console.log("The /update command requires the Pi UI.");
195
+ return;
196
+ }
197
+
198
+ const selected = await chooseUpdates(ctx);
199
+
200
+ if (selected.length === 0) {
201
+ ctx.ui.notify("Update cancelled.", "info");
202
+ return;
203
+ }
204
+
205
+ const summary = selected
206
+ .map((option) => `- ${option.label}\n ${option.shell}`)
207
+ .join("\n");
208
+
209
+ const ok = await ctx.ui.confirm(
210
+ "Confirm updates",
211
+ `The following updates will run:\n\n${summary}\n\nContinue?`
212
+ );
213
+
214
+ if (!ok) {
215
+ ctx.ui.notify("Update cancelled.", "info");
216
+ return;
217
+ }
218
+
219
+ let passed = 0;
220
+ let failed = 0;
221
+
222
+ for (const option of selected) {
223
+ const updateOk = await runUpdate(option, ctx);
224
+
225
+ if (updateOk) {
226
+ passed += 1;
227
+ } else {
228
+ failed += 1;
229
+ }
230
+ }
231
+
232
+ ctx.ui.notify(
233
+ [
234
+ "Update finished.",
235
+ "",
236
+ `Passed: ${passed}`,
237
+ `Failed: ${failed}`,
238
+ "",
239
+ "Run /reload or restart Pi if needed."
240
+ ].join("\n"),
241
+ failed > 0 ? "warning" : "info"
242
+ );
243
+ }
244
+ });
245
+ }
@@ -0,0 +1,154 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+
3
+ type EventContext = Parameters<Parameters<ExtensionAPI["on"]>[1]>[1];
4
+
5
+ type AssistantMessageLike = {
6
+ role: "assistant";
7
+ usage?: {
8
+ input?: number;
9
+ output?: number;
10
+ cacheRead?: number;
11
+ cacheWrite?: number;
12
+ cost?: {
13
+ total?: number;
14
+ };
15
+ };
16
+ };
17
+ type UsageTotals = {
18
+ input: number;
19
+ output: number;
20
+ cacheRead: number;
21
+ cacheWrite: number;
22
+ totalTokens: number;
23
+ cost: number;
24
+ };
25
+
26
+ export default function (pi: ExtensionAPI): void {
27
+ const STATUS_KEY = "usage-tracker";
28
+ let baseline: UsageTotals = emptyTotals();
29
+
30
+ function emptyTotals(): UsageTotals {
31
+ return {
32
+ input: 0,
33
+ output: 0,
34
+ cacheRead: 0,
35
+ cacheWrite: 0,
36
+ totalTokens: 0,
37
+ cost: 0
38
+ };
39
+ }
40
+
41
+ function formatNumber(value: number): string {
42
+ if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}m`;
43
+ if (value >= 1_000) return `${(value / 1_000).toFixed(1)}k`;
44
+ return String(value);
45
+ }
46
+
47
+ function formatCost(value: number): string {
48
+ if (value < 0.001) return "$0.000";
49
+ return `$${value.toFixed(3)}`;
50
+ }
51
+
52
+ function subtractTotals(
53
+ current: UsageTotals,
54
+ base: UsageTotals
55
+ ): UsageTotals {
56
+ return {
57
+ input: Math.max(0, current.input - base.input),
58
+ output: Math.max(0, current.output - base.output),
59
+ cacheRead: Math.max(0, current.cacheRead - base.cacheRead),
60
+ cacheWrite: Math.max(0, current.cacheWrite - base.cacheWrite),
61
+ totalTokens: Math.max(0, current.totalTokens - base.totalTokens),
62
+ cost: Math.max(0, current.cost - base.cost)
63
+ };
64
+ }
65
+
66
+ function getUsageTotals(ctx: EventContext): UsageTotals {
67
+ const totals = emptyTotals();
68
+
69
+ for (const entry of ctx.sessionManager.getBranch()) {
70
+ if (entry.type !== "message") continue;
71
+ if (entry.message.role !== "assistant") continue;
72
+
73
+ const message = entry.message as AssistantMessageLike;
74
+ const usage = message.usage;
75
+
76
+ if (!usage) continue;
77
+
78
+ totals.input += usage.input ?? 0;
79
+ totals.output += usage.output ?? 0;
80
+ totals.cacheRead += usage.cacheRead ?? 0;
81
+ totals.cacheWrite += usage.cacheWrite ?? 0;
82
+ totals.cost += usage.cost?.total ?? 0;
83
+ }
84
+
85
+ totals.totalTokens =
86
+ totals.input + totals.output + totals.cacheRead + totals.cacheWrite;
87
+
88
+ return totals;
89
+ }
90
+
91
+ function updateStatus(ctx: EventContext): void {
92
+ if (!ctx.hasUI) return;
93
+
94
+ const totals = subtractTotals(getUsageTotals(ctx), baseline);
95
+
96
+ ctx.ui.setStatus(
97
+ STATUS_KEY,
98
+ `usage: ${formatNumber(totals.totalTokens)} ${formatCost(totals.cost)}`
99
+ );
100
+ }
101
+
102
+ function buildUsageReport(ctx: EventContext): string {
103
+ const totals = subtractTotals(getUsageTotals(ctx), baseline);
104
+
105
+ return [
106
+ "Token and price usage",
107
+ "",
108
+ `Input tokens: ${totals.input.toLocaleString()}`,
109
+ `Output tokens: ${totals.output.toLocaleString()}`,
110
+ `Cache read tokens: ${totals.cacheRead.toLocaleString()}`,
111
+ `Cache write tokens: ${totals.cacheWrite.toLocaleString()}`,
112
+ `Total tokens: ${totals.totalTokens.toLocaleString()}`,
113
+ `Estimated cost: ${formatCost(totals.cost)}`,
114
+ "",
115
+ `Model: ${ctx.model?.id ?? "unknown"}`
116
+ ].join("\n");
117
+ }
118
+
119
+ pi.on("session_start", async (_event, ctx) => {
120
+ baseline = emptyTotals();
121
+ updateStatus(ctx);
122
+ });
123
+
124
+ pi.on("message_end", async (event, ctx) => {
125
+ if (event.message.role === "assistant") {
126
+ updateStatus(ctx);
127
+ }
128
+ });
129
+
130
+ pi.on("model_select", async (_event, ctx) => {
131
+ updateStatus(ctx);
132
+ });
133
+
134
+ pi.registerCommand("usage", {
135
+ description: "Show token and price usage for this session",
136
+ handler: async (_args, ctx) => {
137
+ if (!ctx.hasUI) return;
138
+ ctx.ui.notify(buildUsageReport(ctx), "info");
139
+ updateStatus(ctx);
140
+ }
141
+ });
142
+
143
+ pi.registerCommand("usage-reset", {
144
+ description: "Reset token and price tracker baseline",
145
+ handler: async (_args, ctx) => {
146
+ baseline = getUsageTotals(ctx);
147
+
148
+ if (ctx.hasUI) {
149
+ ctx.ui.notify("Usage tracker reset for this session.", "info");
150
+ updateStatus(ctx);
151
+ }
152
+ }
153
+ });
154
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@preapexis/pi-kit",
3
+ "version": "1.0.0",
4
+ "description": "Personal Pi coding-agent kit with safety extensions, status UI, prompt workflows, skills, and themes.",
5
+ "keywords": [
6
+ "pi-package",
7
+ "pi-coding-agent",
8
+ "coding-agent",
9
+ "prompts",
10
+ "skills",
11
+ "extensions"
12
+ ],
13
+ "pi": {
14
+ "extensions": [
15
+ "./extensions"
16
+ ],
17
+ "skills": [
18
+ "./skills"
19
+ ],
20
+ "prompts": [
21
+ "./prompts"
22
+ ],
23
+ "themes": [
24
+ "./themes"
25
+ ]
26
+ },
27
+ "peerDependencies": {
28
+ "@earendil-works/pi-coding-agent": "*"
29
+ },
30
+ "author": "Varun Gaikwad",
31
+ "license": "ISC",
32
+ "type": "module",
33
+ "scripts": {
34
+ "test": "vitest run",
35
+ "test:watch": "vitest"
36
+ },
37
+ "devDependencies": {
38
+ "typescript": "^5.5.0",
39
+ "vitest": "^2.0.0"
40
+ }
41
+ }
@@ -0,0 +1,79 @@
1
+ # Git Commit Message
2
+
3
+ Analyze the current changes in this repository and propose a git commit message.
4
+
5
+ Do not create the commit. Only suggest the message.
6
+
7
+ ## How to read the changes
8
+
9
+ Run safe git read commands only:
10
+
11
+ - `git status`
12
+ - `git diff`
13
+ - `git diff --cached`
14
+ - `git log --oneline -5` if useful for style
15
+
16
+ If there is nothing staged and nothing unstaged, report that no changes are ready to commit.
17
+
18
+ ## Output format
19
+
20
+ Provide a commit message in this format:
21
+
22
+ ```txt
23
+ <type>: <short summary>
24
+
25
+ <plain-language explanation>
26
+
27
+ <technical explanation>
28
+
29
+ - <specific change 1>
30
+ - <specific change 2>
31
+ ```
32
+
33
+ ## Guidelines
34
+
35
+ 1. Use a conventional commit type:
36
+ - `feat`
37
+ - `fix`
38
+ - `refactor`
39
+ - `docs`
40
+ - `test`
41
+ - `chore`
42
+ - `style`
43
+ - `build`
44
+ - `ci`
45
+
46
+ 2. The short summary must be:
47
+ - one line
48
+ - concise
49
+ - imperative mood
50
+ - no period at the end
51
+
52
+ 3. The plain-language paragraph should explain what changed and why.
53
+
54
+ 4. The technical paragraph should explain the implementation details briefly.
55
+
56
+ 5. The bullet list should include the most important changes.
57
+
58
+ ## Split commits
59
+
60
+ If there are many unrelated changes, suggest splitting them into multiple commits.
61
+
62
+ For each group, provide:
63
+
64
+ ```txt
65
+ Commit 1:
66
+ <message>
67
+
68
+ Commit 2:
69
+ <message>
70
+ ```
71
+
72
+ ## Rules
73
+
74
+ - Do not commit.
75
+ - Do not stage files.
76
+ - Do not edit files.
77
+ - Do not run destructive git commands.
78
+ - Do not include secrets or long raw diffs in the message.
79
+ - Keep the message clear and useful for humans.
@@ -0,0 +1,41 @@
1
+ # Implement Plan
2
+
3
+ Implement the plan below. Do not change scope unless you ask first.
4
+
5
+ ## Plan
6
+
7
+ {PLAN_CONTENT}
8
+
9
+ Or, if the plan is saved as a file, provide the path and read it first.
10
+
11
+ ## Steps
12
+
13
+ 1. Read the plan carefully.
14
+ 2. Read the project files mentioned in the plan.
15
+ 3. Briefly explain the implementation approach before editing.
16
+ 4. Implement the steps one by one.
17
+ 5. Keep changes small and reviewable.
18
+ 6. Run tests, lint, or type checks if available.
19
+ 7. Do not delete files or rewrite history without approval.
20
+
21
+ ## Rules
22
+
23
+ - Follow the plan closely.
24
+ - If something is unclear or blocked, stop and ask.
25
+ - Do not guess important missing details.
26
+ - Prefer editing existing files when it fits the existing project structure.
27
+ - Create new files only when the plan clearly requires it.
28
+ - Match the project's existing style and conventions.
29
+ - Add or update tests for new behavior when appropriate.
30
+ - Run available checks: tests, lint, type check.
31
+ - Do not touch secrets, env files, lockfiles, or generated files unless the plan explicitly requires it and the user approves.
32
+
33
+ ## Output
34
+
35
+ At the end, summarize:
36
+
37
+ - What was implemented
38
+ - Files changed
39
+ - Tests added or updated
40
+ - Commands run
41
+ - Anything still pending
@@ -0,0 +1,98 @@
1
+ # Initialize Repository Understanding
2
+
3
+ Initialize your understanding of this repository.
4
+
5
+ Do not edit files.
6
+
7
+ Your job is to inspect the project and produce a clear onboarding report for future work.
8
+
9
+ If the repository is large, inspect the most important files first and clearly mention what was not inspected.
10
+
11
+ ## Steps
12
+
13
+ 1. Read the most important project files first:
14
+ - README.md
15
+ - AGENTS.md
16
+ - package.json / pyproject.toml / go.mod / Cargo.toml / pom.xml / build.gradle
17
+ - CONTRIBUTING.md
18
+ - docs/
19
+ - .github/workflows/
20
+ - docker-compose.yml / Dockerfile
21
+ - existing test configuration
22
+
23
+ 2. Identify the project structure:
24
+ - main application entry points
25
+ - important directories
26
+ - test locations
27
+ - config files
28
+ - generated/build output directories
29
+ - files that should be treated carefully
30
+
31
+ 3. Detect the development workflow:
32
+ - install command
33
+ - dev command
34
+ - build command
35
+ - test command
36
+ - lint command
37
+ - typecheck command
38
+ - formatting command
39
+
40
+ 4. Identify safety risks:
41
+ - secrets or env files
42
+ - deployment files
43
+ - database migrations
44
+ - production config
45
+ - generated files
46
+ - lockfiles
47
+ - destructive scripts
48
+ - commands that should require confirmation
49
+
50
+ 5. Infer coding conventions:
51
+ - language/framework
52
+ - naming style
53
+ - error handling style
54
+ - testing style
55
+ - API conventions
56
+ - dependency management
57
+
58
+ ## Output
59
+
60
+ Return a repository onboarding report with these sections:
61
+
62
+ # Repository Summary
63
+
64
+ Briefly explain what this project appears to do.
65
+
66
+ # Tech Stack
67
+
68
+ List the detected languages, frameworks, libraries, and tools.
69
+
70
+ # Project Map
71
+
72
+ Explain the important folders and files.
73
+
74
+ # Common Commands
75
+
76
+ List install, dev, build, test, lint, typecheck, and format commands if found.
77
+
78
+ # Safety Notes
79
+
80
+ List files, directories, and commands that should be treated carefully.
81
+
82
+ # Coding Conventions
83
+
84
+ Summarize the project’s apparent patterns.
85
+
86
+ # Recommended Agent Rules
87
+
88
+ Suggest repo-specific rules that should be added to AGENTS.md or team instructions.
89
+
90
+ # Open Questions
91
+
92
+ List anything unclear or missing.
93
+
94
+ # Inspection Notes
95
+
96
+ Mention important files or areas that were not inspected.
97
+
98
+ Do not modify the repository unless I explicitly ask.