pi-skillful 0.3.14 → 0.5.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.
- package/CHANGELOG.md +43 -13
- package/CONTRIBUTING.md +14 -0
- package/README.md +24 -2
- package/SECURITY.md +10 -0
- package/extensions/index.ts +2 -0
- package/package.json +9 -6
- package/src/extensions/progressive-skills.ts +55 -0
- package/src/extensions/session-skill-toggles.ts +10 -12
- package/src/extensions/skill-visibility.ts +37 -15
- package/src/install-telemetry.ts +20 -47
- package/src/skill-prompt.ts +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,20 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This project follows the spirit of [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and uses semantic versioning for releases.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## [Unreleased]
|
|
8
8
|
|
|
9
|
-
## 0.
|
|
9
|
+
## [0.5.0] - 2026-10-09
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
### Fixed
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
- Read the current theme when rendering session-toggle borders and fit the complete border at very narrow widths. Extend editor composition coverage for paste, padding, indicators, and disposal.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
### Changed
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
- Update skill visibility and session toggles through Pi's structured prompt API instead of replacing the rendered system prompt. Preserve unrelated sections and deliberate full-prompt overrides.
|
|
18
|
+
- Update the shared Pi development and contract-test baseline to 1.1.0; require Node.js >=22.19.0 to match the host runtime. Pi remains a host-supplied peer dependency.
|
|
19
|
+
- Share install telemetry mechanics through `@mocito/install-telemetry` while preserving Pi-specific settings and state paths.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Apply visibility settings to Pi 1.1.0's wrapped skills section, including when file readers are hidden behind codemode.
|
|
24
|
+
- Keep frontmatter and earlier extensions' visibility restrictions when session toggle slots are active.
|
|
25
|
+
- Restore hidden-skill startup colors on Pi 1.x while preserving theme changes, expanded resource paths, and reloads.
|
|
26
|
+
- Let `enableInstallTelemetry: false` override an enabled `PI_TELEMETRY` environment flag.
|
|
27
|
+
|
|
28
|
+
## [0.4.0] - 2026-07-28
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- Progressive skill loading: discover `.agents/skills/` directories from all ancestor directories (like `AGENTS.md`), not just within the git repo boundary. Uses the `resources_discover` event to contribute missed paths.
|
|
33
|
+
|
|
34
|
+
## [0.3.14] - 2026-07-17
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Restore red/dim coloring for hidden and visible skills in Pi's startup `[Skills]` list while preserving project-trust-aware settings.
|
|
39
|
+
|
|
40
|
+
## [0.3.13] - 2026-07-17
|
|
18
41
|
|
|
19
42
|
### Fixed
|
|
20
43
|
|
|
@@ -22,16 +45,23 @@ This project follows the spirit of [Keep a Changelog](https://keepachangelog.com
|
|
|
22
45
|
- Let an explicit project `toggleModifier: "alt"` override a non-default global modifier; unsupported explicit values now follow the documented `"alt"` fallback.
|
|
23
46
|
- Consume only configured modifier-slot combinations in the prompt editor instead of registering all 54 possible shortcuts.
|
|
24
47
|
- Propagate focus and custom-editor hooks through the session-toggle editor wrapper for IME and extension compatibility.
|
|
25
|
-
- Restore red/dim coloring for hidden and visible skills in Pi's startup `[Skills]` list while preserving project-trust-aware settings.
|
|
26
48
|
- Remove the stale package-local `package-lock.json` in favor of the monorepo root lockfile.
|
|
27
|
-
- Preserve configured hidden skills at session start. Skillful no longer treats a temporarily incomplete loaded-skill list as authoritative and removes global or project visibility settings.
|
|
28
49
|
|
|
29
|
-
## [0.3.
|
|
50
|
+
## [0.3.12] - 2026-07-13
|
|
30
51
|
|
|
31
52
|
### Fixed
|
|
32
53
|
|
|
33
|
-
-
|
|
34
|
-
|
|
54
|
+
- Preserve configured hidden skills at session start instead of pruning settings from a temporarily incomplete loaded-skill list.
|
|
55
|
+
|
|
56
|
+
## [0.3.11] - 2026-07-01
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
|
|
60
|
+
- Color hidden skills in the startup `[Skills]` list on Pi 0.80+, which moved the rendered resources into `loadedResourcesContainer`.
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
|
|
64
|
+
- Raise the Pi development dependency floor to `^0.80.0`.
|
|
35
65
|
|
|
36
66
|
## [0.3.9] - 2026-06-14
|
|
37
67
|
|
package/CONTRIBUTING.md
CHANGED
|
@@ -33,6 +33,14 @@ pi -e /path/to/pi-mono/packages/pi-skillful
|
|
|
33
33
|
|
|
34
34
|
## Pull request checklist
|
|
35
35
|
|
|
36
|
+
For prompt changes, also run the shared real-session contracts from the monorepo root:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
node --import tsx --test tests/structured-prompts.test.mjs
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
These load both Skillful and Scout through Pi's SDK, inspect serialized provider requests and session history, and exercise the toggle editor wrapper. They use disposable profiles, synthetic credentials, temporary reference directories, and mocked provider responses; they do not use a live model or existing user settings. Coverage includes extension load order, explicit prompt overrides, hidden skill invocation, codemode, reload, resume, fork/tree navigation, and providers that collapse system updates.
|
|
43
|
+
|
|
36
44
|
Before opening a pull request:
|
|
37
45
|
|
|
38
46
|
- Run `npm run check`.
|
|
@@ -52,3 +60,9 @@ Before opening a pull request:
|
|
|
52
60
|
## Code of conduct
|
|
53
61
|
|
|
54
62
|
This project follows the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md).
|
|
63
|
+
## Pi 1.1.0 terminal audit
|
|
64
|
+
|
|
65
|
+
From the monorepo root, run `node --import tsx --test tests/tui-contracts.test.mjs`
|
|
66
|
+
alongside the package tests. `tests/TUI_AUDIT.md` records coverage, known host
|
|
67
|
+
limitations, and the disposable-profile physical-terminal acceptance procedure.
|
|
68
|
+
The startup patch remains isolated; no unreleased cursor API is required.
|
package/README.md
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
Make [Pi](https://pi.dev) skills easier to invoke, curate, and control without editing their source files.
|
|
8
8
|
|
|
9
|
-
`pi-skillful` provides
|
|
9
|
+
`pi-skillful` provides four focused upgrades:
|
|
10
10
|
|
|
11
|
+
- **Progressive skill loading**: discover `.agents/skills/` from every ancestor directory (like `AGENTS.md`), not just within the git repo boundary.
|
|
11
12
|
- **Inline skill invocation**: invoke one or more skills anywhere in a prompt with `/skill:name`.
|
|
12
13
|
- **Skill prompt visibility**: choose which skills are hidden from the model's automatic skill-discovery prompt while keeping them explicitly invokable.
|
|
13
14
|
- **Session skill toggles**: assign up to nine skills to number slots and toggle model visibility while writing a prompt.
|
|
@@ -17,6 +18,14 @@ Make [Pi](https://pi.dev) skills easier to invoke, curate, and control without e
|
|
|
17
18
|
|
|
18
19
|
## Features
|
|
19
20
|
|
|
21
|
+
### Progressive skill loading
|
|
22
|
+
|
|
23
|
+
Pi discovers `.agents/skills/` directories in ancestor folders, but stops at the git repo root — skills above the repo boundary are invisible. `pi-skillful` removes that boundary by walking the full ancestor chain to the filesystem root, mirroring how `AGENTS.md` context files work.
|
|
24
|
+
|
|
25
|
+
No configuration needed. When a git repo is detected, `pi-skillful` automatically contributes `.agents/skills/` directories from parent directories above the repo root (excluding `~/.agents/skills/`, which Pi already loads globally). In projects without a git repo, Pi already walks to the filesystem root and this feature has no effect.
|
|
26
|
+
|
|
27
|
+
Skill name collisions follow Pi's first-wins rule: skills discovered closer to the working directory take precedence over skills with the same name found higher up.
|
|
28
|
+
|
|
20
29
|
### Inline skill invocation
|
|
21
30
|
|
|
22
31
|
Vanilla Pi expands `/skill:name` only when it appears at the beginning of the prompt. `pi-skillful` expands known skill markers anywhere in the prompt, including multiple skills:
|
|
@@ -93,6 +102,19 @@ Configured slots appear on the prompt editor's top border as `N skill-name`. Pro
|
|
|
93
102
|
|
|
94
103
|
On app startup, non-hidden skills are active and hidden skills are inactive. Within a running Pi process, `/new` preserves the current toggle state for the new session. Resuming, forking, cloning, reloading, or restarting Pi resets toggle state from settings. Inline `/skill:name` invocation remains explicit and works even when that skill is inactive. Skills bundled in Pi packages are never modified by these toggles.
|
|
95
104
|
|
|
105
|
+
### Prompt updates and extension composition
|
|
106
|
+
|
|
107
|
+
Toggle-border colors follow the current theme without another toggle or reload.
|
|
108
|
+
At very narrow widths, the complete border is clipped to the available columns.
|
|
109
|
+
|
|
110
|
+
Visibility and toggles use Pi's structured `systemPromptOptions.skills` API (Pi 1.1.0 or newer), not text replacement of the full system prompt. Pi records skill-list changes in the session transcript, alongside independent updates from other extensions such as Scout.
|
|
111
|
+
|
|
112
|
+
An active slot can lift a restriction from `skillful.hiddenSkills`, but does not clear `disable-model-invocation` frontmatter or a restriction applied by an earlier prompt handler. Later handlers see the updated list and can apply their own policy. Package-bundled skills remain outside Skillful's visibility controls and toggle slots.
|
|
113
|
+
|
|
114
|
+
A deliberate full-prompt override from another extension (`systemPrompt` or `forceSystemPrompt`) takes precedence. Skillful updates the structured skill list but does not rewrite that override; its author controls any skill descriptions embedded in the replacement text. Explicit skill invocation remains available.
|
|
115
|
+
|
|
116
|
+
Structured changes let Pi update individual prompt sections. Providers that cannot accept mid-conversation system changes may still need a full prompt checkpoint; cache savings are not guaranteed.
|
|
117
|
+
|
|
96
118
|
## Installation
|
|
97
119
|
|
|
98
120
|
Install from npm:
|
|
@@ -139,7 +161,7 @@ This package is source-distributed. Pi loads the TypeScript extensions directly
|
|
|
139
161
|
|
|
140
162
|
Requirements:
|
|
141
163
|
|
|
142
|
-
- Node.js >=
|
|
164
|
+
- Node.js >= 22.19.0
|
|
143
165
|
- npm for local development commands
|
|
144
166
|
|
|
145
167
|
Common commands:
|
package/SECURITY.md
CHANGED
|
@@ -20,3 +20,13 @@ The maintainer will acknowledge reports as soon as practical and coordinate disc
|
|
|
20
20
|
## Security model
|
|
21
21
|
|
|
22
22
|
`pi-skillful` is a Pi package. Pi extensions execute with the same permissions as the local user running Pi. Users should review installed Pi packages and only install packages from sources they trust.
|
|
23
|
+
|
|
24
|
+
At startup, `@mocito/install-telemetry` sends a best-effort install/update ping to the configured telemetry endpoint once per package version unless CI, Pi offline/telemetry settings, or `enableInstallTelemetry: false` disables it. It contains only the package name/version and parsed platform/runtime/architecture; it does not include prompts, paths, configuration values, credentials, or provider responses.
|
|
25
|
+
|
|
26
|
+
### Progressive skill loading and trust boundary
|
|
27
|
+
|
|
28
|
+
`pi-skillful` extends Pi's skill discovery to ancestor directories above the git repository root, loading `.agents/skills/` from every parent directory up to the filesystem root (excluding `~/.agents/skills/`, which Pi already loads globally).
|
|
29
|
+
|
|
30
|
+
This means skills placed in a shared or writable ancestor directory — for example, `/home/shared/.agents/skills/` or `/tmp/.agents/skills/` — are automatically available as skill instructions when Pi runs in any nested repository. Review the contents and ownership of `.agents/skills/` directories above your repositories, as they are now part of the effective skill set.
|
|
31
|
+
|
|
32
|
+
To disable progressive loading without removing the package, uninstall `pi-skillful` or use Pi's built-in `--no-skills` flag to suppress all skill loading for a session.
|
package/extensions/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import inlineSkillInvocation from "../src/extensions/inline-skill-invocation.js";
|
|
3
|
+
import progressiveSkills from "../src/extensions/progressive-skills.js";
|
|
3
4
|
import { reportInstallTelemetry } from "../src/install-telemetry.js";
|
|
4
5
|
import skillVisibility from "../src/extensions/skill-visibility.js";
|
|
5
6
|
import sessionSkillToggles from "../src/extensions/session-skill-toggles.js";
|
|
@@ -7,6 +8,7 @@ import sessionSkillToggles from "../src/extensions/session-skill-toggles.js";
|
|
|
7
8
|
export default function piSkillful(pi: ExtensionAPI) {
|
|
8
9
|
reportInstallTelemetry();
|
|
9
10
|
|
|
11
|
+
progressiveSkills(pi);
|
|
10
12
|
inlineSkillInvocation(pi);
|
|
11
13
|
skillVisibility(pi);
|
|
12
14
|
sessionSkillToggles(pi);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-skillful",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Pi package with skill invocation and visibility improvements.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"@earendil-works/pi-tui": "*"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@earendil-works/pi-coding-agent": "
|
|
49
|
-
"@earendil-works/pi-tui": "
|
|
50
|
-
"@types/node": "^26.
|
|
51
|
-
"typescript": "^
|
|
48
|
+
"@earendil-works/pi-coding-agent": "1.1.0",
|
|
49
|
+
"@earendil-works/pi-tui": "1.1.0",
|
|
50
|
+
"@types/node": "^26.6.3",
|
|
51
|
+
"typescript": "^7.0.2"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"check": "tsc --noEmit",
|
|
@@ -60,6 +60,9 @@
|
|
|
60
60
|
"access": "public"
|
|
61
61
|
},
|
|
62
62
|
"engines": {
|
|
63
|
-
"node": ">=
|
|
63
|
+
"node": ">=22.19.0"
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"@mocito/install-telemetry": "0.1.1"
|
|
64
67
|
}
|
|
65
68
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Walks from cwd up through all ancestor directories (no git boundary)
|
|
8
|
+
* and discovers `.agents/skills/` directories that Pi's built-in discovery
|
|
9
|
+
* misses because it stops at the git repo root.
|
|
10
|
+
*
|
|
11
|
+
* This mirrors how AGENTS.md context files are loaded from every parent
|
|
12
|
+
* directory, not just within the repo boundary.
|
|
13
|
+
*/
|
|
14
|
+
export default function progressiveSkills(pi: ExtensionAPI) {
|
|
15
|
+
pi.on("resources_discover", async (event) => {
|
|
16
|
+
const gitRoot = findGitRoot(event.cwd);
|
|
17
|
+
|
|
18
|
+
// No git repo — Pi already walks to filesystem root, nothing to add.
|
|
19
|
+
if (!gitRoot) return;
|
|
20
|
+
|
|
21
|
+
const resolvedGitRoot = resolve(gitRoot);
|
|
22
|
+
const homeAgentsSkills = resolve(homedir(), ".agents", "skills");
|
|
23
|
+
const skillPaths: string[] = [];
|
|
24
|
+
|
|
25
|
+
// Walk from git root's parent up to filesystem root.
|
|
26
|
+
// Order: git-parent first (closer to cwd = higher priority via first-wins).
|
|
27
|
+
let dir = dirname(resolvedGitRoot);
|
|
28
|
+
while (true) {
|
|
29
|
+
const agentsSkills = join(dir, ".agents", "skills");
|
|
30
|
+
const resolved = resolve(agentsSkills);
|
|
31
|
+
|
|
32
|
+
// Skip ~/.agents/skills (handled globally by Pi) and non-existent dirs.
|
|
33
|
+
if (resolved !== homeAgentsSkills && existsSync(resolved)) {
|
|
34
|
+
skillPaths.push(resolved);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const parent = dirname(dir);
|
|
38
|
+
if (parent === dir) break;
|
|
39
|
+
dir = parent;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (skillPaths.length === 0) return;
|
|
43
|
+
return { skillPaths };
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function findGitRoot(startDir: string): string | undefined {
|
|
48
|
+
let dir = resolve(startDir);
|
|
49
|
+
while (true) {
|
|
50
|
+
if (existsSync(join(dir, ".git"))) return dir;
|
|
51
|
+
const parent = dirname(dir);
|
|
52
|
+
if (parent === dir) return undefined;
|
|
53
|
+
dir = parent;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
type AppKeybinding,
|
|
4
4
|
type ExtensionAPI,
|
|
5
5
|
type KeybindingsManager,
|
|
6
|
-
type Skill,
|
|
7
6
|
type Theme,
|
|
8
7
|
} from "@earendil-works/pi-coding-agent";
|
|
9
8
|
import type { AutocompleteProvider, EditorComponent, EditorTheme, Focusable, KeyId, TUI } from "@earendil-works/pi-tui";
|
|
@@ -16,7 +15,6 @@ import {
|
|
|
16
15
|
type SkillToggleModifier,
|
|
17
16
|
type SkillToggleSlot,
|
|
18
17
|
} from "../config.js";
|
|
19
|
-
import { replaceSkillsSection } from "../skill-prompt.js";
|
|
20
18
|
import { isTopLevelSkill, listLoadedSkills } from "../skills.js";
|
|
21
19
|
|
|
22
20
|
const STORE_KEY = Symbol.for("pi-skillful.sessionSkillTogglesStore");
|
|
@@ -40,7 +38,7 @@ interface SessionToggleState {
|
|
|
40
38
|
installedEditor: boolean;
|
|
41
39
|
previousEditorFactory: SkillfulEditorFactory | undefined;
|
|
42
40
|
activeTui: TUI | undefined;
|
|
43
|
-
theme: Theme | undefined;
|
|
41
|
+
theme: (() => Theme) | undefined;
|
|
44
42
|
notify: (message: string, type?: "info" | "warning" | "error") => void;
|
|
45
43
|
}
|
|
46
44
|
|
|
@@ -79,7 +77,7 @@ export default function sessionSkillToggles(pi: ExtensionAPI) {
|
|
|
79
77
|
installedEditor: false,
|
|
80
78
|
previousEditorFactory: undefined,
|
|
81
79
|
activeTui: undefined,
|
|
82
|
-
theme: ctx.ui.theme,
|
|
80
|
+
theme: () => ctx.ui.theme,
|
|
83
81
|
notify: ctx.ui.notify.bind(ctx.ui),
|
|
84
82
|
};
|
|
85
83
|
|
|
@@ -90,13 +88,13 @@ export default function sessionSkillToggles(pi: ExtensionAPI) {
|
|
|
90
88
|
pi.on("before_agent_start", (event) => {
|
|
91
89
|
if (state.slots.length === 0 || !event.systemPromptOptions.skills?.length) return;
|
|
92
90
|
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
// Active slots lift only Skillful's own restriction, never frontmatter or
|
|
92
|
+
// restrictions already applied by another prompt handler.
|
|
93
|
+
event.systemPromptOptions.skills = event.systemPromptOptions.skills.map((skill) =>
|
|
94
|
+
isTopLevelSkill(skill) && !isSkillActive(normalizeSkillName(skill.name))
|
|
95
|
+
? { ...skill, disableModelInvocation: true }
|
|
96
|
+
: skill,
|
|
95
97
|
);
|
|
96
|
-
|
|
97
|
-
const systemPrompt = replaceSkillsSection(event.systemPrompt, updatedSkills);
|
|
98
|
-
if (!systemPrompt) return;
|
|
99
|
-
return { systemPrompt };
|
|
100
98
|
});
|
|
101
99
|
|
|
102
100
|
pi.on("session_shutdown", (event, ctx) => {
|
|
@@ -373,7 +371,7 @@ function renderToggleBorder(width: number, borderColor: (text: string) => string
|
|
|
373
371
|
const fittedContent = truncateToWidth(renderToggleSegments(available), available, "");
|
|
374
372
|
const used = BORDER_PREFIX_WIDTH + visibleWidth(fittedContent) + BORDER_SUFFIX_WIDTH;
|
|
375
373
|
const fill = borderColor("─".repeat(Math.max(0, width - used)));
|
|
376
|
-
return `${borderColor(BORDER_PREFIX)}${fittedContent}${BORDER_SUFFIX}${fill}
|
|
374
|
+
return truncateToWidth(`${borderColor(BORDER_PREFIX)}${fittedContent}${BORDER_SUFFIX}${fill}`, width, "");
|
|
377
375
|
}
|
|
378
376
|
|
|
379
377
|
function renderToggleSegments(availableWidth: number): string {
|
|
@@ -396,5 +394,5 @@ function renderToggleSegments(availableWidth: number): string {
|
|
|
396
394
|
}
|
|
397
395
|
|
|
398
396
|
function stateThemeFg(color: "accent" | "muted", text: string): string {
|
|
399
|
-
return state.theme?.fg(color, text) ?? text;
|
|
397
|
+
return state.theme?.().fg(color, text) ?? text;
|
|
400
398
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stripVTControlCharacters } from "node:util";
|
|
1
2
|
import {
|
|
2
3
|
DynamicBorder,
|
|
3
4
|
getSettingsListTheme,
|
|
@@ -19,20 +20,23 @@ import {
|
|
|
19
20
|
writeProjectSkillfulOverride,
|
|
20
21
|
writeToggleSlots,
|
|
21
22
|
} from "../config.js";
|
|
22
|
-
import { replaceSkillsSection } from "../skill-prompt.js";
|
|
23
23
|
import { isTopLevelSkill, listLoadedSkills, type LoadedSkillInfo } from "../skills.js";
|
|
24
24
|
import { hasActiveSessionSkillToggles, refreshSessionSkillToggles } from "./session-skill-toggles.js";
|
|
25
25
|
const SCOPES: SkillfulScope[] = ["global", "project"];
|
|
26
26
|
const STORE_KEY = Symbol.for("pi-skillful.skillVisibilityStore");
|
|
27
|
-
|
|
27
|
+
// Install the new component contract even after reloading a V3-patched process.
|
|
28
|
+
const STARTUP_PATCH_KEY = Symbol.for("pi-skillful.startupPatchV4");
|
|
28
29
|
|
|
29
30
|
interface SkillVisibilityStore {
|
|
30
31
|
hiddenSkillsByCwd: Map<string, Set<string>>;
|
|
31
32
|
theme: Theme | null;
|
|
33
|
+
getTheme?: () => Theme;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
interface ExpandableTextLike {
|
|
35
|
-
getCollapsedText
|
|
37
|
+
getCollapsedText?: () => string;
|
|
38
|
+
build?: () => string;
|
|
39
|
+
invalidate?: () => void;
|
|
36
40
|
setText: (text: string) => void;
|
|
37
41
|
}
|
|
38
42
|
|
|
@@ -43,6 +47,7 @@ interface BoxLike {
|
|
|
43
47
|
interface InteractiveModeLike {
|
|
44
48
|
loadedResourcesContainer?: BoxLike;
|
|
45
49
|
showLoadedResources?: (options?: unknown) => void;
|
|
50
|
+
getStartupExpansionState?: () => boolean;
|
|
46
51
|
session?: { resourceLoader?: { getSkills: () => { skills: Skill[]; diagnostics: unknown[] } } };
|
|
47
52
|
sessionManager?: { getCwd?: () => string };
|
|
48
53
|
}
|
|
@@ -84,6 +89,7 @@ export default function skillVisibility(pi: ExtensionAPI) {
|
|
|
84
89
|
|
|
85
90
|
pi.on("session_start", async (_event, ctx) => {
|
|
86
91
|
store.theme = ctx.ui.theme;
|
|
92
|
+
store.getTheme = () => ctx.ui.theme;
|
|
87
93
|
await refreshHiddenSkillCache(ctx.cwd, ctx.isProjectTrusted());
|
|
88
94
|
});
|
|
89
95
|
|
|
@@ -93,12 +99,9 @@ export default function skillVisibility(pi: ExtensionAPI) {
|
|
|
93
99
|
const hidden = await refreshHiddenSkillCache(ctx.cwd, ctx.isProjectTrusted());
|
|
94
100
|
if (hidden.size === 0 || !event.systemPromptOptions.skills?.length) return;
|
|
95
101
|
|
|
96
|
-
|
|
102
|
+
event.systemPromptOptions.skills = event.systemPromptOptions.skills.map((skill) =>
|
|
97
103
|
isTopLevelSkill(skill) && hidden.has(skill.name) ? { ...skill, disableModelInvocation: true } : skill,
|
|
98
104
|
);
|
|
99
|
-
const systemPrompt = replaceSkillsSection(event.systemPrompt, filteredSkills);
|
|
100
|
-
if (!systemPrompt) return;
|
|
101
|
-
return { systemPrompt };
|
|
102
105
|
});
|
|
103
106
|
|
|
104
107
|
pi.registerCommand("skillful", {
|
|
@@ -184,7 +187,6 @@ export function installStartupSkillListPatch(
|
|
|
184
187
|
return result;
|
|
185
188
|
};
|
|
186
189
|
|
|
187
|
-
const childrenBefore = this.loadedResourcesContainer?.children.length ?? 0;
|
|
188
190
|
try {
|
|
189
191
|
original.call(this, options);
|
|
190
192
|
} finally {
|
|
@@ -193,16 +195,36 @@ export function installStartupSkillListPatch(
|
|
|
193
195
|
|
|
194
196
|
if (rawSkillNames.length === 0 || !cwd || !this.loadedResourcesContainer) return;
|
|
195
197
|
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
const colorized = () => buildColorizedSkillList(
|
|
199
|
+
rawSkillNames,
|
|
200
|
+
store.hiddenSkillsByCwd.get(cwd) ?? new Set<string>(),
|
|
201
|
+
store.getTheme?.() ?? store.theme,
|
|
202
|
+
);
|
|
203
|
+
const collapsedText = buildColorizedSkillList(rawSkillNames, new Set(), null);
|
|
204
|
+
// Pi clears and rebuilds this container, including during /reload.
|
|
205
|
+
for (const entry of this.loadedResourcesContainer.children) {
|
|
206
|
+
const child = entry as ExpandableTextLike | undefined;
|
|
207
|
+
if (child && typeof child.build === "function") {
|
|
208
|
+
// Pi 1.x closes over expansion state in ThemedText.build instead of
|
|
209
|
+
// exposing getCollapsedText. Decorate only the compact skill list;
|
|
210
|
+
// leave expanded paths and other sections to Pi. Rebuild colors on
|
|
211
|
+
// invalidation so theme changes never retain captured ANSI strings.
|
|
212
|
+
const build = child.build;
|
|
213
|
+
if (!stripVTControlCharacters(build.call(child)).startsWith("[Skills]\n")) continue;
|
|
214
|
+
child.build = () => {
|
|
215
|
+
const text = build.call(child);
|
|
216
|
+
return stripVTControlCharacters(text) === collapsedText ? colorized() : text;
|
|
217
|
+
};
|
|
218
|
+
child.invalidate?.();
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
200
221
|
if (!child || typeof child.getCollapsedText !== "function") continue;
|
|
201
222
|
if (!child.getCollapsedText().includes("[Skills]")) continue;
|
|
202
223
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
child.
|
|
224
|
+
// Retain the older Pi component contract without collapsing an
|
|
225
|
+
// initially expanded resource listing.
|
|
226
|
+
child.getCollapsedText = colorized;
|
|
227
|
+
if (!this.getStartupExpansionState?.()) child.setText(colorized());
|
|
206
228
|
break;
|
|
207
229
|
}
|
|
208
230
|
};
|
package/src/install-telemetry.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
2
|
import { join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { reportInstallTelemetry as report } from "@mocito/install-telemetry";
|
|
5
5
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
|
|
7
7
|
const PACKAGE_NAME = "pi-skillful";
|
|
8
|
-
const
|
|
9
|
-
const INSTALL_TELEMETRY_TIMEOUT_MS = 5000;
|
|
8
|
+
const INSTALL_TELEMETRY_ENDPOINT = "https://mocito.dev/api/report-install";
|
|
10
9
|
const CI_ENVIRONMENT_VARIABLES = [
|
|
11
10
|
"APPVEYOR",
|
|
12
11
|
"BITBUCKET_BUILD_NUMBER",
|
|
@@ -24,10 +23,6 @@ const CI_ENVIRONMENT_VARIABLES = [
|
|
|
24
23
|
"VERCEL",
|
|
25
24
|
];
|
|
26
25
|
|
|
27
|
-
interface InstallTelemetryState {
|
|
28
|
-
lastReportedVersion?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
26
|
interface PiSettingsDocument {
|
|
32
27
|
enableInstallTelemetry?: unknown;
|
|
33
28
|
}
|
|
@@ -51,18 +46,15 @@ function isPresentEnvFlag(value: string | undefined): boolean {
|
|
|
51
46
|
return normalized !== "0" && normalized !== "false" && normalized !== "no";
|
|
52
47
|
}
|
|
53
48
|
|
|
54
|
-
function
|
|
55
|
-
if (isTruthyEnvFlag(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
function isInstallTelemetryEnabled(): boolean {
|
|
60
|
-
if (isCiEnvironment()) return false;
|
|
61
|
-
if (isTruthyEnvFlag(process.env.PI_OFFLINE)) return false;
|
|
62
|
-
if (process.env.PI_TELEMETRY !== undefined) return isTruthyEnvFlag(process.env.PI_TELEMETRY);
|
|
49
|
+
export function isInstallTelemetryEnabled(env: NodeJS.ProcessEnv = process.env, settingsPath = join(getAgentDir(), "settings.json")): boolean {
|
|
50
|
+
if (isTruthyEnvFlag(env.CI)) return false;
|
|
51
|
+
if (CI_ENVIRONMENT_VARIABLES.some((name) => isPresentEnvFlag(env[name]))) return false;
|
|
52
|
+
if (isTruthyEnvFlag(env.PI_OFFLINE)) return false;
|
|
63
53
|
|
|
64
|
-
const settings = readJsonFile(
|
|
65
|
-
|
|
54
|
+
const settings = readJsonFile(settingsPath) as PiSettingsDocument;
|
|
55
|
+
if (settings.enableInstallTelemetry === false) return false;
|
|
56
|
+
if (env.PI_TELEMETRY !== undefined) return isTruthyEnvFlag(env.PI_TELEMETRY);
|
|
57
|
+
return true;
|
|
66
58
|
}
|
|
67
59
|
|
|
68
60
|
function getPackageVersion(): string {
|
|
@@ -70,35 +62,16 @@ function getPackageVersion(): string {
|
|
|
70
62
|
return typeof packageJson.version === "string" && packageJson.version.length > 0 ? packageJson.version : "0.0.0";
|
|
71
63
|
}
|
|
72
64
|
|
|
73
|
-
function
|
|
74
|
-
const runtimeVersions = process.versions as NodeJS.ProcessVersions & { bun?: string };
|
|
75
|
-
const runtime = runtimeVersions.bun ? `bun/${runtimeVersions.bun}` : `node/${process.version}`;
|
|
76
|
-
return `${PACKAGE_NAME}/${version} (${process.platform}; ${runtime}; ${process.arch})`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function reportInstallTelemetryAsync(): Promise<void> {
|
|
65
|
+
export function reportInstallTelemetry(): void {
|
|
80
66
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
await mkdir(extensionsDir, { recursive: true });
|
|
90
|
-
await writeFile(statePath, `${JSON.stringify({ lastReportedVersion: version }, null, 2)}\n`, "utf8");
|
|
91
|
-
|
|
92
|
-
const params = new URLSearchParams({ tool: PACKAGE_NAME, version });
|
|
93
|
-
await fetch(`${INSTALL_TELEMETRY_URL}?${params.toString()}`, {
|
|
94
|
-
headers: { "User-Agent": getInstallTelemetryUserAgent(version) },
|
|
95
|
-
signal: AbortSignal.timeout(INSTALL_TELEMETRY_TIMEOUT_MS),
|
|
96
|
-
});
|
|
67
|
+
void report({
|
|
68
|
+
endpoint: INSTALL_TELEMETRY_ENDPOINT,
|
|
69
|
+
tool: PACKAGE_NAME,
|
|
70
|
+
version: getPackageVersion(),
|
|
71
|
+
statePath: join(getAgentDir(), "extensions", "skillful-install.json"),
|
|
72
|
+
enabled: isInstallTelemetryEnabled(),
|
|
73
|
+
}).catch(() => undefined);
|
|
97
74
|
} catch {
|
|
98
|
-
// Best-effort telemetry: ignore
|
|
75
|
+
// Best-effort telemetry: ignore local policy and filesystem failures.
|
|
99
76
|
}
|
|
100
77
|
}
|
|
101
|
-
|
|
102
|
-
export function reportInstallTelemetry(): void {
|
|
103
|
-
void reportInstallTelemetryAsync();
|
|
104
|
-
}
|
package/src/skill-prompt.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { formatSkillsForPrompt, type Skill } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
|
|
3
|
-
export const SKILLS_SECTION_PATTERN = /\n\nThe following skills provide specialized instructions for specific tasks\.[\s\S]*?<\/available_skills>/;
|
|
4
|
-
|
|
5
|
-
export function replaceSkillsSection(systemPrompt: string, skills: Skill[]): string | undefined {
|
|
6
|
-
const next = systemPrompt.replace(SKILLS_SECTION_PATTERN, formatSkillsForPrompt(skills));
|
|
7
|
-
return next === systemPrompt ? undefined : next;
|
|
8
|
-
}
|