@lebronj/pi-suite 0.1.11 → 0.1.13
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 +5 -3
- package/package.json +1 -1
- package/skills/pi-skill/SKILL.md +1 -3
- package/vendor/pi-memory/src/service-controller.ts +57 -8
- package/extensions/redraws.ts +0 -24
- package/extensions/simple-replies.ts +0 -10
- package/skills/leetcode-array/SKILL.md +0 -40
- package/skills/leetcode-array/problems/best_time_to_buy_and_sell_stock.py +0 -19
- package/skills/leetcode-array/problems/product_of_array_except_self.py +0 -22
- package/skills/leetcode-array/problems/two_sum.py +0 -19
- package/skills/weather/SKILL.md +0 -49
package/README.md
CHANGED
|
@@ -12,14 +12,14 @@ pi install npm:@lebronj/pi-suite
|
|
|
12
12
|
Or use the bootstrap script to install Pi, configure the team OpenAI-compatible endpoint, install this suite, and set up Bun + qmd for memory search:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
curl -fsSL https://registry.npmjs.org/@lebronj/pi-suite/-/pi-suite-0.1.
|
|
15
|
+
curl -fsSL https://registry.npmjs.org/@lebronj/pi-suite/-/pi-suite-0.1.13.tgz | tar -xzO package/scripts/bootstrap.sh | bash
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## What Is Included
|
|
19
19
|
|
|
20
|
-
- Local extensions: autogoal, goal mode, pet, prompt URL widget,
|
|
20
|
+
- Local extensions: autogoal, goal mode, pet, prompt URL widget, snake, TPS notifications.
|
|
21
21
|
- Prompts: changelog audit, issue analysis, PR review, review workflow, commit workflow, wrap workflow.
|
|
22
|
-
- Skills: provider checklist,
|
|
22
|
+
- Skills: provider checklist, Pi capability reference, image-to-editable-PPT workflow.
|
|
23
23
|
- Vendored package: `@jhp/pi-memory`, including qmd search, external curator service, and memory/skill-draft versioning.
|
|
24
24
|
|
|
25
25
|
`pi-mcp-adapter`, `pi-subagents`, and `pi-web-access` are loaded by this suite. Do not install them separately unless you filter suite resources, because duplicate extension tools/flags can conflict.
|
|
@@ -98,6 +98,8 @@ qmd embed
|
|
|
98
98
|
|
|
99
99
|
Memory versioning is enabled by default. It snapshots `~/.pi/agent/memory` and `~/.pi/agent/skill-drafts` into `~/.pi/agent/evolution`, commits local changes automatically, and leaves push manual by default. `memory_curate` also scans yesterday's daily log into `REVIEW.md` when learning is enabled and the daily file changed since the last scan.
|
|
100
100
|
|
|
101
|
+
The external memory curator service uses a systemd user timer when available, with cron fallback. When the service points at a vendored TypeScript CLI under `node_modules`, the launcher uses Bun or tsx instead of plain Node so Node 22 can run it reliably.
|
|
102
|
+
|
|
101
103
|
Useful commands:
|
|
102
104
|
|
|
103
105
|
```bash
|
package/package.json
CHANGED
package/skills/pi-skill/SKILL.md
CHANGED
|
@@ -95,6 +95,7 @@ External curator service:
|
|
|
95
95
|
|
|
96
96
|
- This is the main self-evolution maintenance loop: it can run daily outside the pi process, even when pi is closed.
|
|
97
97
|
- It uses a systemd user timer when available, with cron fallback.
|
|
98
|
+
- For vendored TypeScript CLI paths under `node_modules`, the service launcher uses Bun or tsx instead of plain Node so Node 22 does not fail on TypeScript type stripping.
|
|
98
99
|
- On `session_start` and after `/reload`, pi-memory checks service status. If the service is disabled and UI is available, it shows a startup hint with enable/status/disable commands.
|
|
99
100
|
- Enable with `/memory-curator-enable 03:00` or ask the agent to call `memory_curator_enable`.
|
|
100
101
|
- Inspect with `/memory-curator-status` or `memory_curator_status`.
|
|
@@ -206,7 +207,6 @@ The pet extension provides a small terminal companion and durable profile.
|
|
|
206
207
|
## UI And Utility Extensions
|
|
207
208
|
|
|
208
209
|
- `prompt-url-widget.ts`: detects PR/issue prompt templates, fetches GitHub metadata with `gh`, shows a widget, and names the session when possible.
|
|
209
|
-
- `redraws.ts`: `/tui` shows TUI full redraw stats.
|
|
210
210
|
- `snake.ts`: `/snake` opens a TUI snake game; `Esc` pauses/saves, `q` quits, arrows/WASD move.
|
|
211
211
|
- `tps.ts`: after each assistant run, shows tokens-per-second and token usage details.
|
|
212
212
|
- `memory-curator.ts`: deprecated compatibility notice only; external curation is managed by pi-memory service tools.
|
|
@@ -217,8 +217,6 @@ Suite skills currently include:
|
|
|
217
217
|
|
|
218
218
|
- `add-llm-provider`: checklist for adding providers to `packages/ai`.
|
|
219
219
|
- `image-to-editable-ppt-slide`: rebuild reference images as editable PowerPoint slides.
|
|
220
|
-
- `leetcode-array`: array problem patterns and Python references.
|
|
221
|
-
- `weather`: current weather and forecasts using no-key services.
|
|
222
220
|
- `pi-skill`: this capability index.
|
|
223
221
|
|
|
224
222
|
Optional package-provided skills can include:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { accessSync, constants, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
|
|
@@ -79,8 +79,30 @@ function writeState(state: CuratorServiceState): void {
|
|
|
79
79
|
writeFileSync(statePath(state.memoryDir), `${JSON.stringify(state, null, 2)}\n`, "utf-8");
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
function commandPath(command: string): string | undefined {
|
|
83
|
+
if (command.includes("/")) {
|
|
84
|
+
try {
|
|
85
|
+
accessSync(command, constants.X_OK);
|
|
86
|
+
return command;
|
|
87
|
+
} catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (const dir of (process.env.PATH || "").split(":")) {
|
|
92
|
+
if (!dir) continue;
|
|
93
|
+
const candidate = join(dir, command);
|
|
94
|
+
try {
|
|
95
|
+
accessSync(candidate, constants.X_OK);
|
|
96
|
+
return candidate;
|
|
97
|
+
} catch {
|
|
98
|
+
// keep searching PATH
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
82
104
|
function hasCommand(command: string): boolean {
|
|
83
|
-
return spawnSync(command, ["--version"], { stdio: "ignore" }).status === 0;
|
|
105
|
+
return commandPath(command) !== undefined || spawnSync(command, ["--version"], { stdio: "ignore" }).status === 0;
|
|
84
106
|
}
|
|
85
107
|
|
|
86
108
|
function canUseSystemdUser(): boolean {
|
|
@@ -93,6 +115,29 @@ function shellQuote(value: string): string {
|
|
|
93
115
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
94
116
|
}
|
|
95
117
|
|
|
118
|
+
function systemdQuote(value: string): string {
|
|
119
|
+
if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) return value;
|
|
120
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$/g, "\\$")}"`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
type CliInvocation = {
|
|
124
|
+
command: string;
|
|
125
|
+
args: string[];
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
function resolveCliInvocation(cliPath: string): CliInvocation {
|
|
129
|
+
const normalized = cliPath.replace(/\\/g, "/");
|
|
130
|
+
const isNodeModulesTypeScript = normalized.endsWith(".ts") && normalized.includes("/node_modules/");
|
|
131
|
+
if (!isNodeModulesTypeScript) return { command: process.execPath, args: [cliPath] };
|
|
132
|
+
|
|
133
|
+
const bunPath = commandPath("bun");
|
|
134
|
+
if (bunPath) return { command: bunPath, args: [cliPath] };
|
|
135
|
+
const tsxPath = commandPath("tsx");
|
|
136
|
+
if (tsxPath) return { command: tsxPath, args: [cliPath] };
|
|
137
|
+
|
|
138
|
+
throw new Error(`Cannot install memory curator service: Node cannot run TypeScript files under node_modules (${cliPath}). Install Bun or tsx, or publish a compiled JavaScript curator CLI.`);
|
|
139
|
+
}
|
|
140
|
+
|
|
96
141
|
function parseSchedule(schedule: string): { hour: string; minute: string } {
|
|
97
142
|
const match = /^(\d{1,2}):(\d{2})$/.exec(schedule);
|
|
98
143
|
if (!match) throw new Error(`Invalid schedule '${schedule}'. Expected HH:MM.`);
|
|
@@ -109,7 +154,8 @@ function systemdCalendar(schedule: string): string {
|
|
|
109
154
|
|
|
110
155
|
function writeSystemdUnits(memoryDir: string, cliPath: string, schedule: string): void {
|
|
111
156
|
mkdirSync(systemdUserDir(), { recursive: true });
|
|
112
|
-
const
|
|
157
|
+
const invocation = resolveCliInvocation(cliPath);
|
|
158
|
+
const execStart = [invocation.command, ...invocation.args, "run-once", "--memory-dir", memoryDir, "--reason", "systemd-timer"].map(systemdQuote).join(" ");
|
|
113
159
|
writeFileSync(
|
|
114
160
|
servicePath(),
|
|
115
161
|
[
|
|
@@ -187,9 +233,10 @@ function enableCron(memoryDir: string, cliPath: string, schedule: string): void
|
|
|
187
233
|
if (!hasCommand("crontab")) throw new Error("Neither systemd user timers nor crontab are available.");
|
|
188
234
|
const { hour, minute } = parseSchedule(schedule);
|
|
189
235
|
removeCronLine();
|
|
190
|
-
const
|
|
236
|
+
const invocation = resolveCliInvocation(cliPath);
|
|
237
|
+
const command = [invocation.command, ...invocation.args, "run-once", "--memory-dir", memoryDir, "--reason", "cron"].map(shellQuote).join(" ");
|
|
191
238
|
const existing = currentCrontab().trim();
|
|
192
|
-
const next = `${existing ? `${existing}\n` : ""}${minute} ${hour} * * * ${command}\n`;
|
|
239
|
+
const next = `${existing ? `${existing}\n` : ""}${minute} ${hour} * * * ${command} ${CRON_MARKER}\n`;
|
|
193
240
|
installCrontab(next);
|
|
194
241
|
}
|
|
195
242
|
|
|
@@ -202,7 +249,7 @@ export function enableCuratorService(options: { memoryDir?: string; cliPath: str
|
|
|
202
249
|
enableSystemd(memoryDir, options.cliPath, schedule);
|
|
203
250
|
const state: CuratorServiceState = { ...baseState, enabled: true, backend: "systemd-user", installedAt: new Date().toISOString() };
|
|
204
251
|
writeState(state);
|
|
205
|
-
return { ok: true, backend: "systemd-user", message:
|
|
252
|
+
return { ok: true, backend: "systemd-user", message: `Enabled systemd user timer for daily ${schedule} memory curation.`, state };
|
|
206
253
|
}
|
|
207
254
|
enableCron(memoryDir, options.cliPath, schedule);
|
|
208
255
|
const state: CuratorServiceState = { ...baseState, enabled: true, backend: "cron", installedAt: new Date().toISOString() };
|
|
@@ -241,8 +288,10 @@ export function getCuratorServiceStatus(options: { memoryDir?: string; cliPath:
|
|
|
241
288
|
];
|
|
242
289
|
if (state.lastError) parts.push(`Last error: ${state.lastError}`);
|
|
243
290
|
if (state.backend === "systemd-user" && hasCommand("systemctl")) {
|
|
244
|
-
const
|
|
245
|
-
|
|
291
|
+
const timerActive = spawnSync("systemctl", ["--user", "is-active", `${SERVICE_NAME}.timer`], { encoding: "utf-8" });
|
|
292
|
+
const serviceActive = spawnSync("systemctl", ["--user", "is-active", `${SERVICE_NAME}.service`], { encoding: "utf-8" });
|
|
293
|
+
parts.push(`systemd timer active: ${timerActive.stdout.trim() || "unknown"}`);
|
|
294
|
+
parts.push(`systemd service active: ${serviceActive.stdout.trim() || "unknown"}`);
|
|
246
295
|
}
|
|
247
296
|
return { ok: true, backend: state.backend, message: parts.join("\n"), state };
|
|
248
297
|
}
|
package/extensions/redraws.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Redraws Extension
|
|
3
|
-
*
|
|
4
|
-
* Exposes /tui to show TUI redraw stats.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
9
|
-
|
|
10
|
-
export default function (pi: ExtensionAPI) {
|
|
11
|
-
pi.registerCommand("tui", {
|
|
12
|
-
description: "Show TUI stats",
|
|
13
|
-
handler: async (_args, ctx) => {
|
|
14
|
-
if (!ctx.hasUI) return;
|
|
15
|
-
let redraws = 0;
|
|
16
|
-
await ctx.ui.custom<void>((tui, _theme, _keybindings, done) => {
|
|
17
|
-
redraws = tui.fullRedraws;
|
|
18
|
-
done(undefined);
|
|
19
|
-
return new Text("", 0, 0);
|
|
20
|
-
});
|
|
21
|
-
ctx.ui.notify(`TUI full redraws: ${redraws}`, "info");
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
|
|
3
|
-
export default function simpleRepliesExtension(pi: ExtensionAPI): void {
|
|
4
|
-
pi.on("before_agent_start", async (event) => {
|
|
5
|
-
if (event.prompt.trim().toLowerCase() !== "hi") return undefined;
|
|
6
|
-
return {
|
|
7
|
-
systemPrompt: `${event.systemPrompt}\n\nFor this turn only, the user's entire message is "hi". Reply exactly: hi`,
|
|
8
|
-
};
|
|
9
|
-
});
|
|
10
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: leetcode-array
|
|
3
|
-
description: Common LeetCode array problems with Python reference solutions. Use when the user wants array practice, standard solution patterns, or quick runnable examples.
|
|
4
|
-
metadata:
|
|
5
|
-
clawdbot:
|
|
6
|
-
emoji: "🧮"
|
|
7
|
-
requires:
|
|
8
|
-
files: ["problems/*.py"]
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# LeetCode Array
|
|
12
|
-
|
|
13
|
-
Use this skill when the user wants representative LeetCode array exercises with runnable Python solutions.
|
|
14
|
-
|
|
15
|
-
## Included problems
|
|
16
|
-
|
|
17
|
-
- `problems/two_sum.py` - hash map lookup for complement matching
|
|
18
|
-
- `problems/best_time_to_buy_and_sell_stock.py` - one-pass minimum tracking
|
|
19
|
-
- `problems/product_of_array_except_self.py` - prefix and suffix products
|
|
20
|
-
|
|
21
|
-
## How to use
|
|
22
|
-
|
|
23
|
-
Read the matching problem file first, then explain:
|
|
24
|
-
- the core pattern
|
|
25
|
-
- time and space complexity
|
|
26
|
-
- one common mistake
|
|
27
|
-
|
|
28
|
-
Run examples locally if needed:
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
python3 problems/two_sum.py
|
|
32
|
-
python3 problems/best_time_to_buy_and_sell_stock.py
|
|
33
|
-
python3 problems/product_of_array_except_self.py
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Answering style
|
|
37
|
-
|
|
38
|
-
- Start with the pattern before the code
|
|
39
|
-
- Prefer the runnable Python examples in `problems/`
|
|
40
|
-
- If the user asks for another language, translate from the Python reference solution
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def max_profit(prices: list[int]) -> int:
|
|
5
|
-
min_price = float("inf")
|
|
6
|
-
best = 0
|
|
7
|
-
|
|
8
|
-
for price in prices:
|
|
9
|
-
if price < min_price:
|
|
10
|
-
min_price = price
|
|
11
|
-
continue
|
|
12
|
-
best = max(best, price - min_price)
|
|
13
|
-
|
|
14
|
-
return best
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if __name__ == "__main__":
|
|
18
|
-
sample_prices = [7, 1, 5, 3, 6, 4]
|
|
19
|
-
print(max_profit(sample_prices))
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def product_except_self(nums: list[int]) -> list[int]:
|
|
5
|
-
result = [1] * len(nums)
|
|
6
|
-
|
|
7
|
-
prefix = 1
|
|
8
|
-
for index, value in enumerate(nums):
|
|
9
|
-
result[index] = prefix
|
|
10
|
-
prefix *= value
|
|
11
|
-
|
|
12
|
-
suffix = 1
|
|
13
|
-
for index in range(len(nums) - 1, -1, -1):
|
|
14
|
-
result[index] *= suffix
|
|
15
|
-
suffix *= nums[index]
|
|
16
|
-
|
|
17
|
-
return result
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if __name__ == "__main__":
|
|
21
|
-
sample_nums = [1, 2, 3, 4]
|
|
22
|
-
print(product_except_self(sample_nums))
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def two_sum(nums: list[int], target: int) -> list[int]:
|
|
5
|
-
seen: dict[int, int] = {}
|
|
6
|
-
|
|
7
|
-
for index, value in enumerate(nums):
|
|
8
|
-
complement = target - value
|
|
9
|
-
if complement in seen:
|
|
10
|
-
return [seen[complement], index]
|
|
11
|
-
seen[value] = index
|
|
12
|
-
|
|
13
|
-
return []
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if __name__ == "__main__":
|
|
17
|
-
sample_nums = [2, 7, 11, 15]
|
|
18
|
-
sample_target = 9
|
|
19
|
-
print(two_sum(sample_nums, sample_target))
|
package/skills/weather/SKILL.md
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: weather
|
|
3
|
-
description: Get current weather and forecasts (no API key required).
|
|
4
|
-
homepage: https://wttr.in/:help
|
|
5
|
-
metadata: {"clawdbot":{"emoji":"🌤️","requires":{"bins":["curl"]}}}
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Weather
|
|
9
|
-
|
|
10
|
-
Two free services, no API keys needed.
|
|
11
|
-
|
|
12
|
-
## wttr.in (primary)
|
|
13
|
-
|
|
14
|
-
Quick one-liner:
|
|
15
|
-
```bash
|
|
16
|
-
curl -s "wttr.in/London?format=3"
|
|
17
|
-
# Output: London: ⛅️ +8°C
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Compact format:
|
|
21
|
-
```bash
|
|
22
|
-
curl -s "wttr.in/London?format=%l:+%c+%t+%h+%w"
|
|
23
|
-
# Output: London: ⛅️ +8°C 71% ↙5km/h
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Full forecast:
|
|
27
|
-
```bash
|
|
28
|
-
curl -s "wttr.in/London?T"
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Format codes: `%c` condition · `%t` temp · `%h` humidity · `%w` wind · `%l` location · `%m` moon
|
|
32
|
-
|
|
33
|
-
Tips:
|
|
34
|
-
- URL-encode spaces: `wttr.in/New+York`
|
|
35
|
-
- Airport codes: `wttr.in/JFK`
|
|
36
|
-
- Units: `?m` (metric) `?u` (USCS)
|
|
37
|
-
- Today only: `?1` · Current only: `?0`
|
|
38
|
-
- PNG: `curl -s "wttr.in/Berlin.png" -o /tmp/weather.png`
|
|
39
|
-
|
|
40
|
-
## Open-Meteo (fallback, JSON)
|
|
41
|
-
|
|
42
|
-
Free, no key, good for programmatic use:
|
|
43
|
-
```bash
|
|
44
|
-
curl -s "https://api.open-meteo.com/v1/forecast?latitude=51.5&longitude=-0.12¤t_weather=true"
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Find coordinates for a city, then query. Returns JSON with temp, windspeed, weathercode.
|
|
48
|
-
|
|
49
|
-
Docs: https://open-meteo.com/en/docs
|