@pulsemcp/air-adapter-pi 0.7.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/README.md +70 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/pi-adapter.d.ts +114 -0
- package/dist/pi-adapter.d.ts.map +1 -0
- package/dist/pi-adapter.js +444 -0
- package/dist/pi-adapter.js.map +1 -0
- package/dist/scan-local-skills.d.ts +11 -0
- package/dist/scan-local-skills.d.ts.map +1 -0
- package/dist/scan-local-skills.js +92 -0
- package/dist/scan-local-skills.js.map +1 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# @pulsemcp/air-adapter-pi
|
|
2
|
+
|
|
3
|
+
AIR adapter extension for the [Pi coding agent](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) (`pi`). Injects AIR skills into Pi's native skills location and prepares working directories for agent sessions.
|
|
4
|
+
|
|
5
|
+
> **Scope: skills only.** Pi does not ship with pre-baked MCP servers, hooks, references, or plugins, so this adapter translates *only* skills. MCP servers, hooks, and standalone references are intentionally not translated (see [Known gaps](#known-gaps)).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @pulsemcp/air-adapter-pi
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### With the AIR CLI
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Install the adapter globally alongside the CLI
|
|
19
|
+
npm install -g @pulsemcp/air-cli @pulsemcp/air-adapter-pi
|
|
20
|
+
|
|
21
|
+
# Start a Pi session
|
|
22
|
+
air start pi --root web-app
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Programmatic
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { resolveArtifacts } from "@pulsemcp/air-core";
|
|
29
|
+
import { PiAdapter } from "@pulsemcp/air-adapter-pi";
|
|
30
|
+
|
|
31
|
+
const artifacts = await resolveArtifacts("./air.json");
|
|
32
|
+
const adapter = new PiAdapter();
|
|
33
|
+
|
|
34
|
+
// Prepare a working directory for a Pi session
|
|
35
|
+
const session = await adapter.prepareSession(artifacts, "./my-project", {
|
|
36
|
+
root: artifacts.roots["web-app"],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// session.configFiles — [] (Pi discovers skills from the filesystem)
|
|
40
|
+
// session.skillPaths — skill dirs created in .pi/skills/
|
|
41
|
+
// session.hookPaths — [] (Pi is skills-only)
|
|
42
|
+
// session.startCommand — { command: "pi", args: [], cwd: "..." }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## What `prepareSession()` does
|
|
46
|
+
|
|
47
|
+
1. **Injects skills** — copies `SKILL.md` files and associated content into `.pi/skills/{name}/`, where Pi auto-discovers them. Any directory containing a `SKILL.md` is treated by Pi as a skill root.
|
|
48
|
+
2. **Copies references** — attaches a skill's referenced documents into `<skill>/references/`, so they travel with the self-contained skill directory.
|
|
49
|
+
3. **Reconciles via the manifest** — re-runs remove skills that are no longer activated before injecting the current set, keeping `air clean` and re-`prepare` idempotent.
|
|
50
|
+
4. **Respects local priority** — if a skill directory already exists in the target, it is not overwritten.
|
|
51
|
+
|
|
52
|
+
No config file is written: Pi loads `.pi/skills/` directly from the filesystem, so `prepareSession()` returns an **empty `configFiles` array**.
|
|
53
|
+
|
|
54
|
+
## Translation Details
|
|
55
|
+
|
|
56
|
+
| AIR Format | Pi Format |
|
|
57
|
+
|------------|-----------|
|
|
58
|
+
| Skills (`SKILL.md` + content) | `.pi/skills/{name}/` (auto-discovered as a project skill) |
|
|
59
|
+
| Skill-owned references | `.pi/skills/{name}/references/` |
|
|
60
|
+
| Plugin-declared skills | merged into the skill activation set (composition sugar) |
|
|
61
|
+
|
|
62
|
+
## Known gaps
|
|
63
|
+
|
|
64
|
+
These AIR features are intentionally **not** translated, because Pi is a skills-only target for AIR:
|
|
65
|
+
|
|
66
|
+
- **MCP servers** — Pi does not ship with an AIR-translatable MCP server registry. MCP server entries are ignored; the manifest records `mcpServers: []`.
|
|
67
|
+
- **Hooks** — Pi has no AIR-translatable hook lifecycle. Hook entries are ignored; the manifest records `hooks: []` and `prepareSession()` returns an empty `hookPaths` array.
|
|
68
|
+
- **Standalone references** — only skill-*owned* references are copied (alongside the skill). There is no standalone reference materialization.
|
|
69
|
+
- **Plugins** — honored only as composition sugar: a plugin's declared *skills* are merged into the activation set; its MCP servers and hooks are ignored.
|
|
70
|
+
- **Subagent context** — this adapter does not wire an AIR-driven system-prompt flag, so subagent-root context is returned to the caller via `PreparedSession.subagentContext` rather than passed to the CLI.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,QAAA,MAAM,SAAS,EAAE,YAGhB,CAAC;AAEF,eAAe,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,SAAS,GAAiB;IAC9B,IAAI,EAAE,IAAI;IACV,OAAO,EAAE,IAAI,SAAS,EAAE;CACzB,CAAC;AAEF,eAAe,SAAS,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { AgentAdapter, AgentSessionConfig, StartCommand, ResolvedArtifacts, RootEntry, PrepareSessionOptions, PreparedSession, CleanSessionOptions, CleanSessionResult, LocalArtifacts } from "@pulsemcp/air-core";
|
|
2
|
+
/**
|
|
3
|
+
* AIR adapter for the Pi coding agent (`pi`, https://www.npmjs.com/package/@earendil-works/pi-coding-agent).
|
|
4
|
+
*
|
|
5
|
+
* Scope: SKILLS ONLY. Pi does not ship with pre-baked MCP servers, hooks,
|
|
6
|
+
* references, or plugins the way a fuller agent runtime might, so this adapter
|
|
7
|
+
* translates only skills. MCP servers, hooks, and standalone references are
|
|
8
|
+
* intentionally not translated — see `generateConfig` and `prepareSession`.
|
|
9
|
+
*
|
|
10
|
+
* Pi auto-discovers project skills from `<cwd>/.pi/skills/`: any directory
|
|
11
|
+
* containing a `SKILL.md` is treated as a skill root (Pi stops recursing into
|
|
12
|
+
* it, so bundled reference files are safe). This adapter materializes activated
|
|
13
|
+
* skills into `.pi/skills/<name>/` — purely filesystem placement, no config
|
|
14
|
+
* file is written or required for Pi to load them.
|
|
15
|
+
*/
|
|
16
|
+
export declare class PiAdapter implements AgentAdapter {
|
|
17
|
+
name: string;
|
|
18
|
+
displayName: string;
|
|
19
|
+
isAvailable(): Promise<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Translate resolved AIR artifacts into a Pi session config.
|
|
22
|
+
*
|
|
23
|
+
* Skills only: plugin-declared skills are merged into the root's default
|
|
24
|
+
* skills (additive), since AIR plugins are composition sugar. A plugin's
|
|
25
|
+
* MCP servers and hooks are intentionally ignored — Pi is skills-only.
|
|
26
|
+
*/
|
|
27
|
+
generateConfig(artifacts: ResolvedArtifacts, root?: RootEntry, _workDir?: string): AgentSessionConfig;
|
|
28
|
+
buildStartCommand(config: AgentSessionConfig): StartCommand;
|
|
29
|
+
/**
|
|
30
|
+
* Prepare a working directory for a Pi session.
|
|
31
|
+
*
|
|
32
|
+
* Injects activated skills + their references into `.pi/skills/<name>/`,
|
|
33
|
+
* reconciling against the per-target manifest so re-runs and `air clean`
|
|
34
|
+
* are idempotent. No config file is written: Pi auto-discovers `.pi/skills/`
|
|
35
|
+
* by walking the filesystem, so there is nothing for AIR's JSON transform
|
|
36
|
+
* pipeline to post-process — `configFiles` is returned empty.
|
|
37
|
+
*
|
|
38
|
+
* Inputs to the skill activation list (root defaults, overrides, subagent
|
|
39
|
+
* roots, plugin-declared skills) are accepted as either qualified (`@scope/id`)
|
|
40
|
+
* or short form; ambiguous short forms are rejected. Filesystem
|
|
41
|
+
* materialization uses shortnames — `.pi/skills/` and the manifest are
|
|
42
|
+
* scope-naive. Two activated qualified IDs that share a shortname hard-fail
|
|
43
|
+
* with a clear "add one to exclude" message.
|
|
44
|
+
*
|
|
45
|
+
* MCP servers, hooks, and standalone references are intentionally NOT
|
|
46
|
+
* translated: Pi is skills-only. The manifest records skills only
|
|
47
|
+
* (`hooks: []`, `mcpServers: []`).
|
|
48
|
+
*/
|
|
49
|
+
prepareSession(artifacts: ResolvedArtifacts, targetDir: string, options?: PrepareSessionOptions): Promise<PreparedSession>;
|
|
50
|
+
/**
|
|
51
|
+
* Enumerate skills checked into `<targetDir>/.pi/skills/`. Pi loads these
|
|
52
|
+
* directly from the filesystem regardless of AIR's involvement, so they're
|
|
53
|
+
* always active and must not be overwritten or removed. The TUI uses this
|
|
54
|
+
* list to surface them as read-only entries.
|
|
55
|
+
*/
|
|
56
|
+
listLocalArtifacts(targetDir: string): Promise<LocalArtifacts>;
|
|
57
|
+
/**
|
|
58
|
+
* Remove every skill AIR has previously written into `targetDir`.
|
|
59
|
+
*
|
|
60
|
+
* Reads the per-target manifest and deletes each tracked skill directory
|
|
61
|
+
* under `.pi/skills/`. Pi is skills-only, so there are no hooks, MCP servers,
|
|
62
|
+
* or settings files to prune. When skills are cleaned (the only category that
|
|
63
|
+
* can hold entries), the manifest itself is deleted; a partial clean
|
|
64
|
+
* (`keepSkills`) updates the manifest with the kept entries instead.
|
|
65
|
+
*
|
|
66
|
+
* Items in the manifest that no longer exist on disk are silently skipped —
|
|
67
|
+
* the manifest can drift if a user removed files manually between runs.
|
|
68
|
+
*/
|
|
69
|
+
cleanSession(targetDir: string, options?: CleanSessionOptions): Promise<CleanSessionResult>;
|
|
70
|
+
/**
|
|
71
|
+
* Resolve subagent roots from the root's default_subagent_roots.
|
|
72
|
+
* IDs are already qualified after composition-time canonicalization.
|
|
73
|
+
*/
|
|
74
|
+
private resolveSubagentRoots;
|
|
75
|
+
/**
|
|
76
|
+
* Merge subagent roots' default_skills into the parent's activated skills
|
|
77
|
+
* (union, preserving order with parent first). MCP servers are not merged —
|
|
78
|
+
* Pi is skills-only.
|
|
79
|
+
*/
|
|
80
|
+
private mergeSubagentSkills;
|
|
81
|
+
/**
|
|
82
|
+
* Build a system prompt section describing the subagent root dependencies.
|
|
83
|
+
* Skills-scoped: MCP servers are not surfaced because Pi is skills-only.
|
|
84
|
+
*/
|
|
85
|
+
private buildSubagentContext;
|
|
86
|
+
/**
|
|
87
|
+
* Resolve a list of activation IDs (each qualified or short) into qualified
|
|
88
|
+
* IDs paired with shortnames suitable for filesystem materialization.
|
|
89
|
+
*
|
|
90
|
+
* Throws on:
|
|
91
|
+
* - unknown IDs (after attempting both qualified and short-form lookup)
|
|
92
|
+
* - ambiguous short references (multiple scopes contribute the shortname)
|
|
93
|
+
* - shortname collisions in the activation set itself (two qualified IDs
|
|
94
|
+
* with the same shortname can't share a single materialization dir)
|
|
95
|
+
*/
|
|
96
|
+
private resolveActivations;
|
|
97
|
+
/**
|
|
98
|
+
* Build the warning emitted when a registered skill's `path` does not exist
|
|
99
|
+
* on disk at materialization time. The qualified ID encodes the declaring
|
|
100
|
+
* catalog's scope, so a reviewer can trace the offending entry back to its
|
|
101
|
+
* index file. Materialization is skipped for this skill and the rest of the
|
|
102
|
+
* session proceeds.
|
|
103
|
+
*/
|
|
104
|
+
private missingSourceDirMessage;
|
|
105
|
+
private formatPoolKeys;
|
|
106
|
+
/**
|
|
107
|
+
* Copy referenced documents into a references/ subdirectory of the skill.
|
|
108
|
+
* `refIds` are qualified IDs (post-canonicalization). Pi treats the skill
|
|
109
|
+
* directory as a self-contained root, so bundled references travel with it.
|
|
110
|
+
*/
|
|
111
|
+
private copyReferences;
|
|
112
|
+
private copyDirRecursive;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=pi-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pi-adapter.d.ts","sourceRoot":"","sources":["../src/pi-adapter.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EACjB,SAAS,EAET,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EAEf,MAAM,oBAAoB,CAAC;AAsB5B;;;;;;;;;;;;;GAaG;AACH,qBAAa,SAAU,YAAW,YAAY;IAC5C,IAAI,SAAQ;IACZ,WAAW,SAAQ;IAEb,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IASrC;;;;;;OAMG;IACH,cAAc,CACZ,SAAS,EAAE,iBAAiB,EAC5B,IAAI,CAAC,EAAE,SAAS,EAChB,QAAQ,CAAC,EAAE,MAAM,GAChB,kBAAkB;IA+BrB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY;IAY3D;;;;;;;;;;;;;;;;;;;OAmBG;IACG,cAAc,CAClB,SAAS,EAAE,iBAAiB,EAC5B,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,eAAe,CAAC;IAyH3B;;;;;OAKG;IACG,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAIpE;;;;;;;;;;;OAWG;IACG,YAAY,CAChB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IAsE9B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAkB5B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAa3B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAyB5B;;;;;;;;;OASG;IACH,OAAO,CAAC,kBAAkB;IAgD1B;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB;IAY/B,OAAO,CAAC,cAAc;IAStB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,gBAAgB;CAYzB"}
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, copyFileSync, rmSync, statSync, } from "fs";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { buildManifest, deleteManifest, diffManifest, getManifestPath, loadManifest, writeManifest, parseQualifiedId, resolveReference, } from "@pulsemcp/air-core";
|
|
5
|
+
import { scanLocalSkills } from "./scan-local-skills.js";
|
|
6
|
+
/**
|
|
7
|
+
* AIR adapter for the Pi coding agent (`pi`, https://www.npmjs.com/package/@earendil-works/pi-coding-agent).
|
|
8
|
+
*
|
|
9
|
+
* Scope: SKILLS ONLY. Pi does not ship with pre-baked MCP servers, hooks,
|
|
10
|
+
* references, or plugins the way a fuller agent runtime might, so this adapter
|
|
11
|
+
* translates only skills. MCP servers, hooks, and standalone references are
|
|
12
|
+
* intentionally not translated — see `generateConfig` and `prepareSession`.
|
|
13
|
+
*
|
|
14
|
+
* Pi auto-discovers project skills from `<cwd>/.pi/skills/`: any directory
|
|
15
|
+
* containing a `SKILL.md` is treated as a skill root (Pi stops recursing into
|
|
16
|
+
* it, so bundled reference files are safe). This adapter materializes activated
|
|
17
|
+
* skills into `.pi/skills/<name>/` — purely filesystem placement, no config
|
|
18
|
+
* file is written or required for Pi to load them.
|
|
19
|
+
*/
|
|
20
|
+
export class PiAdapter {
|
|
21
|
+
name = "pi";
|
|
22
|
+
displayName = "Pi";
|
|
23
|
+
async isAvailable() {
|
|
24
|
+
try {
|
|
25
|
+
execSync("which pi", { stdio: "pipe" });
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Translate resolved AIR artifacts into a Pi session config.
|
|
34
|
+
*
|
|
35
|
+
* Skills only: plugin-declared skills are merged into the root's default
|
|
36
|
+
* skills (additive), since AIR plugins are composition sugar. A plugin's
|
|
37
|
+
* MCP servers and hooks are intentionally ignored — Pi is skills-only.
|
|
38
|
+
*/
|
|
39
|
+
generateConfig(artifacts, root, _workDir) {
|
|
40
|
+
const pluginActivations = root?.default_plugins
|
|
41
|
+
? this.resolveActivations(artifacts.plugins, root.default_plugins, "plugin")
|
|
42
|
+
: [];
|
|
43
|
+
const plugins = {};
|
|
44
|
+
for (const a of pluginActivations)
|
|
45
|
+
plugins[a.qualified] = artifacts.plugins[a.qualified];
|
|
46
|
+
// Merge plugin-declared skills into root defaults (additive). All incoming
|
|
47
|
+
// IDs are qualified (post-canonicalization at composition time), so we
|
|
48
|
+
// deduplicate on qualified IDs.
|
|
49
|
+
const skillQualSet = new Set(root?.default_skills ?? []);
|
|
50
|
+
for (const plugin of Object.values(plugins)) {
|
|
51
|
+
if (plugin.skills) {
|
|
52
|
+
for (const id of plugin.skills)
|
|
53
|
+
skillQualSet.add(id);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const skillActivations = this.resolveActivations(artifacts.skills, [...skillQualSet], "skill");
|
|
57
|
+
const skillPaths = skillActivations.map((a) => artifacts.skills[a.qualified].path);
|
|
58
|
+
return {
|
|
59
|
+
agent: "pi",
|
|
60
|
+
skillPaths,
|
|
61
|
+
env: {},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
buildStartCommand(config) {
|
|
65
|
+
// Pi auto-discovers `.pi/skills/` relative to its working directory.
|
|
66
|
+
// Pointing the working directory at the prepared directory loads the
|
|
67
|
+
// injected skills; no extra flags are required.
|
|
68
|
+
return {
|
|
69
|
+
command: "pi",
|
|
70
|
+
args: [],
|
|
71
|
+
env: config.env,
|
|
72
|
+
cwd: config.workDir,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Prepare a working directory for a Pi session.
|
|
77
|
+
*
|
|
78
|
+
* Injects activated skills + their references into `.pi/skills/<name>/`,
|
|
79
|
+
* reconciling against the per-target manifest so re-runs and `air clean`
|
|
80
|
+
* are idempotent. No config file is written: Pi auto-discovers `.pi/skills/`
|
|
81
|
+
* by walking the filesystem, so there is nothing for AIR's JSON transform
|
|
82
|
+
* pipeline to post-process — `configFiles` is returned empty.
|
|
83
|
+
*
|
|
84
|
+
* Inputs to the skill activation list (root defaults, overrides, subagent
|
|
85
|
+
* roots, plugin-declared skills) are accepted as either qualified (`@scope/id`)
|
|
86
|
+
* or short form; ambiguous short forms are rejected. Filesystem
|
|
87
|
+
* materialization uses shortnames — `.pi/skills/` and the manifest are
|
|
88
|
+
* scope-naive. Two activated qualified IDs that share a shortname hard-fail
|
|
89
|
+
* with a clear "add one to exclude" message.
|
|
90
|
+
*
|
|
91
|
+
* MCP servers, hooks, and standalone references are intentionally NOT
|
|
92
|
+
* translated: Pi is skills-only. The manifest records skills only
|
|
93
|
+
* (`hooks: []`, `mcpServers: []`).
|
|
94
|
+
*/
|
|
95
|
+
async prepareSession(artifacts, targetDir, options) {
|
|
96
|
+
const root = options?.root;
|
|
97
|
+
const skillPaths = [];
|
|
98
|
+
const prevManifest = loadManifest(targetDir);
|
|
99
|
+
// 1. Resolve which skills to activate (overrides take precedence over root defaults).
|
|
100
|
+
let skillIds = options?.skillOverrides ?? root?.default_skills ?? [];
|
|
101
|
+
// 1b. Merge subagent roots' skills if applicable.
|
|
102
|
+
const subagentRoots = this.resolveSubagentRoots(root, artifacts, options);
|
|
103
|
+
if (subagentRoots.length > 0 && !options?.skillOverrides) {
|
|
104
|
+
skillIds = this.mergeSubagentSkills(subagentRoots, skillIds);
|
|
105
|
+
}
|
|
106
|
+
// 1c. Resolve plugins and merge their declared skills (additive). A plugin's
|
|
107
|
+
// MCP servers and hooks are intentionally ignored — Pi is skills-only.
|
|
108
|
+
const pluginIds = options?.pluginOverrides ?? root?.default_plugins ?? undefined;
|
|
109
|
+
const pluginActivations = pluginIds?.length
|
|
110
|
+
? this.resolveActivations(artifacts.plugins, pluginIds, "plugin")
|
|
111
|
+
: [];
|
|
112
|
+
const skillSet = new Set(skillIds);
|
|
113
|
+
for (const a of pluginActivations) {
|
|
114
|
+
const plugin = artifacts.plugins[a.qualified];
|
|
115
|
+
if (plugin.skills)
|
|
116
|
+
for (const id of plugin.skills)
|
|
117
|
+
skillSet.add(id);
|
|
118
|
+
}
|
|
119
|
+
skillIds = [...skillSet];
|
|
120
|
+
// 2. Resolve activations: qualified ID + shortname per skill.
|
|
121
|
+
// Throws on unknown IDs, ambiguous shortnames, and shortname collisions.
|
|
122
|
+
const skillActs = this.resolveActivations(artifacts.skills, skillIds, "skill");
|
|
123
|
+
const skillShortIds = skillActs.map((a) => a.short);
|
|
124
|
+
// 3. Reconcile against prior manifest using shortnames — those are the keys
|
|
125
|
+
// used for filesystem materialization and stored in the manifest.
|
|
126
|
+
const diff = diffManifest(prevManifest, {
|
|
127
|
+
skills: skillShortIds,
|
|
128
|
+
hooks: [],
|
|
129
|
+
mcpServers: [],
|
|
130
|
+
});
|
|
131
|
+
for (const staleSkillId of diff.staleSkills) {
|
|
132
|
+
const staleDir = join(targetDir, ".pi", "skills", staleSkillId);
|
|
133
|
+
if (existsSync(staleDir)) {
|
|
134
|
+
rmSync(staleDir, { recursive: true, force: true });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// 4. Inject skills + references into .pi/skills/<short>/.
|
|
138
|
+
const materializedSkillShortIds = [];
|
|
139
|
+
for (const a of skillActs) {
|
|
140
|
+
const skill = artifacts.skills[a.qualified];
|
|
141
|
+
const skillTargetDir = join(targetDir, ".pi", "skills", a.short);
|
|
142
|
+
if (existsSync(skillTargetDir)) {
|
|
143
|
+
materializedSkillShortIds.push(a.short);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const skillSourceDir = skill.path;
|
|
147
|
+
if (!existsSync(skillSourceDir)) {
|
|
148
|
+
console.warn(this.missingSourceDirMessage("skill", a.qualified, skillSourceDir));
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
this.copyDirRecursive(skillSourceDir, skillTargetDir);
|
|
152
|
+
skillPaths.push(skillTargetDir);
|
|
153
|
+
materializedSkillShortIds.push(a.short);
|
|
154
|
+
if (skill.references && skill.references.length > 0) {
|
|
155
|
+
this.copyReferences(skill.references, skillTargetDir, artifacts);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// 5. Persist the updated manifest (shortnames — keyed by filesystem dir).
|
|
159
|
+
// Only record skills that were actually materialized so the manifest
|
|
160
|
+
// does not claim ownership of artifacts AIR skipped (e.g. a missing
|
|
161
|
+
// source dir). Hooks and MCP servers are always empty for Pi.
|
|
162
|
+
writeManifest(buildManifest(targetDir, {
|
|
163
|
+
adapter: this.name,
|
|
164
|
+
skills: materializedSkillShortIds,
|
|
165
|
+
hooks: [],
|
|
166
|
+
mcpServers: [],
|
|
167
|
+
}));
|
|
168
|
+
// 6. Generate ephemeral subagent context for the session. Pi loads skills
|
|
169
|
+
// from the filesystem and has no AIR-driven system-prompt flag wired by
|
|
170
|
+
// this adapter, so subagent context is surfaced to the caller via
|
|
171
|
+
// `subagentContext` rather than the start command.
|
|
172
|
+
let subagentContext;
|
|
173
|
+
if (subagentRoots.length > 0) {
|
|
174
|
+
subagentContext = this.buildSubagentContext(subagentRoots);
|
|
175
|
+
}
|
|
176
|
+
// 7. Build start command (working directory = prepared directory).
|
|
177
|
+
// `root` is passed as `undefined` here on purpose: skill activation
|
|
178
|
+
// (root defaults + overrides + subagent + plugin skills) was already
|
|
179
|
+
// resolved and materialized above, so we only need `generateConfig` for
|
|
180
|
+
// the agent name + env that `buildStartCommand` consumes. Passing `root`
|
|
181
|
+
// would redundantly re-resolve skill paths from defaults alone, ignoring
|
|
182
|
+
// the overrides applied in this call.
|
|
183
|
+
const config = this.generateConfig(artifacts, undefined, targetDir);
|
|
184
|
+
const startCommand = this.buildStartCommand({
|
|
185
|
+
...config,
|
|
186
|
+
workDir: targetDir,
|
|
187
|
+
});
|
|
188
|
+
return {
|
|
189
|
+
// Empty by design — Pi discovers `.pi/skills/` from the filesystem, so
|
|
190
|
+
// there is no JSON config file for AIR's transform pipeline to process.
|
|
191
|
+
configFiles: [],
|
|
192
|
+
skillPaths,
|
|
193
|
+
// Pi is skills-only: no hooks are ever materialized.
|
|
194
|
+
hookPaths: [],
|
|
195
|
+
startCommand,
|
|
196
|
+
subagentContext,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Enumerate skills checked into `<targetDir>/.pi/skills/`. Pi loads these
|
|
201
|
+
* directly from the filesystem regardless of AIR's involvement, so they're
|
|
202
|
+
* always active and must not be overwritten or removed. The TUI uses this
|
|
203
|
+
* list to surface them as read-only entries.
|
|
204
|
+
*/
|
|
205
|
+
async listLocalArtifacts(targetDir) {
|
|
206
|
+
return { skills: scanLocalSkills(targetDir) };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Remove every skill AIR has previously written into `targetDir`.
|
|
210
|
+
*
|
|
211
|
+
* Reads the per-target manifest and deletes each tracked skill directory
|
|
212
|
+
* under `.pi/skills/`. Pi is skills-only, so there are no hooks, MCP servers,
|
|
213
|
+
* or settings files to prune. When skills are cleaned (the only category that
|
|
214
|
+
* can hold entries), the manifest itself is deleted; a partial clean
|
|
215
|
+
* (`keepSkills`) updates the manifest with the kept entries instead.
|
|
216
|
+
*
|
|
217
|
+
* Items in the manifest that no longer exist on disk are silently skipped —
|
|
218
|
+
* the manifest can drift if a user removed files manually between runs.
|
|
219
|
+
*/
|
|
220
|
+
async cleanSession(targetDir, options) {
|
|
221
|
+
const dryRun = options?.dryRun ?? false;
|
|
222
|
+
const cleanSkills = !(options?.keepSkills ?? false);
|
|
223
|
+
const cleanHooks = !(options?.keepHooks ?? false);
|
|
224
|
+
const cleanMcpServers = !(options?.keepMcpServers ?? false);
|
|
225
|
+
// Pi only ever tracks skills, but honor the same "full clean" predicate as
|
|
226
|
+
// other adapters so a caller that keeps any category preserves the manifest.
|
|
227
|
+
const fullClean = cleanSkills && cleanHooks && cleanMcpServers;
|
|
228
|
+
const manifestPath = getManifestPath(targetDir);
|
|
229
|
+
const manifestFileExists = existsSync(manifestPath);
|
|
230
|
+
const manifest = loadManifest(targetDir);
|
|
231
|
+
if (!manifest) {
|
|
232
|
+
let corruptManifestRemoved = false;
|
|
233
|
+
if (manifestFileExists && fullClean && !dryRun) {
|
|
234
|
+
corruptManifestRemoved = deleteManifest(targetDir);
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
removedSkills: [],
|
|
238
|
+
removedHooks: [],
|
|
239
|
+
removedMcpServers: [],
|
|
240
|
+
mcpConfigPath: null,
|
|
241
|
+
settingsPath: null,
|
|
242
|
+
manifestPath,
|
|
243
|
+
manifestExisted: manifestFileExists,
|
|
244
|
+
manifestRemoved: corruptManifestRemoved,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const removedSkills = [];
|
|
248
|
+
if (cleanSkills) {
|
|
249
|
+
for (const id of manifest.skills) {
|
|
250
|
+
const dir = join(targetDir, ".pi", "skills", id);
|
|
251
|
+
if (!existsSync(dir))
|
|
252
|
+
continue;
|
|
253
|
+
if (!dryRun)
|
|
254
|
+
rmSync(dir, { recursive: true, force: true });
|
|
255
|
+
removedSkills.push(id);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
let manifestRemoved = false;
|
|
259
|
+
if (fullClean) {
|
|
260
|
+
if (!dryRun) {
|
|
261
|
+
manifestRemoved = deleteManifest(targetDir);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
manifestRemoved = manifestFileExists;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
else if (!dryRun) {
|
|
268
|
+
writeManifest(buildManifest(targetDir, {
|
|
269
|
+
adapter: manifest.adapter ?? this.name,
|
|
270
|
+
skills: cleanSkills ? [] : manifest.skills,
|
|
271
|
+
hooks: [],
|
|
272
|
+
mcpServers: [],
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
removedSkills,
|
|
277
|
+
// Pi is skills-only: hooks and MCP servers are never materialized.
|
|
278
|
+
removedHooks: [],
|
|
279
|
+
removedMcpServers: [],
|
|
280
|
+
mcpConfigPath: null,
|
|
281
|
+
settingsPath: null,
|
|
282
|
+
manifestPath,
|
|
283
|
+
manifestExisted: true,
|
|
284
|
+
manifestRemoved,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Resolve subagent roots from the root's default_subagent_roots.
|
|
289
|
+
* IDs are already qualified after composition-time canonicalization.
|
|
290
|
+
*/
|
|
291
|
+
resolveSubagentRoots(root, artifacts, options) {
|
|
292
|
+
if (options?.skipSubagentMerge)
|
|
293
|
+
return [];
|
|
294
|
+
if (!root?.default_subagent_roots?.length)
|
|
295
|
+
return [];
|
|
296
|
+
const resolved = [];
|
|
297
|
+
for (const id of root.default_subagent_roots) {
|
|
298
|
+
const res = resolveReference(artifacts.roots, id, undefined);
|
|
299
|
+
if (res.status === "ok") {
|
|
300
|
+
resolved.push(artifacts.roots[res.qualified]);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return resolved;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Merge subagent roots' default_skills into the parent's activated skills
|
|
307
|
+
* (union, preserving order with parent first). MCP servers are not merged —
|
|
308
|
+
* Pi is skills-only.
|
|
309
|
+
*/
|
|
310
|
+
mergeSubagentSkills(subagentRoots, parentSkillIds) {
|
|
311
|
+
const skillSet = new Set(parentSkillIds);
|
|
312
|
+
for (const sub of subagentRoots) {
|
|
313
|
+
if (sub.default_skills) {
|
|
314
|
+
for (const id of sub.default_skills)
|
|
315
|
+
skillSet.add(id);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return [...skillSet];
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Build a system prompt section describing the subagent root dependencies.
|
|
322
|
+
* Skills-scoped: MCP servers are not surfaced because Pi is skills-only.
|
|
323
|
+
*/
|
|
324
|
+
buildSubagentContext(subagentRoots) {
|
|
325
|
+
const lines = [
|
|
326
|
+
"## Subagent Root Dependencies",
|
|
327
|
+
"",
|
|
328
|
+
"This session includes capabilities from the following subagent roots.",
|
|
329
|
+
"Their skills have been merged into your session.",
|
|
330
|
+
"",
|
|
331
|
+
];
|
|
332
|
+
for (const sub of subagentRoots) {
|
|
333
|
+
lines.push(`### ${sub.display_name || "Subagent"}`);
|
|
334
|
+
lines.push("");
|
|
335
|
+
lines.push(`**Description**: ${sub.description}`);
|
|
336
|
+
if (sub.default_skills?.length) {
|
|
337
|
+
lines.push(`**Skills**: ${sub.default_skills.join(", ")}`);
|
|
338
|
+
}
|
|
339
|
+
if (sub.subdirectory) {
|
|
340
|
+
lines.push(`**Subdirectory**: ${sub.subdirectory}`);
|
|
341
|
+
}
|
|
342
|
+
lines.push("");
|
|
343
|
+
}
|
|
344
|
+
return lines.join("\n");
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Resolve a list of activation IDs (each qualified or short) into qualified
|
|
348
|
+
* IDs paired with shortnames suitable for filesystem materialization.
|
|
349
|
+
*
|
|
350
|
+
* Throws on:
|
|
351
|
+
* - unknown IDs (after attempting both qualified and short-form lookup)
|
|
352
|
+
* - ambiguous short references (multiple scopes contribute the shortname)
|
|
353
|
+
* - shortname collisions in the activation set itself (two qualified IDs
|
|
354
|
+
* with the same shortname can't share a single materialization dir)
|
|
355
|
+
*/
|
|
356
|
+
resolveActivations(pool, ids, artifactType) {
|
|
357
|
+
const acts = [];
|
|
358
|
+
const errors = [];
|
|
359
|
+
const shortToQualified = new Map();
|
|
360
|
+
for (const id of ids) {
|
|
361
|
+
const res = resolveReference(pool, id, undefined);
|
|
362
|
+
if (res.status === "missing") {
|
|
363
|
+
errors.push(`Unknown ${artifactType} ID "${id}". Available: ${this.formatPoolKeys(pool)}.`);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (res.status === "ambiguous") {
|
|
367
|
+
errors.push(`${artifactType} reference "${id}" is ambiguous — candidates: ` +
|
|
368
|
+
`${res.candidates.join(", ")}. Use the qualified form to disambiguate.`);
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const qualified = res.qualified;
|
|
372
|
+
const { id: short } = parseQualifiedId(qualified);
|
|
373
|
+
const prior = shortToQualified.get(short);
|
|
374
|
+
if (prior !== undefined && prior !== qualified) {
|
|
375
|
+
errors.push(`${artifactType} shortname collision: both "${prior}" and "${qualified}" ` +
|
|
376
|
+
`are activated and would write to the same target name "${short}". ` +
|
|
377
|
+
`Add one to air.json#exclude or activate only one of them.`);
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (prior === qualified)
|
|
381
|
+
continue; // dedup
|
|
382
|
+
shortToQualified.set(short, qualified);
|
|
383
|
+
acts.push({ qualified, short });
|
|
384
|
+
}
|
|
385
|
+
if (errors.length > 0) {
|
|
386
|
+
throw new Error(errors.length === 1 ? errors[0] : `Activation errors:\n - ${errors.join("\n - ")}`);
|
|
387
|
+
}
|
|
388
|
+
return acts;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Build the warning emitted when a registered skill's `path` does not exist
|
|
392
|
+
* on disk at materialization time. The qualified ID encodes the declaring
|
|
393
|
+
* catalog's scope, so a reviewer can trace the offending entry back to its
|
|
394
|
+
* index file. Materialization is skipped for this skill and the rest of the
|
|
395
|
+
* session proceeds.
|
|
396
|
+
*/
|
|
397
|
+
missingSourceDirMessage(artifactType, qualified, resolvedPath) {
|
|
398
|
+
return (`warning: ${artifactType} "${qualified}" declares path "${resolvedPath}" but that directory does not exist — skipping. ` +
|
|
399
|
+
`The catalog that contributed "${qualified}" registered a path AIR cannot materialize. ` +
|
|
400
|
+
`Fix the \`path\` field in the catalog's index file (or exclude the artifact in air.json) to restore the ${artifactType}.`);
|
|
401
|
+
}
|
|
402
|
+
formatPoolKeys(pool) {
|
|
403
|
+
const keys = Object.keys(pool);
|
|
404
|
+
if (keys.length === 0)
|
|
405
|
+
return "(none)";
|
|
406
|
+
if (keys.length > 8) {
|
|
407
|
+
return `${keys.slice(0, 8).join(", ")}, … (${keys.length} total)`;
|
|
408
|
+
}
|
|
409
|
+
return keys.join(", ");
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Copy referenced documents into a references/ subdirectory of the skill.
|
|
413
|
+
* `refIds` are qualified IDs (post-canonicalization). Pi treats the skill
|
|
414
|
+
* directory as a self-contained root, so bundled references travel with it.
|
|
415
|
+
*/
|
|
416
|
+
copyReferences(refIds, targetDir, artifacts) {
|
|
417
|
+
const refsTargetDir = join(targetDir, "references");
|
|
418
|
+
for (const refId of refIds) {
|
|
419
|
+
const ref = artifacts.references[refId];
|
|
420
|
+
if (!ref)
|
|
421
|
+
continue;
|
|
422
|
+
const refSourcePath = ref.path;
|
|
423
|
+
if (existsSync(refSourcePath)) {
|
|
424
|
+
const refTargetPath = join(refsTargetDir, ref.path.split("/").pop() || ref.path);
|
|
425
|
+
mkdirSync(dirname(refTargetPath), { recursive: true });
|
|
426
|
+
copyFileSync(refSourcePath, refTargetPath);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
copyDirRecursive(src, dest) {
|
|
431
|
+
mkdirSync(dest, { recursive: true });
|
|
432
|
+
for (const entry of readdirSync(src)) {
|
|
433
|
+
const srcPath = join(src, entry);
|
|
434
|
+
const destPath = join(dest, entry);
|
|
435
|
+
if (statSync(srcPath).isDirectory()) {
|
|
436
|
+
this.copyDirRecursive(srcPath, destPath);
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
copyFileSync(srcPath, destPath);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
//# sourceMappingURL=pi-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pi-adapter.js","sourceRoot":"","sources":["../src/pi-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { LocalSkillEntry } from "@pulsemcp/air-core";
|
|
2
|
+
/**
|
|
3
|
+
* Scan `<targetDir>/.pi/skills/` for user-managed skills and return one entry
|
|
4
|
+
* per directory containing a `SKILL.md`. Pi auto-discovers project skills from
|
|
5
|
+
* `.pi/skills/` (a directory containing `SKILL.md` is treated as a skill root),
|
|
6
|
+
* so these are always active regardless of AIR's involvement — the TUI surfaces
|
|
7
|
+
* them as read-only entries. Missing or unreadable directories yield an empty
|
|
8
|
+
* list — this is a best-effort informational scan, not validation.
|
|
9
|
+
*/
|
|
10
|
+
export declare function scanLocalSkills(targetDir: string): LocalSkillEntry[];
|
|
11
|
+
//# sourceMappingURL=scan-local-skills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan-local-skills.d.ts","sourceRoot":"","sources":["../src/scan-local-skills.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,EAAE,CA2CpE"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Scan `<targetDir>/.pi/skills/` for user-managed skills and return one entry
|
|
5
|
+
* per directory containing a `SKILL.md`. Pi auto-discovers project skills from
|
|
6
|
+
* `.pi/skills/` (a directory containing `SKILL.md` is treated as a skill root),
|
|
7
|
+
* so these are always active regardless of AIR's involvement — the TUI surfaces
|
|
8
|
+
* them as read-only entries. Missing or unreadable directories yield an empty
|
|
9
|
+
* list — this is a best-effort informational scan, not validation.
|
|
10
|
+
*/
|
|
11
|
+
export function scanLocalSkills(targetDir) {
|
|
12
|
+
const skillsDir = join(targetDir, ".pi", "skills");
|
|
13
|
+
if (!existsSync(skillsDir))
|
|
14
|
+
return [];
|
|
15
|
+
let entries;
|
|
16
|
+
try {
|
|
17
|
+
entries = readdirSync(skillsDir);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
const skills = [];
|
|
23
|
+
for (const name of entries) {
|
|
24
|
+
if (name.startsWith("."))
|
|
25
|
+
continue;
|
|
26
|
+
const skillDir = join(skillsDir, name);
|
|
27
|
+
let isDir = false;
|
|
28
|
+
try {
|
|
29
|
+
isDir = statSync(skillDir).isDirectory();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (!isDir)
|
|
35
|
+
continue;
|
|
36
|
+
const skillMdPath = join(skillDir, "SKILL.md");
|
|
37
|
+
if (!existsSync(skillMdPath))
|
|
38
|
+
continue;
|
|
39
|
+
const frontmatter = readFrontmatter(skillMdPath);
|
|
40
|
+
const description = pickString(frontmatter, "description") ?? "(local skill — no description)";
|
|
41
|
+
const title = pickString(frontmatter, "title") ?? pickString(frontmatter, "name");
|
|
42
|
+
skills.push({
|
|
43
|
+
id: name,
|
|
44
|
+
description,
|
|
45
|
+
title,
|
|
46
|
+
path: skillDir,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
skills.sort((a, b) => a.id.localeCompare(b.id));
|
|
50
|
+
return skills;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Minimal YAML frontmatter reader — parses a leading block delimited by
|
|
54
|
+
* `---` lines into a flat key/value map. Only handles top-level
|
|
55
|
+
* `key: value` scalars, which is all SKILL.md frontmatter needs in
|
|
56
|
+
* practice. Unquoted values have surrounding whitespace trimmed and
|
|
57
|
+
* matching single/double quotes stripped.
|
|
58
|
+
*/
|
|
59
|
+
function readFrontmatter(path) {
|
|
60
|
+
let content;
|
|
61
|
+
try {
|
|
62
|
+
content = readFileSync(path, "utf-8");
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
const lines = content.split(/\r?\n/);
|
|
68
|
+
if (lines[0]?.trim() !== "---")
|
|
69
|
+
return {};
|
|
70
|
+
const result = {};
|
|
71
|
+
for (let i = 1; i < lines.length; i++) {
|
|
72
|
+
const line = lines[i];
|
|
73
|
+
if (line.trim() === "---")
|
|
74
|
+
return result;
|
|
75
|
+
const match = line.match(/^([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$/);
|
|
76
|
+
if (!match)
|
|
77
|
+
continue;
|
|
78
|
+
const key = match[1];
|
|
79
|
+
let value = match[2].trim();
|
|
80
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
81
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
82
|
+
value = value.slice(1, -1);
|
|
83
|
+
}
|
|
84
|
+
result[key] = value;
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
function pickString(obj, key) {
|
|
89
|
+
const v = obj[key];
|
|
90
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=scan-local-skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan-local-skills.js","sourceRoot":"","sources":["../src/scan-local-skills.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pulsemcp/air-adapter-pi",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/pulsemcp/air.git",
|
|
10
|
+
"directory": "packages/extensions/adapter-pi"
|
|
11
|
+
},
|
|
12
|
+
"description": "AIR adapter for the Pi coding agent — injects AIR skills into Pi's native skills location",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist/"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"lint": "tsc --noEmit"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@pulsemcp/air-core": "0.7.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.10.0",
|
|
36
|
+
"typescript": "^5.7.0",
|
|
37
|
+
"vitest": "^2.1.0"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
}
|
|
42
|
+
}
|