@saifymatteo/pi-extensions 0.1.0 → 0.1.2

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 (56) hide show
  1. package/README.md +38 -32
  2. package/package.json +6 -2
  3. package/packages/pi-bg-bash/README.md +4 -0
  4. package/packages/pi-bg-bash/package.json +1 -1
  5. package/packages/pi-claude-usage/README.md +5 -1
  6. package/packages/pi-claude-usage/package.json +1 -1
  7. package/packages/pi-claude-usage/src/index.ts +10 -1
  8. package/packages/pi-claude-usage/test/behavior.test.mjs +71 -0
  9. package/packages/pi-clipboard-image/README.md +4 -0
  10. package/packages/pi-clipboard-image/package.json +1 -1
  11. package/packages/pi-codeblock-borders/README.md +4 -0
  12. package/packages/pi-codeblock-borders/package.json +1 -1
  13. package/packages/pi-codegraph/README.md +4 -0
  14. package/packages/pi-codegraph/package.json +1 -1
  15. package/packages/pi-compact-tools/README.md +4 -0
  16. package/packages/pi-compact-tools/package.json +1 -1
  17. package/packages/pi-decisions/README.md +4 -0
  18. package/packages/pi-decisions/package.json +1 -1
  19. package/packages/pi-load-pi-dotenv/README.md +4 -0
  20. package/packages/pi-load-pi-dotenv/package.json +1 -1
  21. package/packages/pi-model-go/README.md +4 -0
  22. package/packages/pi-model-go/package.json +1 -1
  23. package/packages/pi-prestart-working/README.md +4 -0
  24. package/packages/pi-prestart-working/package.json +1 -1
  25. package/packages/pi-root-guard/README.md +4 -0
  26. package/packages/pi-root-guard/package.json +1 -1
  27. package/packages/pi-session-finder/README.md +4 -0
  28. package/packages/pi-session-finder/package.json +1 -1
  29. package/packages/pi-session-id/README.md +4 -0
  30. package/packages/pi-session-id/package.json +1 -1
  31. package/packages/pi-session-nav/README.md +4 -0
  32. package/packages/pi-session-nav/package.json +1 -1
  33. package/packages/pi-session-rename/README.md +4 -0
  34. package/packages/pi-session-rename/package.json +1 -1
  35. package/packages/pi-session-retention/README.md +4 -0
  36. package/packages/pi-session-retention/package.json +1 -1
  37. package/packages/pi-skill-usage-log/README.md +4 -0
  38. package/packages/pi-skill-usage-log/package.json +1 -1
  39. package/packages/pi-tool-usage-log/README.md +4 -0
  40. package/packages/pi-tool-usage-log/package.json +1 -1
  41. package/packages/pi-tts/README.md +4 -0
  42. package/packages/pi-tts/package.json +1 -1
  43. package/packages/pi-user-image-previews/README.md +4 -0
  44. package/packages/pi-user-image-previews/package.json +1 -1
  45. package/packages/pi-vision/README.md +5 -1
  46. package/packages/pi-vision/package.json +1 -1
  47. package/packages/pi-vision/skills/vision-tools/SKILL.md +37 -21
  48. package/packages/pi-vision/skills/vision-tools/references/long-screenshot-ocr.md +7 -7
  49. package/packages/pi-vision/skills/vision-tools/references/restore-graphic.md +7 -7
  50. package/packages/pi-vision/skills/vision-tools/references/restore-ui.md +4 -4
  51. package/packages/pi-vision/skills/vision-tools/scripts/long_screenshot_ocr.py +4 -4
  52. package/packages/pi-vision/test/skill-cli.test.mjs +71 -0
  53. package/packages/pi-wait/README.md +4 -0
  54. package/packages/pi-wait/package.json +1 -1
  55. package/packages/pi-zvec/README.md +4 -0
  56. package/packages/pi-zvec/package.json +1 -1
package/README.md CHANGED
@@ -2,8 +2,12 @@
2
2
 
3
3
  Reusable pieces of a personal Pi setup, packaged separately from the setup itself.
4
4
 
5
+ ![Demo: every extension in this collection loaded into one Pi session](demo.gif)
6
+
5
7
  **Work in progress.** Packages are licensed AGPL-3.0-only ([NOTICE.md](NOTICE.md)) and publish through GitHub Actions when tagged: each extension individually, plus the root as the all-in-one collection [`@saifymatteo/pi-extensions`](https://github.com/saifymatteo/pi-extensions). Git is local for now, with no GitHub repository yet.
6
8
 
9
+ Each package README opens with a screenshot of what it adds. The screenshots are real captures, not mockups: stock Pi 1.4.0 driven by a scripted demo model, in an isolated agent home. The catalogue below shows them as thumbnails; click one for the package README.
10
+
7
11
  ## Start here
8
12
 
9
13
  Use Node.js 24 or newer. From this repository:
@@ -27,6 +31,8 @@ Host-provided Pi packages (`@earendil-works/*`) are declared only as package pee
27
31
  - `pack:check`: asks npm to show each standalone package's and the root collection's prospective contents without publishing or creating archives.
28
32
  - `smoke`: loads each package through isolated host help/startup. Checks module/factory loading, not session events, interactive rendering, models, external CLIs, or backend compatibility. The host is located automatically (`PI_HOST`, `pi` on PATH, then `pi-bolt` for a Pi-Bolt build, then the local `~/.pi-bolt` install). Pi-Bolt is optional; set `PI_HOST` to another executable to compare hosts. A pass here does not prove compatibility with a different host build.
29
33
  - `context`: measures what each package adds to the first-turn context window. Current result: the whole collection costs about **1.7k tokens** on top of stock Pi, and 17 of 22 packages contribute nothing (they register hooks, commands, or UI patches only). Measured by capturing the actual request Pi sends to a local provider, against the pinned stock host, with machine state excluded. First-turn static cost only: runtime injections (tool results, hook-driven messages) are not measured. Not part of the `check` gate.
34
+ - `screenshots`: regenerates each `packages/<name>/screenshot.png` (`npm run screenshots -- pi-bg-bash` for one). Drives the pinned stock host in a pseudo-terminal against a scripted local model and fake service endpoints, in a throwaway `/tmp/demo` home, with a stubbed clipboard. macOS only for now (font path, clipboard stub). Needs `agent-browser` on PATH. One-time setup: `npm ci --prefix scripts/screenshots`, then make node-pty's helper executable (`chmod +x scripts/screenshots/node_modules/node-pty/prebuilds/*/spawn-helper`) if npm skipped its install script. Not part of the `check` gate.
35
+ - `demo`: re-records the root `demo.gif` (also the gallery preview via `pi.image`): one session with the whole root collection loaded, same harness and setup as `screenshots`. `DRY=1` prints each scene's screen without rendering. Not part of the `check` gate.
30
36
 
31
37
  ## Isolated sessions
32
38
 
@@ -50,46 +56,46 @@ Each directory under `packages/` is an independently loadable, independently pub
50
56
 
51
57
  ### Session and model navigation
52
58
 
53
- | Package | Purpose |
54
- | --- | --- |
55
- | [pi-session-id](packages/pi-session-id) | Copy the current session ID |
56
- | [pi-session-finder](packages/pi-session-finder) | Search saved sessions across projects; open or fork them |
57
- | [pi-session-nav](packages/pi-session-nav) | Jump between messages in the active context (compaction-aware) |
58
- | [pi-session-rename](packages/pi-session-rename) | Generate session titles from the first prompt using configured models |
59
- | [pi-session-retention](packages/pi-session-retention) | Inspect and manage stored sessions; review deletion behavior before use |
60
- | [pi-model-go](packages/pi-model-go) | `/model-go`: cost-aware model picker with Go-plan pricing and intelligence scores |
59
+ | Package | Purpose | Preview |
60
+ | --- | --- | --- |
61
+ | [pi-session-id](packages/pi-session-id) | Copy the current session ID | <a href="packages/pi-session-id"><img src="packages/pi-session-id/screenshot.png" width="280" alt="pi-session-id screenshot"></a> |
62
+ | [pi-session-finder](packages/pi-session-finder) | Search saved sessions across projects; open or fork them | <a href="packages/pi-session-finder"><img src="packages/pi-session-finder/screenshot.png" width="280" alt="pi-session-finder screenshot"></a> |
63
+ | [pi-session-nav](packages/pi-session-nav) | Jump between messages in the active context (compaction-aware) | <a href="packages/pi-session-nav"><img src="packages/pi-session-nav/screenshot.png" width="280" alt="pi-session-nav screenshot"></a> |
64
+ | [pi-session-rename](packages/pi-session-rename) | Generate session titles from the first prompt using configured models | <a href="packages/pi-session-rename"><img src="packages/pi-session-rename/screenshot.png" width="280" alt="pi-session-rename screenshot"></a> |
65
+ | [pi-session-retention](packages/pi-session-retention) | Inspect and manage stored sessions; review deletion behavior before use | <a href="packages/pi-session-retention"><img src="packages/pi-session-retention/screenshot.png" width="280" alt="pi-session-retention screenshot"></a> |
66
+ | [pi-model-go](packages/pi-model-go) | `/model-go`: cost-aware model picker with Go-plan pricing and intelligence scores | <a href="packages/pi-model-go"><img src="packages/pi-model-go/screenshot.png" width="280" alt="pi-model-go screenshot"></a> |
61
67
 
62
68
  ### Transcript and terminal UI
63
69
 
64
- | Package | Purpose |
65
- | --- | --- |
66
- | [pi-compact-tools](packages/pi-compact-tools) | Group consecutive tool calls into one collapsible card |
67
- | [pi-clipboard-image](packages/pi-clipboard-image) | Attach clipboard images or copied image files (Alt+V) |
68
- | [pi-codeblock-borders](packages/pi-codeblock-borders) | Render code fences as rules with titles, line numbers, and highlights |
69
- | [pi-user-image-previews](packages/pi-user-image-previews) | Collapsible inline previews for newly submitted attachments |
70
- | [pi-claude-usage](packages/pi-claude-usage) | Show Claude 5h/7d subscription usage in the footer |
71
- | [pi-prestart-working](packages/pi-prestart-working) | Labelled "Working" spinner while `before_agent_start` hooks run |
70
+ | Package | Purpose | Preview |
71
+ | --- | --- | --- |
72
+ | [pi-compact-tools](packages/pi-compact-tools) | Group consecutive tool calls into one collapsible card | <a href="packages/pi-compact-tools"><img src="packages/pi-compact-tools/screenshot.png" width="280" alt="pi-compact-tools screenshot"></a> |
73
+ | [pi-clipboard-image](packages/pi-clipboard-image) | Attach clipboard images or copied image files (Alt+V) | <a href="packages/pi-clipboard-image"><img src="packages/pi-clipboard-image/screenshot.png" width="280" alt="pi-clipboard-image screenshot"></a> |
74
+ | [pi-codeblock-borders](packages/pi-codeblock-borders) | Render code fences as rules with titles, line numbers, and highlights | <a href="packages/pi-codeblock-borders"><img src="packages/pi-codeblock-borders/screenshot.png" width="280" alt="pi-codeblock-borders screenshot"></a> |
75
+ | [pi-user-image-previews](packages/pi-user-image-previews) | Collapsible inline previews for newly submitted attachments | <a href="packages/pi-user-image-previews"><img src="packages/pi-user-image-previews/screenshot.png" width="280" alt="pi-user-image-previews screenshot"></a> |
76
+ | [pi-claude-usage](packages/pi-claude-usage) | Show Claude 5h/7d subscription usage in the footer | <a href="packages/pi-claude-usage"><img src="packages/pi-claude-usage/screenshot.png" width="280" alt="pi-claude-usage screenshot"></a> |
77
+ | [pi-prestart-working](packages/pi-prestart-working) | Labelled "Working" spinner while `before_agent_start` hooks run | <a href="packages/pi-prestart-working"><img src="packages/pi-prestart-working/screenshot.png" width="280" alt="pi-prestart-working screenshot"></a> |
72
78
 
73
79
  ### Tools and service adapters
74
80
 
75
- | Package | Purpose |
76
- | --- | --- |
77
- | [pi-bg-bash](packages/pi-bg-bash) | Add background execution to the built-in bash/powershell tools |
78
- | [pi-wait](packages/pi-wait) | Join background jobs (shell:/agent:) with lease-based delivery |
79
- | [pi-codegraph](packages/pi-codegraph) | CodeGraph CLI adapter: explore tool plus index auto-init/sync |
80
- | [pi-zvec](packages/pi-zvec) | Workspace search via the zg CLI: exact, lexical, semantic, hybrid |
81
- | [pi-decisions](packages/pi-decisions) | Agent-facing classify tool backed by configured Decisions endpoints |
82
- | [pi-tts](packages/pi-tts) | Generate WAV files through an OpenAI-compatible speech endpoint |
83
- | [pi-vision](packages/pi-vision) | Describe images for text-only models via a configured vision chain |
81
+ | Package | Purpose | Preview |
82
+ | --- | --- | --- |
83
+ | [pi-bg-bash](packages/pi-bg-bash) | Add background execution to the built-in bash/powershell tools | <a href="packages/pi-bg-bash"><img src="packages/pi-bg-bash/screenshot.png" width="280" alt="pi-bg-bash screenshot"></a> |
84
+ | [pi-wait](packages/pi-wait) | Join background jobs (shell:/agent:) with lease-based delivery | <a href="packages/pi-wait"><img src="packages/pi-wait/screenshot.png" width="280" alt="pi-wait screenshot"></a> |
85
+ | [pi-codegraph](packages/pi-codegraph) | CodeGraph CLI adapter: explore tool plus index auto-init/sync | <a href="packages/pi-codegraph"><img src="packages/pi-codegraph/screenshot.png" width="280" alt="pi-codegraph screenshot"></a> |
86
+ | [pi-zvec](packages/pi-zvec) | Workspace search via the zg CLI: exact, lexical, semantic, hybrid | <a href="packages/pi-zvec"><img src="packages/pi-zvec/screenshot.png" width="280" alt="pi-zvec screenshot"></a> |
87
+ | [pi-decisions](packages/pi-decisions) | Agent-facing classify tool backed by configured Decisions endpoints | <a href="packages/pi-decisions"><img src="packages/pi-decisions/screenshot.png" width="280" alt="pi-decisions screenshot"></a> |
88
+ | [pi-tts](packages/pi-tts) | Generate WAV files through an OpenAI-compatible speech endpoint | <a href="packages/pi-tts"><img src="packages/pi-tts/screenshot.png" width="280" alt="pi-tts screenshot"></a> |
89
+ | [pi-vision](packages/pi-vision) | Describe images for text-only models via a configured vision chain | <a href="packages/pi-vision"><img src="packages/pi-vision/screenshot.png" width="280" alt="pi-vision screenshot"></a> |
84
90
 
85
91
  ### Environment and local observability
86
92
 
87
- | Package | Purpose |
88
- | --- | --- |
89
- | [pi-load-pi-dotenv](packages/pi-load-pi-dotenv) | Load the agent-local .env into the environment for tool subprocesses |
90
- | [pi-root-guard](packages/pi-root-guard) | Block agent writes into the Pi and Claude config roots |
91
- | [pi-skill-usage-log](packages/pi-skill-usage-log) | Log which skills were actually used; `/skill-usage` summary |
92
- | [pi-tool-usage-log](packages/pi-tool-usage-log) | Passive log of tool executions (names and status, never contents) |
93
+ | Package | Purpose | Preview |
94
+ | --- | --- | --- |
95
+ | [pi-load-pi-dotenv](packages/pi-load-pi-dotenv) | Load the agent-local .env into the environment for tool subprocesses | <a href="packages/pi-load-pi-dotenv"><img src="packages/pi-load-pi-dotenv/screenshot.png" width="280" alt="pi-load-pi-dotenv screenshot"></a> |
96
+ | [pi-root-guard](packages/pi-root-guard) | Block agent writes into the Pi and Claude config roots | <a href="packages/pi-root-guard"><img src="packages/pi-root-guard/screenshot.png" width="280" alt="pi-root-guard screenshot"></a> |
97
+ | [pi-skill-usage-log](packages/pi-skill-usage-log) | Log which skills were actually used; `/skill-usage` summary | <a href="packages/pi-skill-usage-log"><img src="packages/pi-skill-usage-log/screenshot.png" width="280" alt="pi-skill-usage-log screenshot"></a> |
98
+ | [pi-tool-usage-log](packages/pi-tool-usage-log) | Passive log of tool executions (names and status, never contents) | <a href="packages/pi-tool-usage-log"><img src="packages/pi-tool-usage-log/screenshot.png" width="280" alt="pi-tool-usage-log screenshot"></a> |
93
99
 
94
100
  ## Individual and collection loading
95
101
 
@@ -133,7 +139,7 @@ packages/<extension>/
133
139
  src/index.ts # existing extension implementation
134
140
  src/lib/ # only helpers needed by this package, where applicable
135
141
  examples/ # sanitized, inert examples where applicable
136
- scripts/ # boundary checks, isolated sessions, host smoke
142
+ scripts/ # boundary checks, isolated sessions, host smoke, screenshots
137
143
  test/ # test-only TS loader and collection behavior checks
138
144
  inventory.json # inclusion/exclusion map
139
145
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-extensions",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-only",
6
6
  "description": "Independently installable Pi extensions",
@@ -14,7 +14,9 @@
14
14
  "pack:check": "node scripts/check-pack.mjs",
15
15
  "dev": "node scripts/dev.mjs",
16
16
  "smoke": "node scripts/smoke.mjs",
17
- "context": "node scripts/context-audit.mjs"
17
+ "context": "node scripts/context-audit.mjs",
18
+ "screenshots": "node scripts/screenshots/shots.mjs",
19
+ "demo": "node scripts/screenshots/demo.mjs"
18
20
  },
19
21
  "devDependencies": {
20
22
  "@earendil-works/pi-coding-agent": "1.4.0",
@@ -27,6 +29,7 @@
27
29
  "node": ">=24"
28
30
  },
29
31
  "pi": {
32
+ "image": "https://raw.githubusercontent.com/saifymatteo/pi-extensions/main/demo.gif",
30
33
  "extensions": [
31
34
  "./packages/pi-load-pi-dotenv/src/index.ts",
32
35
  "./packages/pi-bg-bash/src/index.ts",
@@ -68,6 +71,7 @@
68
71
  ],
69
72
  "files": [
70
73
  "packages",
74
+ "!packages/*/screenshot.png",
71
75
  "inventory.json",
72
76
  "README.md",
73
77
  "NOTICE.md",
@@ -1,5 +1,9 @@
1
1
  # @saifymatteo/pi-bg-bash
2
2
 
3
+ ![The agent runs npm test in the background, keeps working, and the result arrives as its own row](screenshot.png)
4
+
5
+ *The agent starts `npm test` with `background: true` and keeps working. When the job exits, its result arrives as a separate row. Ctrl+O expands that row to the output tail.*
6
+
3
7
  ## Purpose and registrations
4
8
 
5
9
  Overrides `bash` and, when provided by the host, `powershell`, adding `background` and `job`. Registers `/bg`, result/call renderers, timeout injection, sleep-wait guard, session/settlement hooks, and shell provider discovery.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-bg-bash",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -1,5 +1,9 @@
1
1
  # Claude subscription usage
2
2
 
3
+ ![Footer showing Claude 5-hour and 7-day usage with reset timers](screenshot.png)
4
+
5
+ *An extra footer line shows Claude 5-hour and 7-day usage and when each window resets. Its colour changes at 75% and 90%.*
6
+
3
7
  Displays Claude subscription rate-limit windows from provider message diagnostics.
4
8
 
5
9
  ## Try locally
@@ -14,7 +18,7 @@ This uses your normal personal resources unless you isolate the agent home. Foll
14
18
 
15
19
  ## Configuration and requirements
16
20
 
17
- Uses rate-limit diagnostics from the external pi-claude-code provider; it does not poll Anthropic. Caches the most recent windows in <agent-dir>/state/claude-usage.json. No additional configuration.
21
+ Uses rate-limit diagnostics from the external pi-claude-code provider; it does not poll Anthropic. Caches the most recent windows in <agent-dir>/state/claude-usage.json. The footer is shown only while the selected model uses the `claude-code` provider, not for Claude models accessed through other providers. Switching providers hides or restores it immediately. No additional configuration.
18
22
 
19
23
  ## Compatibility and safety
20
24
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-claude-usage",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Displays Claude subscription rate-limit windows from provider message diagnostics.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -77,7 +77,11 @@ export default function (pi: ExtensionAPI) {
77
77
  let tick: ReturnType<typeof setInterval> | undefined;
78
78
 
79
79
  function render() {
80
- if (!ctx || !usage) return;
80
+ if (!ctx) return;
81
+ if (ctx.model?.provider !== "claude-code" || !usage) {
82
+ ctx.ui.setStatus(STATUS_KEY, undefined);
83
+ return;
84
+ }
81
85
  const now = Date.now();
82
86
  const text = formatFooter(usage, now);
83
87
  const p = peak(usage, now);
@@ -115,6 +119,11 @@ export default function (pi: ExtensionAPI) {
115
119
  }
116
120
  });
117
121
 
122
+ pi.on("model_select", async (_event, sessionCtx) => {
123
+ ctx = sessionCtx;
124
+ render();
125
+ });
126
+
118
127
  pi.on("session_shutdown", async () => {
119
128
  if (tick) clearInterval(tick);
120
129
  tick = undefined;
@@ -0,0 +1,71 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { loadExtension } from '../../../test/load-extension.mjs';
4
+
5
+ const cached = { fiveHour: { pct: 42, resetsAt: 4_000_000_000_000 } };
6
+
7
+ function harness(t, initialModel, cache = cached) {
8
+ const hooks = {}, statuses = new Map(), timers = new Set(), writes = [];
9
+ const extension = loadExtension(new URL('../src/index.ts', import.meta.url), {
10
+ 'node:fs': {
11
+ readFileSync: () => {
12
+ if (!cache) throw new Error('no cache');
13
+ return JSON.stringify(cache);
14
+ },
15
+ mkdirSync: () => {},
16
+ writeFileSync: (_path, contents) => writes.push(JSON.parse(contents)),
17
+ },
18
+ }, {
19
+ setInterval: fn => { timers.add(fn); return fn; },
20
+ clearInterval: fn => timers.delete(fn),
21
+ });
22
+ extension.default({ on: (name, fn) => { hooks[name] = fn; } });
23
+ const ui = {
24
+ setStatus: (key, value) => { if (value === undefined) statuses.delete(key); else statuses.set(key, value); },
25
+ theme: { fg: (_color, value) => value },
26
+ };
27
+ const context = model => ({ model, ui });
28
+ const ctx = context(initialModel);
29
+ t.after(() => hooks.session_shutdown());
30
+ return {
31
+ hooks, ctx, statuses, timers, writes,
32
+ select: model => hooks.model_select({ model }, context(model)),
33
+ };
34
+ }
35
+
36
+ for (const model of [undefined, { provider: 'openai', id: 'gpt-5' }, { provider: 'anthropic', id: 'claude-sonnet-4-6' }]) {
37
+ test(`cached Claude usage stays hidden for ${model?.provider ?? 'no model'}`, async t => {
38
+ const h = harness(t, model);
39
+ await h.hooks.session_start({}, h.ctx);
40
+ assert.equal(h.statuses.has('claude-usage'), false);
41
+ });
42
+ }
43
+
44
+ test('model changes hide and restore cached usage immediately, including timer refreshes', async t => {
45
+ const h = harness(t, { provider: 'claude-code', id: 'sonnet' });
46
+ await h.hooks.session_start({}, h.ctx);
47
+ assert.match(h.statuses.get('claude-usage'), /^Claude 5h 42%/);
48
+
49
+ await h.select({ provider: 'openai', id: 'gpt-5' });
50
+ assert.equal(h.statuses.has('claude-usage'), false);
51
+ for (const refresh of h.timers) refresh();
52
+ assert.equal(h.statuses.has('claude-usage'), false);
53
+
54
+ await h.select({ provider: 'claude-code', id: 'sonnet' });
55
+ assert.match(h.statuses.get('claude-usage'), /^Claude 5h 42%/);
56
+ });
57
+
58
+ test('diagnostics update the cache without exposing usage on another provider', async t => {
59
+ const h = harness(t, { provider: 'openai', id: 'gpt-5' }, null);
60
+ await h.hooks.session_start({}, h.ctx);
61
+ await h.hooks.message_end({ message: {
62
+ role: 'assistant',
63
+ diagnostics: [{ type: 'claude-code.rate-limit', details: {
64
+ unifiedWindows: { five_hour: { utilization: 0.75, resetsAt: 4_000_000_000 } },
65
+ } }],
66
+ } }, h.ctx);
67
+ assert.equal(h.statuses.has('claude-usage'), false);
68
+ assert.equal(h.writes.at(-1).fiveHour.pct, 75);
69
+ await h.select({ provider: 'claude-code', id: 'sonnet' });
70
+ assert.match(h.statuses.get('claude-usage'), /^Claude 5h 75%/);
71
+ });
@@ -1,5 +1,9 @@
1
1
  # Clipboard image attachment
2
2
 
3
+ ![Alt+V pastes a clipboard image into the draft as [Image #1]](screenshot.png)
4
+
5
+ *Alt+V adds the clipboard image to the draft as `[Image #1]`. Sending the message sends the image to the model with it.*
6
+
3
7
  Attaches clipboard images to the draft, including copied image files on Windows.
4
8
 
5
9
  ## Try locally
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-clipboard-image",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Attaches clipboard images to the draft, including copied image files on Windows.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -1,5 +1,9 @@
1
1
  # pi-codeblock-borders
2
2
 
3
+ ![The same reply rendered by stock pi and with codeblock borders](screenshot.png)
4
+
5
+ *The same reply, rendered by stock pi and then with this extension. Code fences become full-width rules, and `title=`, `:line-numbers` and `{2}` line focus are rendered.*
6
+
3
7
  Render markdown code fences as full-width rules spanning the content area, and upgrade the fence info string with language decoration, titles, and line numbers that Pi's built-in renderer does not support.
4
8
 
5
9
  ## Running locally
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-codeblock-borders",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "description": "Decorated code fences",
6
6
  "engines": {
@@ -1,5 +1,9 @@
1
1
  # @saifymatteo/pi-codegraph
2
2
 
3
+ ![codegraph_explore returning source and blast radius, with CodeGraph sync status in the footer](screenshot.png)
4
+
5
+ *The agent calls `codegraph_explore` and gets back the relevant source with its blast radius (expanded here with Ctrl+O). The footer shows the index status, which is synced after every turn.*
6
+
3
7
  ## Purpose and registrations
4
8
 
5
9
  Registers `codegraph_explore`, `/codegraph-init`, `/codegraph-sync`, `/codegraph-status`, `/codegraph-unlock`, session startup/shutdown and settle-sync hooks, and footer status.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-codegraph",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -1,5 +1,9 @@
1
1
  # Compact tool cards
2
2
 
3
+ ![Four tool calls folded into one card, then expanded with Ctrl+O](screenshot.png)
4
+
5
+ *Four tool calls in a row fold into one summary card. Ctrl+O expands it, and each call keeps its own status and output.*
6
+
3
7
  Groups consecutive tool calls into compact expandable transcript cards.
4
8
 
5
9
  ## Try locally
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-compact-tools",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Groups consecutive tool calls into compact expandable transcript cards.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -1,5 +1,9 @@
1
1
  # @saifymatteo/pi-decisions
2
2
 
3
+ ![The classify tool returning a structured triage answer with probabilities](screenshot.png)
4
+
5
+ *The agent calls `classify` against a configured Decisions endpoint and gets structured answers with per-label probabilities.*
6
+
3
7
  ## Purpose and registrations
4
8
 
5
9
  When configured, registers classifier providers and the `classify` tool (choice/bool/score; JSON state and optional PNG/JPEG/WebP images). No slash commands. Requires Pi's classifier model registry APIs (`findOfType`, `classify`, provider `classifiers`); older hosts may not implement them.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-decisions",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -1,5 +1,9 @@
1
1
  # Pi dotenv loader
2
2
 
3
+ ![Environment variables from ~/.pi/agent/.env reaching a bash tool call](screenshot.png)
4
+
5
+ *With this extension, variables from `~/.pi/agent/.env`, including `${VAR}` expansion, reach the tools the agent runs. Stock pi passes nothing through.*
6
+
3
7
  Loads environment variables from the Pi agent directory without replacing existing environment values.
4
8
 
5
9
  ## Try locally
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-load-pi-dotenv",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Loads environment variables from the Pi agent directory without replacing existing environment values.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -1,5 +1,9 @@
1
1
  # pi-model-go
2
2
 
3
+ ![/model-go picker sorted by effective price with credit multipliers and intelligence scores](screenshot.png)
4
+
5
+ *`/model-go` lists OpenCode Go models sorted by effective price (input ÷ plan credit), with context size, intelligence score and credit multiplier.*
6
+
3
7
  Cost-aware model picker showing registry prices/context, OpenCode Go credit multipliers and stencil.so intelligence scores. Source describes it as a port of rizal72/oc-models-pro.
4
8
 
5
9
  ## Running locally
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-model-go",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -1,5 +1,9 @@
1
1
  # @saifymatteo/pi-prestart-working
2
2
 
3
+ ![Spinner labelled with the extension whose before_agent_start hook is running](screenshot.png)
4
+
5
+ *A slow `before_agent_start` hook normally leaves the UI silent after you press Enter. With this extension, the spinner names the extension that is running.*
6
+
3
7
  ## Purpose and registrations
4
8
 
5
9
  Shows Pi's native "Working" spinner, labelled with the extension currently blocking (e.g. `⠙ Loading hindsight-coding-agents`), while `before_agent_start` hooks run. Pi only raises its working indicator at `turn_start`, so slow handlers otherwise leave the UI silent. No tools, commands, or configuration.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-prestart-working",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Show a labelled Working spinner while before_agent_start hooks run.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -1,5 +1,9 @@
1
1
  # Agent configuration root guard
2
2
 
3
+ ![A write into ~/.pi/agent blocked by the guardrail](screenshot.png)
4
+
5
+ *Stock pi lets the agent rewrite its own config. With the guard, writes into the Pi and Claude config roots are blocked, and the agent is told why.*
6
+
3
7
  Blocks selected write/edit and simple bash writes into Pi and Claude configuration roots.
4
8
 
5
9
  ## Try locally
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-root-guard",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Blocks selected write/edit and simple bash writes into Pi and Claude configuration roots.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -1,5 +1,9 @@
1
1
  # pi-session-finder
2
2
 
3
+ ![/session-global listing sessions from every project, with forks nested](screenshot.png)
4
+
5
+ *`/session-global` searches saved sessions across all projects. They are grouped by folder, with forks nested under their parent.*
6
+
3
7
  Search saved sessions across projects, display their fork families, and open or fork a conversation into the current project.
4
8
 
5
9
  ## Running locally
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-session-finder",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -1,5 +1,9 @@
1
1
  # pi-session-id
2
2
 
3
+ ![/session-id confirming the copied session ID](screenshot.png)
4
+
5
+ *`/session-id` copies the current session ID to the clipboard.*
6
+
3
7
  Copy the current session ID to the system clipboard.
4
8
 
5
9
  ## Running locally
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-session-id",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -1,5 +1,9 @@
1
1
  # pi-session-nav
2
2
 
3
+ ![Alt+O message navigator over the current conversation](screenshot.png)
4
+
5
+ *Alt+O lists the conversation's messages. Pick one and press Enter to jump to it.*
6
+
3
7
  Navigate visible user and assistant messages on the active conversation branch without adding transcript entries.
4
8
 
5
9
  ## Running locally
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-session-nav",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -1,5 +1,9 @@
1
1
  # pi-session-rename
2
2
 
3
+ ![Generated session title in the footer and terminal title](screenshot.png)
4
+
5
+ *After the first prompt, the session gets a generated title. It appears in the footer and in the terminal tab title.*
6
+
3
7
  Automatically title main interactive sessions from the first submitted prompt and optionally mirror the name to a Herdr tab. Manual names take precedence; failed generation retries on later prompts.
4
8
 
5
9
  ## Running locally
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-session-rename",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -1,5 +1,9 @@
1
1
  # pi-session-retention
2
2
 
3
+ ![/session-cleanup preview listing inactive session families](screenshot.png)
4
+
5
+ *`/session-cleanup preview` shows which inactive session families would move to Trash. It changes nothing.*
6
+
3
7
  From the monorepo root:
4
8
 
5
9
  ```sh
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-session-retention",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -1,5 +1,9 @@
1
1
  # pi-skill-usage-log
2
2
 
3
+ ![/skill-usage report of loaded and used skills across projects](screenshot.png)
4
+
5
+ *`/skill-usage` shows which installed skills were loaded and which were actually used, across all projects.*
6
+
3
7
  Passively record skill loads and subsequent tool activity, then show cross-project usage without adding the report to conversation context.
4
8
 
5
9
  ## Running locally
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-skill-usage-log",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -1,5 +1,9 @@
1
1
  # pi-tool-usage-log
2
2
 
3
+ ![/tool-usage report of calls, errors, nesting and duration per tool](screenshot.png)
4
+
5
+ *`/tool-usage` totals every tool call: count, sessions, errors, nested calls, average duration and last use.*
6
+
3
7
  Passively record direct and nested tool executions and show aggregate usage, errors, duration, selected model and thinking level.
4
8
 
5
9
  ## Running locally
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-tool-usage-log",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -1,5 +1,9 @@
1
1
  # @saifymatteo/pi-tts
2
2
 
3
+ ![The text_to_speech tool saving a WAV file into the project](screenshot.png)
4
+
5
+ *The agent calls `text_to_speech`, and the WAV file from the configured speech endpoint is saved into the project.*
6
+
3
7
  ## Purpose and registrations
4
8
 
5
9
  Registers `text_to_speech` when the registry has a `type: "tts"` model; no slash commands or playback. Sends narration to a service and saves a new WAV, never overwriting an existing output. Uses standard fetch/streams and Pi tool registration; requires filesystem hard-link support for atomic publication.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-tts",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -1,5 +1,9 @@
1
1
  # Inline user image previews
2
2
 
3
+ ![Attached image shown as a collapsible preview row, expanded inline with Ctrl+O](screenshot.png)
4
+
5
+ *An attached image gets a collapsible preview row in the transcript. Ctrl+O opens it inline. Stock pi shows nothing.*
6
+
3
7
  Adds collapsible UI-only previews for newly submitted image attachments.
4
8
 
5
9
  ## Try locally
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-user-image-previews",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Adds collapsible UI-only previews for newly submitted image attachments.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -1,5 +1,9 @@
1
1
  # @saifymatteo/pi-vision
2
2
 
3
+ ![A text-only model answering about an image via a vision model description](screenshot.png)
4
+
5
+ *The main model here is text-only. Stock pi drops the image. With pi-vision, a configured vision model describes the image and the main model works from that description.*
6
+
3
7
  ## Purpose and registrations
4
8
 
5
9
  Registers a `context` hook, not a tool or slash command. Preserves native images for image-capable session models by default. For text-only/unknown models it replaces image blocks with model-generated descriptions. Optional `describe` / `describe-old` policies self-describe all/older images using the current model on OpenAI Chat Completions connections.
@@ -12,7 +16,7 @@ Resolution also consults agent `models.json`, `models-store.json`, `auth.json`,
12
16
 
13
17
  ## Bundled skill
14
18
 
15
- The package bundles the `vision-tools` skill (`skills/vision-tools`): `bin/` launcher CLIs (`crop`, `detect`, `glance`, `ground`, `trace`, `vision-doctor`), Python tools under `scripts/`, reference guides, and `SKILL.md`. The extension's guidance messages point the agent at `vision-doctor` and the skill. Requires Python 3 with Pillow (PIL); the CLIs are Python scripts, not compiled binaries. Based on [Anionex/agent-vision-toolkit](https://github.com/Anionex/agent-vision-toolkit) (MIT), modified in this repository — see [NOTICE.md](NOTICE.md).
19
+ The package bundles the `vision-tools` skill (`skills/vision-tools`): `bin/` launcher CLIs (`crop`, `detect`, `glance`, `ground`, `trace`, `vision-doctor`), Python tools under `scripts/`, reference guides, and `SKILL.md`. Invoke the launchers with Python and their absolute skill-relative paths, for example `python3 "<skill-directory>/bin/crop" image.png --region 0,0,100,100 -o detail.png`. Follow the interpreter-selection guidance in `SKILL.md` if Windows exposes Python as `python` or `py -3`. No PATH registration, executable bit, or agent-home shim is required. The extension does not modify PATH. Automated long-screenshot OCR prefers the bundled `bin/glance` with the current Python interpreter, falling back to PATH only if that script is missing. The extension's guidance messages point the agent at `vision-doctor` and the skill. Requires Python 3 with Pillow (PIL); the CLIs are Python scripts, not compiled binaries. Based on [Anionex/agent-vision-toolkit](https://github.com/Anionex/agent-vision-toolkit) (MIT), modified in this repository — see [NOTICE.md](NOTICE.md).
16
20
 
17
21
  ## Host limitations and security
18
22
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-vision",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -33,20 +33,35 @@ For browser or accessible GUI work, prefer DOM/accessibility information for
33
33
  controls and state. Use screenshot coordinates when that information is
34
34
  unavailable or the task specifically concerns appearance.
35
35
 
36
+ ## Run the packaged scripts
37
+
38
+ 1. Resolve `<skill-dir>` to the absolute directory containing this `SKILL.md`,
39
+ using the skill location supplied by the host. Replace that placeholder in
40
+ commands below; keep script paths quoted and keep the project's working
41
+ directory.
42
+ 2. Select a Python 3 interpreter: try `python3 --version`; on Windows, use
43
+ `python --version` or `py -3 --version` if needed. Substitute that command
44
+ for `python3` in the examples.
45
+ 3. Invoke `bin/<tool>` and `scripts/<name>.py` through that interpreter. The
46
+ extensionless files in `bin/` are Python scripts, not installed binaries.
47
+ This works without PATH registration, executable permissions, or agent-home
48
+ symlinks. Short tool names in prose refer to these same script paths.
49
+
50
+ Use `--help` on the selected script for additional options.
51
+
36
52
  ## Local tools: use when their output is needed
37
53
 
38
- Resolve `scripts/` paths relative to this skill directory. Use `--help` for
39
- additional options. These tools do not use the vision API; HTML rendering can
40
- load resources referenced by the page.
54
+ These tools do not use the vision API; HTML rendering can load resources
55
+ referenced by the page.
41
56
 
42
57
  | Need | Command |
43
58
  | --- | --- |
44
- | Reusable crop or close-up | `crop shot.png --region X1,Y1,X2,Y2 -o detail.png` |
45
- | Locate pixel differences | `python3 scripts/pixel_diff.py before.png after.png` |
46
- | Measure a region's palette | `python3 scripts/dominant_colors.py shot.png --region X1,Y1,X2,Y2` |
47
- | Vectorize a flat graphic | `trace icon.png -o icon.svg` |
48
- | Extract a transparent foreground | `python3 scripts/extract_fg.py shot.png --region X1,Y1,X2,Y2 -o icon.png` |
49
- | Render an HTML file | `python3 scripts/html_shot.py page.html --width 1440 --height 900 -o page.png` |
59
+ | Reusable crop or close-up | `python3 "<skill-dir>/bin/crop" shot.png --region X1,Y1,X2,Y2 -o detail.png` |
60
+ | Locate pixel differences | `python3 "<skill-dir>/scripts/pixel_diff.py" before.png after.png` |
61
+ | Measure a region's palette | `python3 "<skill-dir>/scripts/dominant_colors.py" shot.png --region X1,Y1,X2,Y2` |
62
+ | Vectorize a flat graphic | `python3 "<skill-dir>/bin/trace" icon.png -o icon.svg` |
63
+ | Extract a transparent foreground | `python3 "<skill-dir>/scripts/extract_fg.py" shot.png --region X1,Y1,X2,Y2 -o icon.png` |
64
+ | Render an HTML file | `python3 "<skill-dir>/scripts/html_shot.py" page.html --width 1440 --height 900 -o page.png` |
50
65
 
51
66
  - `crop --scale 4` enlarges a crop; it adds no missing detail. Coordinates
52
67
  measured on that output use the enlarged grid, not the original image.
@@ -84,11 +99,11 @@ separately configured vision chain, not to the main agent model automatically.
84
99
  Structured output does not guarantee better recognition or localization.
85
100
 
86
101
  ```bash
87
- glance shot.png -q "Read the small status label."
88
- glance shot.png --region X1,Y1,X2,Y2 --ocr
89
- glance before.png after.png -q "What changed semantically?"
90
- ground shot.png "the send button"
91
- detect shot.png "buttons"
102
+ python3 "<skill-dir>/bin/glance" shot.png -q "Read the small status label."
103
+ python3 "<skill-dir>/bin/glance" shot.png --region X1,Y1,X2,Y2 --ocr
104
+ python3 "<skill-dir>/bin/glance" before.png after.png -q "What changed semantically?"
105
+ python3 "<skill-dir>/bin/ground" shot.png "the send button"
106
+ python3 "<skill-dir>/bin/detect" shot.png "buttons"
92
107
  ```
93
108
 
94
109
  `ground` and `detect` return estimated boxes in original-image pixels, including
@@ -98,9 +113,9 @@ For multi-image questions, pass the images in one call so the model sees them
98
113
  together. Native side-by-side inspection is also sufficient for semantic
99
114
  comparison; use pixel diff when numerical differences matter.
100
115
 
101
- The long-screenshot OCR script still uses `glance` for automated transcription.
102
- Its `--split-only` mode makes chunks without API requests for native inspection.
103
- Keep the `glance` client/config available if using the automated path.
116
+ The long-screenshot OCR script invokes this skill's `bin/glance` with its
117
+ current Python interpreter, falling back to PATH only if the bundled script
118
+ is missing. Its `--split-only` mode makes chunks without API requests.
104
119
 
105
120
  ### Configuration and failures
106
121
 
@@ -112,10 +127,11 @@ conversion on supported connections. Text-only or unknown-capability models
112
127
  still use the configured compatibility bridge. This policy does not route CLI
113
128
  calls through the main model.
114
129
 
115
- On a model-backed CLI configuration/API error, run `vision-doctor`. Use
116
- `vision-doctor --smoke` only when a real diagnostic request is needed; it sends
117
- a test image. Relay failures honestly. Continue with native inspection when
118
- available; report if the scripted output cannot be produced. Keep provider
130
+ On a model-backed CLI configuration/API error, run
131
+ `python3 "<skill-dir>/bin/vision-doctor"`. Add `--smoke` only when a real
132
+ diagnostic request is needed; it sends a test image. Relay failures honestly.
133
+ Continue with native inspection when available; report if the scripted output
134
+ cannot be produced. Keep provider
119
135
  changes user-controlled: do not scrape credentials, hand-export secret overrides,
120
136
  or silently switch to an unconfigured provider.
121
137
 
@@ -12,7 +12,7 @@ beyond a temporary file's lifetime. Inspect the script's help before choosing
12
12
  options:
13
13
 
14
14
  ```bash
15
- python3 scripts/long_screenshot_ocr.py --help
15
+ python3 "<skill-dir>/scripts/long_screenshot_ocr.py" --help
16
16
  ```
17
17
 
18
18
  Prefer native chunk reading by the main agent; the glance-backed script is
@@ -22,7 +22,7 @@ splitter; verify joins even when the cut falls in a low-content band.
22
22
  ### Native chunk reading
23
23
 
24
24
  ```bash
25
- python3 scripts/long_screenshot_ocr.py work/page.png --split-only
25
+ python3 "<skill-dir>/scripts/long_screenshot_ocr.py" work/page.png --split-only
26
26
  ```
27
27
 
28
28
  This writes ordered chunk images and `manifest.json` without vision calls.
@@ -38,10 +38,10 @@ Choose the content mode; this path makes configured vision calls:
38
38
 
39
39
  ```bash
40
40
  # Web pages, documents, logs, tables, and other general content
41
- python3 scripts/long_screenshot_ocr.py work/page.png -o work/page.ocr.md
41
+ python3 "<skill-dir>/scripts/long_screenshot_ocr.py" work/page.png -o work/page.ocr.md
42
42
 
43
43
  # Chat histories: preserve message grouping, senders, timestamps, and quotes
44
- python3 scripts/long_screenshot_ocr.py work/chat.png --mode chat -o work/chat.ocr.md
44
+ python3 "<skill-dir>/scripts/long_screenshot_ocr.py" work/chat.png --mode chat -o work/chat.ocr.md
45
45
  ```
46
46
 
47
47
  Without `-o`, write `<input-stem>.ocr.md`; store chunks, sidecars, the manifest,
@@ -63,7 +63,7 @@ Use `--resume` after an interrupted run. A chunk is reused only when its image,
63
63
  mode, and custom prompt fingerprint still match:
64
64
 
65
65
  ```bash
66
- python3 scripts/long_screenshot_ocr.py work/chat.png --mode chat --resume -o work/chat.ocr.md
66
+ python3 "<skill-dir>/scripts/long_screenshot_ocr.py" work/chat.png --mode chat --resume -o work/chat.ocr.md
67
67
  ```
68
68
 
69
69
  If the defaults produce awkward chunks, rerun with `--target-height`,
@@ -85,8 +85,8 @@ context; do not make tiny OCR tiles unless the source text is unusually small.
85
85
  unresolved, targeted specialist OCR is available:
86
86
 
87
87
  ```bash
88
- glance work/page_chunks/chunk_002.png --ocr "Re-check the final five lines carefully."
89
- glance work/page_chunks/chunk_002.png --region X1,Y1,X2,Y2 --ocr
88
+ python3 "<skill-dir>/bin/glance" work/page_chunks/chunk_002.png --ocr "Re-check the final five lines carefully."
89
+ python3 "<skill-dir>/bin/glance" work/page_chunks/chunk_002.png --region X1,Y1,X2,Y2 --ocr
90
90
  ```
91
91
 
92
92
  5. Keep visible spelling and punctuation verbatim. Write `[unreadable]` for
@@ -25,16 +25,16 @@ pixels as foreground.
25
25
 
26
26
  ```bash
27
27
  # Known tight region
28
- python3 scripts/extract_fg.py shot.png \
28
+ python3 "<skill-dir>/scripts/extract_fg.py" shot.png \
29
29
  --region X1,Y1,X2,Y2 -o asset.png
30
30
 
31
31
  # Dark or gray line art
32
- python3 scripts/extract_fg.py shot.png \
32
+ python3 "<skill-dir>/scripts/extract_fg.py" shot.png \
33
33
  --region X1,Y1,X2,Y2 --mode dark -o asset.png
34
34
 
35
35
  # Center and enlarge a small visual before automatic extraction
36
- crop shot.png --region X1,Y1,X2,Y2 --scale 4 -o work/asset4x.png
37
- python3 scripts/extract_fg.py work/asset4x.png
36
+ python3 "<skill-dir>/bin/crop" shot.png --region X1,Y1,X2,Y2 --scale 4 -o work/asset4x.png
37
+ python3 "<skill-dir>/scripts/extract_fg.py" work/asset4x.png
38
38
  ```
39
39
 
40
40
  Use `--exclude-color '#RRGGBB'` when a connected background color would
@@ -47,9 +47,9 @@ clean alpha edges, and no neighboring fragments before reuse.
47
47
  ## Rebuild an SVG only when the deliverable needs one
48
48
 
49
49
  ```bash
50
- trace asset.png -o asset.svg
51
- trace asset.png --polygon -o asset.svg
52
- trace shot.png --region X1,Y1,X2,Y2 -o asset.svg
50
+ python3 "<skill-dir>/bin/trace" asset.png -o asset.svg
51
+ python3 "<skill-dir>/bin/trace" asset.png --polygon -o asset.svg
52
+ python3 "<skill-dir>/bin/trace" shot.png --region X1,Y1,X2,Y2 -o asset.svg
53
53
  ```
54
54
 
55
55
  Ship traced paths directly for organic or irregular shapes. For rectangles,
@@ -140,13 +140,13 @@ extraction, tighten the final region to the visual's own ink; adjacent text or
140
140
  rules inside the region can become foreground components too.
141
141
 
142
142
  ```bash
143
- crop shot.png --region X1,Y1,X2,Y2 --scale 4 -o work/icon4x.png
144
- python3 scripts/extract_fg.py work/icon4x.png
143
+ python3 "<skill-dir>/bin/crop" shot.png --region X1,Y1,X2,Y2 --scale 4 -o work/icon4x.png
144
+ python3 "<skill-dir>/scripts/extract_fg.py" work/icon4x.png
145
145
 
146
146
  # Or extract directly from a known tight region:
147
- python3 scripts/extract_fg.py shot.png \
147
+ python3 "<skill-dir>/scripts/extract_fg.py" shot.png \
148
148
  --region X1,Y1,X2,Y2 -o work/icon.png
149
- python3 scripts/extract_fg.py shot.png \
149
+ python3 "<skill-dir>/scripts/extract_fg.py" shot.png \
150
150
  --region X1,Y1,X2,Y2 --mode dark -o work/logo.png
151
151
  ```
152
152
 
@@ -456,13 +456,13 @@ def command_for_path(path: Path) -> list[str]:
456
456
 
457
457
 
458
458
  def resolve_glance_command() -> list[str]:
459
+ bundled_glance = Path(__file__).resolve().parents[1] / "bin" / "glance"
460
+ if bundled_glance.is_file():
461
+ return [sys.executable, str(bundled_glance)]
459
462
  discovered = shutil.which("glance")
460
463
  if discovered:
461
464
  return command_for_path(Path(discovered))
462
- repository_glance = Path(__file__).resolve().parents[3] / "bin" / "glance"
463
- if repository_glance.is_file():
464
- return command_for_path(repository_glance)
465
- raise FileNotFoundError("glance is not on PATH; install the toolkit first")
465
+ raise FileNotFoundError("glance is not in this skill directory or on PATH")
466
466
 
467
467
 
468
468
  def ocr_prompt(mode: str, index: int, total: int, custom: str | None) -> str:
@@ -0,0 +1,71 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { readFileSync, readdirSync, existsSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { spawnSync } from 'node:child_process';
7
+
8
+ const skill = fileURLToPath(new URL('../skills/vision-tools/', import.meta.url));
9
+
10
+ test('skill command examples invoke bundled Python scripts independently of PATH and cwd', () => {
11
+ const docs = [join(skill, 'SKILL.md'), ...readdirSync(join(skill, 'references')).map(name => join(skill, 'references', name))];
12
+ const launchers = new Set();
13
+ for (const doc of docs) {
14
+ const source = readFileSync(doc, 'utf8');
15
+ for (const [, commands] of source.matchAll(/```bash\n([\s\S]*?)```/g)) {
16
+ assert.equal(/^\s*(?:crop|detect|glance|ground|trace|vision-doctor)\s+\S/m.test(commands), false, `${doc}: bare launcher command`);
17
+ }
18
+ assert.equal(/python3 scripts\//.test(source), false, `${doc}: cwd-relative script path`);
19
+ for (const [, relative] of source.matchAll(/python3 "<skill-dir>\/([^"]+)"/g)) {
20
+ assert.ok(existsSync(join(skill, relative)), `${doc}: missing bundled script ${relative}`);
21
+ if (relative.startsWith('bin/')) launchers.add(relative.slice(4));
22
+ }
23
+ }
24
+ assert.deepEqual([...launchers].sort(), ['crop', 'detect', 'glance', 'ground', 'trace', 'vision-doctor'].sort());
25
+ });
26
+
27
+ test('automated OCR finds bundled glance without PATH, including non-executable paths with spaces', t => {
28
+ const python = [['python3'], ['python'], ['py', '-3']].find(([command, ...args]) => {
29
+ const result = spawnSync(command, [...args, '--version'], { encoding: 'utf8', timeout: 10_000 });
30
+ return result.status === 0 && /^Python 3\./.test(result.stdout);
31
+ });
32
+ if (!python) return t.skip('Python 3 is not installed');
33
+ const code = `
34
+ import importlib.util
35
+ from pathlib import Path
36
+ import subprocess
37
+ import sys
38
+ import tempfile
39
+ from unittest.mock import patch
40
+
41
+ source = Path(sys.argv[1])
42
+ spec = importlib.util.spec_from_file_location("long_screenshot_ocr", source)
43
+ module = importlib.util.module_from_spec(spec)
44
+ sys.modules[spec.name] = module
45
+ spec.loader.exec_module(module)
46
+ with tempfile.TemporaryDirectory(prefix="vision skill ") as directory:
47
+ skill = Path(directory)
48
+ script = skill / "scripts" / "long_screenshot_ocr.py"
49
+ script.parent.mkdir()
50
+ script.touch()
51
+ glance = skill / "bin" / "glance"
52
+ glance.parent.mkdir()
53
+ glance.write_text('print("bundled glance reached")', encoding="utf-8")
54
+ with patch.object(module, "__file__", str(script)), patch.object(module.shutil, "which", return_value=None):
55
+ command = module.resolve_glance_command()
56
+ assert command == [sys.executable, str(glance)], command
57
+ result = subprocess.run(command, capture_output=True, text=True, check=True)
58
+ assert result.stdout.strip() == "bundled glance reached", result.stdout
59
+ with patch.object(module, "__file__", str(script)), patch.object(module.shutil, "which", return_value="/unrelated/glance"):
60
+ assert module.resolve_glance_command() == command, "prefer the packaged script over unrelated PATH commands"
61
+ glance.unlink()
62
+ external = skill / "custom_glance.py"
63
+ external.touch()
64
+ with patch.object(module, "__file__", str(script)), patch.object(module.shutil, "which", return_value=str(external)):
65
+ assert module.resolve_glance_command() == [sys.executable, str(external)], "retain the external fallback"
66
+ `;
67
+ const result = spawnSync(python[0], [...python.slice(1), '-c', code, join(skill, 'scripts', 'long_screenshot_ocr.py')], {
68
+ encoding: 'utf8', timeout: 20_000, env: { ...process.env, PYTHONDONTWRITEBYTECODE: '1' },
69
+ });
70
+ assert.equal(result.status, 0, result.stderr || result.error?.message);
71
+ });
@@ -1,5 +1,9 @@
1
1
  # @saifymatteo/pi-wait
2
2
 
3
+ ![wait joining a background shell job and returning its output](screenshot.png)
4
+
5
+ *The agent starts a build in the background, then calls `wait` on `shell:1`. The job's output comes back in the same turn. Needs pi-bg-bash.*
6
+
3
7
  ## Purpose and registrations
4
8
 
5
9
  Registers the `wait` tool, joining 1..64 unique explicit `shell:<id>` / `agent:<id>` jobs. Returns results in requested order. Timeout releases claims without stopping work; abort/session changes invalidate the join. No commands or configuration files; no external CLI/model service.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-wait",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -1,5 +1,9 @@
1
1
  # @saifymatteo/pi-zvec
2
2
 
3
+ ![zvec_search hybrid results with Zvec index status in the footer](screenshot.png)
4
+
5
+ *The agent calls `zvec_search` in hybrid mode, which combines lexical and semantic search (expanded here with Ctrl+O). The footer shows the index status.*
6
+
3
7
  ## Purpose and registrations
4
8
 
5
9
  Registers `zvec_search` (exact/fts/vector/hybrid/auto), `/zvec-init`, `/zvec-sync`, `/zvec-status`, session and settle-sync hooks, and footer status.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saifymatteo/pi-zvec",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "keywords": [