pi-agents-switch 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/frontmatter.ts +0 -1
- package/index.ts +7 -3
- package/package.json +1 -1
- package/profile-manager.ts +69 -38
- package/types.ts +2 -2
package/README.md
CHANGED
|
@@ -12,6 +12,10 @@ pi install ~/project/agents-switch
|
|
|
12
12
|
|
|
13
13
|
Then `/reload` in pi.
|
|
14
14
|
|
|
15
|
+
> **Requires Pi ≥ v0.x** (tested with the version shipping `before_agent_start` + `before_provider_request` events).
|
|
16
|
+
> If Pi's system prompt format changes, agent identity will be prepended instead of replacing the default role line —
|
|
17
|
+
> check the Pi console for a `[agents-switch]` warning if this happens.
|
|
18
|
+
|
|
15
19
|
## Usage
|
|
16
20
|
|
|
17
21
|
| Action | How |
|
|
@@ -53,8 +57,7 @@ The Builder will design the agent, write the AGENTS.md, and set everything up.
|
|
|
53
57
|
```
|
|
54
58
|
~/.pi/agents/<name>/
|
|
55
59
|
├── AGENTS.md ← YAML frontmatter + system prompt (all-in-one)
|
|
56
|
-
|
|
57
|
-
└── prompts/ ← agent-specific prompt templates (optional)
|
|
60
|
+
└── skills/ ← agent-specific skills (auto-loaded, optional)
|
|
58
61
|
```
|
|
59
62
|
|
|
60
63
|
When you switch to an agent, its `AGENTS.md` body is injected into the system prompt. You can also configure model, thinking level, and tools per agent via the frontmatter.
|
package/frontmatter.ts
CHANGED
|
@@ -145,7 +145,6 @@ function parseScalar(value: string): string | boolean | number {
|
|
|
145
145
|
|
|
146
146
|
/**
|
|
147
147
|
* Serialize frontmatter back to a YAML block (for creating agents).
|
|
148
|
-
* Uses the preferred key names: `excluded_tools`, `excluded_extensions`, `excluded_skills`.
|
|
149
148
|
*/
|
|
150
149
|
export function serializeFrontmatter(fm: AgentFrontmatter): string {
|
|
151
150
|
const lines: string[] = ["---"];
|
package/index.ts
CHANGED
|
@@ -140,9 +140,7 @@ If you want to exclude just a few items, add \`*\` to the include list and use t
|
|
|
140
140
|
|
|
141
141
|
\`\`\`
|
|
142
142
|
~/.pi/agents/<name>/
|
|
143
|
-
|
|
144
|
-
├── skills/ ← Agent-specific skills (mkdir)
|
|
145
|
-
└── prompts/ ← Agent-specific prompt templates (mkdir)
|
|
143
|
+
└── skills/ ← Agent-specific skills (mkdir, optional)
|
|
146
144
|
\`\`\`
|
|
147
145
|
|
|
148
146
|
## Workflow: How to create an agent
|
|
@@ -752,6 +750,12 @@ export default function agentsSwitch(pi: ExtensionAPI) {
|
|
|
752
750
|
section +
|
|
753
751
|
newPrompt.substring(roleEnd);
|
|
754
752
|
} else {
|
|
753
|
+
// Pi version mismatch — role line changed. Fall back to prepend.
|
|
754
|
+
console.warn(
|
|
755
|
+
"[agents-switch] Default role line not found in system prompt. " +
|
|
756
|
+
"Pi may have changed its prompt format. " +
|
|
757
|
+
"Agent identity will be prepended instead of replacing the role line.",
|
|
758
|
+
);
|
|
755
759
|
newPrompt = section + "\n\n" + newPrompt;
|
|
756
760
|
}
|
|
757
761
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-agents-switch",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Tab to switch primary agents in Pi — like OpenCode's agent switching. Each agent gets an isolated profile with its own AGENTS.md, extensions, skills, and settings.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"pi": {
|
package/profile-manager.ts
CHANGED
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
* Profile dir layout:
|
|
9
9
|
* ~/.pi/agents/<name>/ ← user-level agent
|
|
10
10
|
* ├── AGENTS.md ← YAML frontmatter + system prompt
|
|
11
|
-
*
|
|
12
|
-
* └── prompts/ ← agent-specific prompt templates
|
|
11
|
+
* └── skills/ ← agent-specific skills
|
|
13
12
|
*
|
|
14
13
|
* <cwd>/.pi/agents/<name>/ ← project-level agent (overrides user)
|
|
15
14
|
* └── AGENTS.md
|
|
@@ -478,12 +477,10 @@ export class ProfileManager {
|
|
|
478
477
|
* Look up full skill objects by name from Pi's standard skill directories.
|
|
479
478
|
* Returns name, description, and filePath for each skill found.
|
|
480
479
|
*/
|
|
481
|
-
/** Get
|
|
480
|
+
/** Get all indexed skill names (global + extension + project). */
|
|
482
481
|
getPISkillNames(): string[] {
|
|
483
482
|
if (!this.skillCache) this.rebuildSkillIndex();
|
|
484
|
-
return [...this.skillCache!.
|
|
485
|
-
.filter(([, v]) => v.source !== "agent")
|
|
486
|
-
.map(([k]) => k);
|
|
483
|
+
return [...this.skillCache!.keys()];
|
|
487
484
|
}
|
|
488
485
|
|
|
489
486
|
/**
|
|
@@ -562,17 +559,76 @@ export class ProfileManager {
|
|
|
562
559
|
if (!this.skillCache) {
|
|
563
560
|
this.rebuildSkillIndex();
|
|
564
561
|
}
|
|
565
|
-
|
|
562
|
+
const cached = this.skillCache!.get(name);
|
|
563
|
+
if (cached) return cached;
|
|
564
|
+
|
|
565
|
+
// Fallback: agent-specific skills are not indexed globally.
|
|
566
|
+
// Check each agent's skills/ dir on demand.
|
|
567
|
+
return this.findAgentSkillOnDisk(name);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Look up a single agent-specific skill by checking each agent's
|
|
572
|
+
* skills/<name>/SKILL.md path directly.
|
|
573
|
+
*/
|
|
574
|
+
private findAgentSkillOnDisk(
|
|
575
|
+
name: string,
|
|
576
|
+
): { name: string; description: string; filePath: string } | undefined {
|
|
577
|
+
const read = (
|
|
578
|
+
filePath: string,
|
|
579
|
+
): { name: string; description: string; filePath: string } | undefined => {
|
|
580
|
+
if (!existsSync(filePath)) return undefined;
|
|
581
|
+
try {
|
|
582
|
+
const content = readFileSync(filePath, "utf8");
|
|
583
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
584
|
+
return {
|
|
585
|
+
name: frontmatter.name ?? name,
|
|
586
|
+
description: frontmatter.description ?? "",
|
|
587
|
+
filePath,
|
|
588
|
+
};
|
|
589
|
+
} catch {
|
|
590
|
+
return undefined;
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
// User-level agents
|
|
595
|
+
const userDir = join(homedir(), ".pi", "agents");
|
|
596
|
+
if (existsSync(userDir)) {
|
|
597
|
+
try {
|
|
598
|
+
for (const agent of readdirSync(userDir)) {
|
|
599
|
+
const found = read(join(userDir, agent, "skills", name, "SKILL.md"));
|
|
600
|
+
if (found) return found;
|
|
601
|
+
}
|
|
602
|
+
} catch {}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Project-level agents
|
|
606
|
+
if (this.cwd) {
|
|
607
|
+
const projDir = join(this.cwd, ".pi", "agents");
|
|
608
|
+
if (existsSync(projDir)) {
|
|
609
|
+
try {
|
|
610
|
+
for (const agent of readdirSync(projDir)) {
|
|
611
|
+
const found = read(
|
|
612
|
+
join(projDir, agent, "skills", name, "SKILL.md"),
|
|
613
|
+
);
|
|
614
|
+
if (found) return found;
|
|
615
|
+
}
|
|
616
|
+
} catch {}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return undefined;
|
|
566
621
|
}
|
|
567
622
|
|
|
568
623
|
/**
|
|
569
|
-
* Rebuild the skill index by scanning
|
|
624
|
+
* Rebuild the skill index by scanning known skill directories.
|
|
625
|
+
* Agent-specific skills are NOT indexed here — they are looked up
|
|
626
|
+
* on demand via findSkillOnDisk → scanForAgentSkill().
|
|
570
627
|
*
|
|
571
628
|
* Locations scanned:
|
|
572
629
|
* 1. ~/.pi/agent/skills/<name>/SKILL.md (flat, user-installed)
|
|
573
630
|
* 2. ~/.pi/agent/npm/node_modules/<ext>/skills/ (extension skills)
|
|
574
|
-
* 3.
|
|
575
|
-
* 4. <cwd>/.pi/skills/ (project skills, flat)
|
|
631
|
+
* 3. <cwd>/.pi/skills/ (project skills, flat)
|
|
576
632
|
*/
|
|
577
633
|
rebuildSkillIndex(): void {
|
|
578
634
|
this.skillCache = new Map();
|
|
@@ -580,7 +636,6 @@ export class ProfileManager {
|
|
|
580
636
|
const scanDir = (dir: string, maxDepth: number, source: string): void => {
|
|
581
637
|
if (maxDepth <= 0) return;
|
|
582
638
|
|
|
583
|
-
// Check for SKILL.md in this directory
|
|
584
639
|
const skillMd = join(dir, "SKILL.md");
|
|
585
640
|
if (existsSync(skillMd)) {
|
|
586
641
|
try {
|
|
@@ -610,7 +665,7 @@ export class ProfileManager {
|
|
|
610
665
|
// 1. Global Pi skills
|
|
611
666
|
scanDir(join(this.piAgentDir, "skills"), 2, "global");
|
|
612
667
|
|
|
613
|
-
// 2. Extension skills from npm packages
|
|
668
|
+
// 2. Extension skills from npm packages
|
|
614
669
|
const npmDir = join(this.piAgentDir, "npm", "node_modules");
|
|
615
670
|
if (existsSync(npmDir)) {
|
|
616
671
|
for (const ext of readdirSync(npmDir)) {
|
|
@@ -620,35 +675,12 @@ export class ProfileManager {
|
|
|
620
675
|
if (statSync(extSkills).isDirectory()) {
|
|
621
676
|
scanDir(extSkills, 3, "extension");
|
|
622
677
|
}
|
|
623
|
-
} catch {
|
|
624
|
-
// Skip
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// 3. Agent-specific skills (nested: agents/<agent>/skills/<name>/SKILL.md)
|
|
631
|
-
const agentsDir = join(homedir(), ".pi", "agents");
|
|
632
|
-
if (existsSync(agentsDir)) {
|
|
633
|
-
try {
|
|
634
|
-
for (const agent of readdirSync(agentsDir)) {
|
|
635
|
-
const agentSkills = join(agentsDir, agent, "skills");
|
|
636
|
-
if (existsSync(agentSkills)) {
|
|
637
|
-
try {
|
|
638
|
-
if (statSync(agentSkills).isDirectory()) {
|
|
639
|
-
scanDir(agentSkills, 3, "agent");
|
|
640
|
-
}
|
|
641
|
-
} catch {
|
|
642
|
-
// Skip
|
|
643
|
-
}
|
|
644
|
-
}
|
|
678
|
+
} catch {}
|
|
645
679
|
}
|
|
646
|
-
} catch {
|
|
647
|
-
// Skip unreadable agents dir
|
|
648
680
|
}
|
|
649
681
|
}
|
|
650
682
|
|
|
651
|
-
//
|
|
683
|
+
// 3. Project-level skills
|
|
652
684
|
if (this.cwd) {
|
|
653
685
|
const projSkillsDir = join(this.cwd, ".pi", "skills");
|
|
654
686
|
if (existsSync(projSkillsDir)) {
|
|
@@ -685,7 +717,6 @@ export class ProfileManager {
|
|
|
685
717
|
try {
|
|
686
718
|
mkdirSync(path, { recursive: true });
|
|
687
719
|
mkdirSync(join(path, "skills"));
|
|
688
|
-
mkdirSync(join(path, "prompts"));
|
|
689
720
|
} catch (err) {
|
|
690
721
|
try {
|
|
691
722
|
rmSync(path, { recursive: true, force: true });
|
package/types.ts
CHANGED
|
@@ -62,8 +62,8 @@ export interface FrontmatterResult {
|
|
|
62
62
|
body: string;
|
|
63
63
|
/**
|
|
64
64
|
* Ordered list of top-level YAML keys as they appear in the file.
|
|
65
|
-
* Used for last-write-wins conflict resolution between `tools`/`excluded_tools
|
|
66
|
-
* `
|
|
65
|
+
* Used for last-write-wins conflict resolution between `tools`/`excluded_tools`
|
|
66
|
+
* and `skills`/`excluded_skills`.
|
|
67
67
|
*/
|
|
68
68
|
keyOrder: string[];
|
|
69
69
|
}
|