@ryan_nookpi/pi-extension-cross-agent 0.1.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 +32 -0
- package/index.ts +161 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# cross-agent
|
|
2
|
+
|
|
3
|
+
Load commands from other AI coding agent directories (`.claude/`, `.gemini/`, `.codex/`) into pi.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Scans project-local and global agent directories and registers `commands/*.md` files as `/name` commands.
|
|
8
|
+
|
|
9
|
+
| Source | Pattern | Behavior |
|
|
10
|
+
|--------|---------|----------|
|
|
11
|
+
| `commands/*.md` | Markdown with optional frontmatter | Registered as `/name` command |
|
|
12
|
+
| `skills/` | Directories with `SKILL.md` or flat `.md` files | Detected but not registered (reserved for future use) |
|
|
13
|
+
| `agents/*.md` | Markdown agent definitions | Detected but not registered (reserved for future use) |
|
|
14
|
+
| `.pi/agents/*.md` | Pi-native agent definitions | Detected but not registered (reserved for future use) |
|
|
15
|
+
|
|
16
|
+
### Scan order
|
|
17
|
+
|
|
18
|
+
For each provider (`claude`, `gemini`, `codex`):
|
|
19
|
+
1. `<cwd>/.<provider>/` (project-local)
|
|
20
|
+
2. `~/.<provider>/` (global)
|
|
21
|
+
|
|
22
|
+
First-seen command name wins on duplicates.
|
|
23
|
+
|
|
24
|
+
### Command template variables
|
|
25
|
+
|
|
26
|
+
Commands support `$ARGUMENTS` / `$@` (full args) and `$1`, `$2`, … (positional) substitution.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pi install npm:@ryan_nookpi/pi-extension-cross-agent
|
|
32
|
+
```
|
package/index.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-Agent — Load commands from other AI coding agent directories
|
|
3
|
+
*
|
|
4
|
+
* Scans .claude/, .gemini/, .codex/ directories (project + global) for:
|
|
5
|
+
* commands/*.md → registered as /name
|
|
6
|
+
* skills/ → detected (reserved for future use)
|
|
7
|
+
* agents/*.md → detected (reserved for future use)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { basename, join } from "node:path";
|
|
13
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
14
|
+
|
|
15
|
+
interface Discovered {
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
content: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface SourceGroup {
|
|
22
|
+
source: string;
|
|
23
|
+
commands: Discovered[];
|
|
24
|
+
skills: string[];
|
|
25
|
+
agents: Discovered[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function parseFrontmatter(raw: string): { description: string; body: string; fields: Record<string, string> } {
|
|
29
|
+
const match = raw.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
|
|
30
|
+
if (!match) return { description: "", body: raw, fields: {} };
|
|
31
|
+
|
|
32
|
+
const front = match[1];
|
|
33
|
+
const body = match[2];
|
|
34
|
+
const fields: Record<string, string> = {};
|
|
35
|
+
for (const line of front.split("\n")) {
|
|
36
|
+
const idx = line.indexOf(":");
|
|
37
|
+
if (idx > 0) fields[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
38
|
+
}
|
|
39
|
+
return { description: fields.description || "", body, fields };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function expandArgs(template: string, args: string): string {
|
|
43
|
+
const parts = args.split(/\s+/).filter(Boolean);
|
|
44
|
+
let result = template;
|
|
45
|
+
result = result.replace(/\$ARGUMENTS|\$@/g, args);
|
|
46
|
+
for (let i = 0; i < parts.length; i++) {
|
|
47
|
+
result = result.replaceAll(`$${i + 1}`, parts[i]);
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function scanCommands(dir: string): Discovered[] {
|
|
53
|
+
if (!existsSync(dir)) return [];
|
|
54
|
+
const items: Discovered[] = [];
|
|
55
|
+
try {
|
|
56
|
+
for (const file of readdirSync(dir)) {
|
|
57
|
+
if (!file.endsWith(".md")) continue;
|
|
58
|
+
const raw = readFileSync(join(dir, file), "utf-8");
|
|
59
|
+
const { description, body } = parseFrontmatter(raw);
|
|
60
|
+
items.push({
|
|
61
|
+
name: basename(file, ".md"),
|
|
62
|
+
description:
|
|
63
|
+
description ||
|
|
64
|
+
body
|
|
65
|
+
.split("\n")
|
|
66
|
+
.find((l) => l.trim())
|
|
67
|
+
?.trim() ||
|
|
68
|
+
"",
|
|
69
|
+
content: body,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
} catch {}
|
|
73
|
+
return items;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function scanSkills(dir: string): string[] {
|
|
77
|
+
if (!existsSync(dir)) return [];
|
|
78
|
+
const names: string[] = [];
|
|
79
|
+
try {
|
|
80
|
+
for (const entry of readdirSync(dir)) {
|
|
81
|
+
const skillFile = join(dir, entry, "SKILL.md");
|
|
82
|
+
const flatFile = join(dir, entry);
|
|
83
|
+
if (existsSync(skillFile) && statSync(skillFile).isFile()) {
|
|
84
|
+
names.push(entry);
|
|
85
|
+
} else if (entry.endsWith(".md") && statSync(flatFile).isFile()) {
|
|
86
|
+
names.push(basename(entry, ".md"));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch {}
|
|
90
|
+
return names;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function scanAgents(dir: string): Discovered[] {
|
|
94
|
+
if (!existsSync(dir)) return [];
|
|
95
|
+
const items: Discovered[] = [];
|
|
96
|
+
try {
|
|
97
|
+
for (const file of readdirSync(dir)) {
|
|
98
|
+
if (!file.endsWith(".md")) continue;
|
|
99
|
+
const raw = readFileSync(join(dir, file), "utf-8");
|
|
100
|
+
const { fields } = parseFrontmatter(raw);
|
|
101
|
+
items.push({
|
|
102
|
+
name: fields.name || basename(file, ".md"),
|
|
103
|
+
description: fields.description || "",
|
|
104
|
+
content: raw,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
} catch {}
|
|
108
|
+
return items;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default function (pi: ExtensionAPI) {
|
|
112
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
113
|
+
const home = homedir();
|
|
114
|
+
const cwd = ctx.cwd;
|
|
115
|
+
const providers = ["claude", "gemini", "codex"];
|
|
116
|
+
const groups: SourceGroup[] = [];
|
|
117
|
+
|
|
118
|
+
for (const p of providers) {
|
|
119
|
+
for (const [dir, label] of [
|
|
120
|
+
[join(cwd, `.${p}`), `.${p}`],
|
|
121
|
+
[join(home, `.${p}`), `~/.${p}`],
|
|
122
|
+
] as const) {
|
|
123
|
+
const commands = scanCommands(join(dir, "commands"));
|
|
124
|
+
const skills = scanSkills(join(dir, "skills"));
|
|
125
|
+
const agents = scanAgents(join(dir, "agents"));
|
|
126
|
+
|
|
127
|
+
if (commands.length || skills.length || agents.length) {
|
|
128
|
+
groups.push({ source: label, commands, skills, agents });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Also scan .pi/agents/
|
|
134
|
+
const localAgents = scanAgents(join(cwd, ".pi", "agents"));
|
|
135
|
+
if (localAgents.length) {
|
|
136
|
+
groups.push({ source: ".pi/agents", commands: [], skills: [], agents: localAgents });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Register commands
|
|
140
|
+
const seenCmds = new Set<string>();
|
|
141
|
+
|
|
142
|
+
for (const g of groups) {
|
|
143
|
+
for (const cmd of g.commands) {
|
|
144
|
+
if (seenCmds.has(cmd.name)) continue;
|
|
145
|
+
seenCmds.add(cmd.name);
|
|
146
|
+
pi.registerCommand(cmd.name, {
|
|
147
|
+
description: `[${g.source}] ${cmd.description}`.slice(0, 120),
|
|
148
|
+
handler: async (args) => {
|
|
149
|
+
pi.sendUserMessage(expandArgs(cmd.content, args || ""));
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (groups.length === 0) return;
|
|
156
|
+
|
|
157
|
+
setTimeout(() => {
|
|
158
|
+
if (!ctx.hasUI) return;
|
|
159
|
+
}, 100);
|
|
160
|
+
});
|
|
161
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ryan_nookpi/pi-extension-cross-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Load commands from Claude, Gemini, and Codex agent directories into pi.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/Jonghakseo/pi-extension.git",
|
|
9
|
+
"directory": "packages/cross-agent"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/Jonghakseo/pi-extension/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/Jonghakseo/pi-extension/tree/main/packages/cross-agent#readme",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"pi-package"
|
|
18
|
+
],
|
|
19
|
+
"files": [
|
|
20
|
+
"index.ts",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"pi": {
|
|
24
|
+
"extensions": [
|
|
25
|
+
"./index.ts"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@mariozechner/pi-coding-agent": "*"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
}
|
|
34
|
+
}
|