@zaganjade/pi-multi-skill 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -8
- package/package.json +1 -1
- package/skill-bundles.example.json +17 -17
- package/src/bmad-auto.ts +99 -99
- package/src/build.ts +352 -270
- package/src/bundles.ts +138 -138
- package/src/discover.ts +161 -161
- package/src/index.ts +13 -17
- package/src/metadata.ts +170 -170
- package/src/parse-args.ts +80 -80
- package/src/registry.ts +46 -46
- package/src/suggestions.ts +70 -70
- package/src/types.ts +64 -64
package/src/metadata.ts
CHANGED
|
@@ -1,170 +1,170 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
parseFrontmatter as parsePiFrontmatter,
|
|
4
|
-
stripFrontmatter as stripPiFrontmatter,
|
|
5
|
-
} from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import type { EnrichedSkillInfo, LoadMode, SkillInfo, SkillMetadata, SkillOrder } from "./types.ts";
|
|
7
|
-
|
|
8
|
-
const PROCESS_NAMES = new Set([
|
|
9
|
-
"using-superpowers",
|
|
10
|
-
"brainstorming",
|
|
11
|
-
"systematic-debugging",
|
|
12
|
-
"bmad-master",
|
|
13
|
-
"writing-plans",
|
|
14
|
-
"dispatching-parallel-agents",
|
|
15
|
-
"subagent-driven-development",
|
|
16
|
-
"executing-plans",
|
|
17
|
-
]);
|
|
18
|
-
|
|
19
|
-
const PLANNING_NAMES = new Set([
|
|
20
|
-
"analyst",
|
|
21
|
-
"pm",
|
|
22
|
-
"architect",
|
|
23
|
-
"ux-designer",
|
|
24
|
-
"scrum-master",
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
|
-
function parseFrontmatter(content: string): Record<string, string> {
|
|
28
|
-
const { frontmatter } = parsePiFrontmatter<Record<string, unknown>>(content);
|
|
29
|
-
const fields: Record<string, string> = {};
|
|
30
|
-
for (const [key, value] of Object.entries(frontmatter)) {
|
|
31
|
-
if (value === undefined || value === null) continue;
|
|
32
|
-
fields[key] = String(value);
|
|
33
|
-
}
|
|
34
|
-
return fields;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function stripFrontmatter(content: string): string {
|
|
38
|
-
return stripPiFrontmatter(content);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function parseListField(value: string | undefined): string[] {
|
|
42
|
-
if (!value) return [];
|
|
43
|
-
const trimmed = value.trim();
|
|
44
|
-
if (trimmed.startsWith("[")) {
|
|
45
|
-
try {
|
|
46
|
-
const parsed = JSON.parse(trimmed) as unknown;
|
|
47
|
-
if (Array.isArray(parsed)) {
|
|
48
|
-
return parsed.map(String).filter(Boolean);
|
|
49
|
-
}
|
|
50
|
-
} catch {
|
|
51
|
-
// fall through
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return trimmed
|
|
55
|
-
.split(",")
|
|
56
|
-
.map((s) => s.trim())
|
|
57
|
-
.filter(Boolean);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function parseTokenBudget(value: string | undefined): LoadMode | undefined {
|
|
61
|
-
if (value === "meta" || value === "lazy" || value === "full") return value;
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function extractCommands(body: string): string[] {
|
|
66
|
-
const commands: string[] = [];
|
|
67
|
-
const section = body.match(
|
|
68
|
-
/## Available Commands[\s\S]*?(?=\n## |\n# |$)/i,
|
|
69
|
-
);
|
|
70
|
-
if (!section) return commands;
|
|
71
|
-
|
|
72
|
-
for (const line of section[0].split("\n")) {
|
|
73
|
-
const match = line.match(/\*\*(\/[\w-]+)/);
|
|
74
|
-
if (match) commands.push(match[1]);
|
|
75
|
-
}
|
|
76
|
-
return commands;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function inferType(
|
|
80
|
-
name: string,
|
|
81
|
-
frontmatter: Record<string, string>,
|
|
82
|
-
): SkillMetadata["type"] {
|
|
83
|
-
const explicit = frontmatter.type?.toLowerCase();
|
|
84
|
-
if (explicit === "process" || explicit === "rigid" || explicit === "flexible") {
|
|
85
|
-
return explicit;
|
|
86
|
-
}
|
|
87
|
-
if (PROCESS_NAMES.has(name)) return "process";
|
|
88
|
-
if (name.includes("debug") || name.includes("tdd")) return "rigid";
|
|
89
|
-
return "unknown";
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function priorityScore(meta: SkillMetadata): number {
|
|
93
|
-
if (meta.type === "process") return 0;
|
|
94
|
-
if (PROCESS_NAMES.has(meta.name)) return 1;
|
|
95
|
-
if (PLANNING_NAMES.has(meta.name)) return 2;
|
|
96
|
-
if (meta.module === "core") return 1;
|
|
97
|
-
if (meta.module === "bmm") return 3;
|
|
98
|
-
return 4;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function enrichSkill(skill: SkillInfo): EnrichedSkillInfo {
|
|
102
|
-
let rawContent = "";
|
|
103
|
-
try {
|
|
104
|
-
rawContent = readFileSync(skill.filePath, "utf-8");
|
|
105
|
-
} catch {
|
|
106
|
-
rawContent = "";
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const frontmatter = parseFrontmatter(rawContent);
|
|
110
|
-
const body = stripFrontmatter(rawContent).trim();
|
|
111
|
-
const name = frontmatter.name || skill.name;
|
|
112
|
-
const description =
|
|
113
|
-
skill.description || frontmatter.description || truncateFirstLine(body);
|
|
114
|
-
|
|
115
|
-
const metadata: SkillMetadata = {
|
|
116
|
-
name,
|
|
117
|
-
description,
|
|
118
|
-
type: inferType(name, frontmatter),
|
|
119
|
-
module: frontmatter.module || "",
|
|
120
|
-
priority: Number.parseInt(frontmatter.priority || "50", 10) || 50,
|
|
121
|
-
commands: extractCommands(body),
|
|
122
|
-
skillId: frontmatter.skill_id || "",
|
|
123
|
-
version: frontmatter.version || "",
|
|
124
|
-
pairsWith: parseListField(frontmatter.pairs_with),
|
|
125
|
-
conflictsWith: parseListField(frontmatter.conflicts_with),
|
|
126
|
-
tokenBudget: parseTokenBudget(frontmatter.token_budget),
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
return { ...skill, name, description, metadata, rawContent, body };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function truncateFirstLine(body: string): string {
|
|
133
|
-
const line = body
|
|
134
|
-
.split("\n")
|
|
135
|
-
.map((l) => l.replace(/^>\s*/, "").trim())
|
|
136
|
-
.find((l) => l.length > 0);
|
|
137
|
-
return line?.slice(0, 120) ?? "";
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function sortSkills(
|
|
141
|
-
skills: EnrichedSkillInfo[],
|
|
142
|
-
order: SkillOrder = "process-first",
|
|
143
|
-
): EnrichedSkillInfo[] {
|
|
144
|
-
if (order === "alpha") {
|
|
145
|
-
return [...skills].sort((a, b) => a.name.localeCompare(b.name));
|
|
146
|
-
}
|
|
147
|
-
if (order === "explicit") return skills;
|
|
148
|
-
|
|
149
|
-
return [...skills].sort((a, b) => {
|
|
150
|
-
const pa = priorityScore(a.metadata);
|
|
151
|
-
const pb = priorityScore(b.metadata);
|
|
152
|
-
if (pa !== pb) return pa - pb;
|
|
153
|
-
if (a.metadata.priority !== b.metadata.priority) {
|
|
154
|
-
return a.metadata.priority - b.metadata.priority;
|
|
155
|
-
}
|
|
156
|
-
return a.name.localeCompare(b.name);
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export function truncateDescription(desc: string, maxLen = 120): string {
|
|
161
|
-
if (!desc) return "";
|
|
162
|
-
const lines = desc
|
|
163
|
-
.split("\n")
|
|
164
|
-
.map((l) => l.replace(/^>\s*/, "").trim())
|
|
165
|
-
.filter((l) => l.length > 0);
|
|
166
|
-
if (lines.length === 0) return "";
|
|
167
|
-
let text = lines[0];
|
|
168
|
-
if (lines.length > 1 && text.length < 40) text = `${text} ${lines[1]}`;
|
|
169
|
-
return text.length > maxLen ? `${text.slice(0, maxLen - 1)}…` : text;
|
|
170
|
-
}
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
parseFrontmatter as parsePiFrontmatter,
|
|
4
|
+
stripFrontmatter as stripPiFrontmatter,
|
|
5
|
+
} from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import type { EnrichedSkillInfo, LoadMode, SkillInfo, SkillMetadata, SkillOrder } from "./types.ts";
|
|
7
|
+
|
|
8
|
+
const PROCESS_NAMES = new Set([
|
|
9
|
+
"using-superpowers",
|
|
10
|
+
"brainstorming",
|
|
11
|
+
"systematic-debugging",
|
|
12
|
+
"bmad-master",
|
|
13
|
+
"writing-plans",
|
|
14
|
+
"dispatching-parallel-agents",
|
|
15
|
+
"subagent-driven-development",
|
|
16
|
+
"executing-plans",
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const PLANNING_NAMES = new Set([
|
|
20
|
+
"analyst",
|
|
21
|
+
"pm",
|
|
22
|
+
"architect",
|
|
23
|
+
"ux-designer",
|
|
24
|
+
"scrum-master",
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
function parseFrontmatter(content: string): Record<string, string> {
|
|
28
|
+
const { frontmatter } = parsePiFrontmatter<Record<string, unknown>>(content);
|
|
29
|
+
const fields: Record<string, string> = {};
|
|
30
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
31
|
+
if (value === undefined || value === null) continue;
|
|
32
|
+
fields[key] = String(value);
|
|
33
|
+
}
|
|
34
|
+
return fields;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function stripFrontmatter(content: string): string {
|
|
38
|
+
return stripPiFrontmatter(content);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseListField(value: string | undefined): string[] {
|
|
42
|
+
if (!value) return [];
|
|
43
|
+
const trimmed = value.trim();
|
|
44
|
+
if (trimmed.startsWith("[")) {
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(trimmed) as unknown;
|
|
47
|
+
if (Array.isArray(parsed)) {
|
|
48
|
+
return parsed.map(String).filter(Boolean);
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
// fall through
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return trimmed
|
|
55
|
+
.split(",")
|
|
56
|
+
.map((s) => s.trim())
|
|
57
|
+
.filter(Boolean);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseTokenBudget(value: string | undefined): LoadMode | undefined {
|
|
61
|
+
if (value === "meta" || value === "lazy" || value === "full") return value;
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function extractCommands(body: string): string[] {
|
|
66
|
+
const commands: string[] = [];
|
|
67
|
+
const section = body.match(
|
|
68
|
+
/## Available Commands[\s\S]*?(?=\n## |\n# |$)/i,
|
|
69
|
+
);
|
|
70
|
+
if (!section) return commands;
|
|
71
|
+
|
|
72
|
+
for (const line of section[0].split("\n")) {
|
|
73
|
+
const match = line.match(/\*\*(\/[\w-]+)/);
|
|
74
|
+
if (match) commands.push(match[1]);
|
|
75
|
+
}
|
|
76
|
+
return commands;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function inferType(
|
|
80
|
+
name: string,
|
|
81
|
+
frontmatter: Record<string, string>,
|
|
82
|
+
): SkillMetadata["type"] {
|
|
83
|
+
const explicit = frontmatter.type?.toLowerCase();
|
|
84
|
+
if (explicit === "process" || explicit === "rigid" || explicit === "flexible") {
|
|
85
|
+
return explicit;
|
|
86
|
+
}
|
|
87
|
+
if (PROCESS_NAMES.has(name)) return "process";
|
|
88
|
+
if (name.includes("debug") || name.includes("tdd")) return "rigid";
|
|
89
|
+
return "unknown";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function priorityScore(meta: SkillMetadata): number {
|
|
93
|
+
if (meta.type === "process") return 0;
|
|
94
|
+
if (PROCESS_NAMES.has(meta.name)) return 1;
|
|
95
|
+
if (PLANNING_NAMES.has(meta.name)) return 2;
|
|
96
|
+
if (meta.module === "core") return 1;
|
|
97
|
+
if (meta.module === "bmm") return 3;
|
|
98
|
+
return 4;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function enrichSkill(skill: SkillInfo): EnrichedSkillInfo {
|
|
102
|
+
let rawContent = "";
|
|
103
|
+
try {
|
|
104
|
+
rawContent = readFileSync(skill.filePath, "utf-8");
|
|
105
|
+
} catch {
|
|
106
|
+
rawContent = "";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const frontmatter = parseFrontmatter(rawContent);
|
|
110
|
+
const body = stripFrontmatter(rawContent).trim();
|
|
111
|
+
const name = frontmatter.name || skill.name;
|
|
112
|
+
const description =
|
|
113
|
+
skill.description || frontmatter.description || truncateFirstLine(body);
|
|
114
|
+
|
|
115
|
+
const metadata: SkillMetadata = {
|
|
116
|
+
name,
|
|
117
|
+
description,
|
|
118
|
+
type: inferType(name, frontmatter),
|
|
119
|
+
module: frontmatter.module || "",
|
|
120
|
+
priority: Number.parseInt(frontmatter.priority || "50", 10) || 50,
|
|
121
|
+
commands: extractCommands(body),
|
|
122
|
+
skillId: frontmatter.skill_id || "",
|
|
123
|
+
version: frontmatter.version || "",
|
|
124
|
+
pairsWith: parseListField(frontmatter.pairs_with),
|
|
125
|
+
conflictsWith: parseListField(frontmatter.conflicts_with),
|
|
126
|
+
tokenBudget: parseTokenBudget(frontmatter.token_budget),
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return { ...skill, name, description, metadata, rawContent, body };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function truncateFirstLine(body: string): string {
|
|
133
|
+
const line = body
|
|
134
|
+
.split("\n")
|
|
135
|
+
.map((l) => l.replace(/^>\s*/, "").trim())
|
|
136
|
+
.find((l) => l.length > 0);
|
|
137
|
+
return line?.slice(0, 120) ?? "";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function sortSkills(
|
|
141
|
+
skills: EnrichedSkillInfo[],
|
|
142
|
+
order: SkillOrder = "process-first",
|
|
143
|
+
): EnrichedSkillInfo[] {
|
|
144
|
+
if (order === "alpha") {
|
|
145
|
+
return [...skills].sort((a, b) => a.name.localeCompare(b.name));
|
|
146
|
+
}
|
|
147
|
+
if (order === "explicit") return skills;
|
|
148
|
+
|
|
149
|
+
return [...skills].sort((a, b) => {
|
|
150
|
+
const pa = priorityScore(a.metadata);
|
|
151
|
+
const pb = priorityScore(b.metadata);
|
|
152
|
+
if (pa !== pb) return pa - pb;
|
|
153
|
+
if (a.metadata.priority !== b.metadata.priority) {
|
|
154
|
+
return a.metadata.priority - b.metadata.priority;
|
|
155
|
+
}
|
|
156
|
+
return a.name.localeCompare(b.name);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function truncateDescription(desc: string, maxLen = 120): string {
|
|
161
|
+
if (!desc) return "";
|
|
162
|
+
const lines = desc
|
|
163
|
+
.split("\n")
|
|
164
|
+
.map((l) => l.replace(/^>\s*/, "").trim())
|
|
165
|
+
.filter((l) => l.length > 0);
|
|
166
|
+
if (lines.length === 0) return "";
|
|
167
|
+
let text = lines[0];
|
|
168
|
+
if (lines.length > 1 && text.length < 40) text = `${text} ${lines[1]}`;
|
|
169
|
+
return text.length > maxLen ? `${text.slice(0, maxLen - 1)}…` : text;
|
|
170
|
+
}
|
package/src/parse-args.ts
CHANGED
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
import type { LoadMode, ParsedSkillsArgs } from "./types.ts";
|
|
2
|
-
|
|
3
|
-
const FLAG_MODES: Record<string, LoadMode> = {
|
|
4
|
-
"--meta": "meta",
|
|
5
|
-
"--full": "full",
|
|
6
|
-
"--lazy": "lazy",
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export function parseSkillsArgs(raw: string): ParsedSkillsArgs {
|
|
10
|
-
let text = raw.trim();
|
|
11
|
-
let mode: LoadMode = "full";
|
|
12
|
-
let auto = false;
|
|
13
|
-
let parallel = false;
|
|
14
|
-
|
|
15
|
-
for (const [flag, loadMode] of Object.entries(FLAG_MODES)) {
|
|
16
|
-
if (text.includes(flag)) {
|
|
17
|
-
mode = loadMode;
|
|
18
|
-
text = text.replace(new RegExp(`\\s*${flag}\\b`, "g"), " ").trim();
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (/\s--auto\b/.test(text)) {
|
|
23
|
-
auto = true;
|
|
24
|
-
text = text.replace(/\s*--auto\b/g, " ").trim();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (/\s--parallel\b/.test(text)) {
|
|
28
|
-
parallel = true;
|
|
29
|
-
text = text.replace(/\s*--parallel\b/g, " ").trim();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const tokens = text.match(/^(\S+)(?:\s+([\s\S]*))?$/);
|
|
33
|
-
if (!tokens) {
|
|
34
|
-
return {
|
|
35
|
-
skillNames: [],
|
|
36
|
-
mode,
|
|
37
|
-
auto,
|
|
38
|
-
parallel,
|
|
39
|
-
parallelTasks: [],
|
|
40
|
-
instructions: "",
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const skillNames = tokens[1]
|
|
45
|
-
.split(",")
|
|
46
|
-
.map((s) => s.trim())
|
|
47
|
-
.filter((s) => s.length > 0);
|
|
48
|
-
|
|
49
|
-
let instructions = tokens[2]?.trim() ?? "";
|
|
50
|
-
let embeddedCommand: string | undefined;
|
|
51
|
-
let parallelTasks: string[] = [];
|
|
52
|
-
|
|
53
|
-
if (parallel && instructions.includes("|")) {
|
|
54
|
-
parallelTasks = instructions
|
|
55
|
-
.split("|")
|
|
56
|
-
.map((s) => s.trim())
|
|
57
|
-
.filter((s) => s.length > 0);
|
|
58
|
-
instructions = "";
|
|
59
|
-
} else if (parallel && instructions) {
|
|
60
|
-
parallelTasks = [instructions];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const cmdMatch = instructions.match(
|
|
64
|
-
/^(\/[\w-]+(?:\s+[^\n|]+)?)(?:\s+([\s\S]*))?$/,
|
|
65
|
-
);
|
|
66
|
-
if (cmdMatch && !parallel) {
|
|
67
|
-
embeddedCommand = cmdMatch[1].trim();
|
|
68
|
-
instructions = cmdMatch[2]?.trim() ?? "";
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
skillNames,
|
|
73
|
-
mode,
|
|
74
|
-
auto,
|
|
75
|
-
parallel,
|
|
76
|
-
parallelTasks,
|
|
77
|
-
embeddedCommand,
|
|
78
|
-
instructions,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
1
|
+
import type { LoadMode, ParsedSkillsArgs } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
const FLAG_MODES: Record<string, LoadMode> = {
|
|
4
|
+
"--meta": "meta",
|
|
5
|
+
"--full": "full",
|
|
6
|
+
"--lazy": "lazy",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function parseSkillsArgs(raw: string): ParsedSkillsArgs {
|
|
10
|
+
let text = raw.trim();
|
|
11
|
+
let mode: LoadMode = "full";
|
|
12
|
+
let auto = false;
|
|
13
|
+
let parallel = false;
|
|
14
|
+
|
|
15
|
+
for (const [flag, loadMode] of Object.entries(FLAG_MODES)) {
|
|
16
|
+
if (text.includes(flag)) {
|
|
17
|
+
mode = loadMode;
|
|
18
|
+
text = text.replace(new RegExp(`\\s*${flag}\\b`, "g"), " ").trim();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (/\s--auto\b/.test(text)) {
|
|
23
|
+
auto = true;
|
|
24
|
+
text = text.replace(/\s*--auto\b/g, " ").trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (/\s--parallel\b/.test(text)) {
|
|
28
|
+
parallel = true;
|
|
29
|
+
text = text.replace(/\s*--parallel\b/g, " ").trim();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const tokens = text.match(/^(\S+)(?:\s+([\s\S]*))?$/);
|
|
33
|
+
if (!tokens) {
|
|
34
|
+
return {
|
|
35
|
+
skillNames: [],
|
|
36
|
+
mode,
|
|
37
|
+
auto,
|
|
38
|
+
parallel,
|
|
39
|
+
parallelTasks: [],
|
|
40
|
+
instructions: "",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const skillNames = tokens[1]
|
|
45
|
+
.split(",")
|
|
46
|
+
.map((s) => s.trim())
|
|
47
|
+
.filter((s) => s.length > 0);
|
|
48
|
+
|
|
49
|
+
let instructions = tokens[2]?.trim() ?? "";
|
|
50
|
+
let embeddedCommand: string | undefined;
|
|
51
|
+
let parallelTasks: string[] = [];
|
|
52
|
+
|
|
53
|
+
if (parallel && instructions.includes("|")) {
|
|
54
|
+
parallelTasks = instructions
|
|
55
|
+
.split("|")
|
|
56
|
+
.map((s) => s.trim())
|
|
57
|
+
.filter((s) => s.length > 0);
|
|
58
|
+
instructions = "";
|
|
59
|
+
} else if (parallel && instructions) {
|
|
60
|
+
parallelTasks = [instructions];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const cmdMatch = instructions.match(
|
|
64
|
+
/^(\/[\w-]+(?:\s+[^\n|]+)?)(?:\s+([\s\S]*))?$/,
|
|
65
|
+
);
|
|
66
|
+
if (cmdMatch && !parallel) {
|
|
67
|
+
embeddedCommand = cmdMatch[1].trim();
|
|
68
|
+
instructions = cmdMatch[2]?.trim() ?? "";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
skillNames,
|
|
73
|
+
mode,
|
|
74
|
+
auto,
|
|
75
|
+
parallel,
|
|
76
|
+
parallelTasks,
|
|
77
|
+
embeddedCommand,
|
|
78
|
+
instructions,
|
|
79
|
+
};
|
|
80
|
+
}
|
package/src/registry.ts
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { enrichSkill } from "./metadata.ts";
|
|
5
|
-
import type { SkillInfo } from "./types.ts";
|
|
6
|
-
|
|
7
|
-
function getAgentDir(): string {
|
|
8
|
-
return join(homedir(), ".pi", "agent");
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function rebuildSkillIndex(skills: SkillInfo[]): void {
|
|
12
|
-
const index = {
|
|
13
|
-
updatedAt: new Date().toISOString(),
|
|
14
|
-
count: skills.length,
|
|
15
|
-
skills: skills.map((skill) => {
|
|
16
|
-
const enriched = enrichSkill(skill);
|
|
17
|
-
return {
|
|
18
|
-
name: enriched.name,
|
|
19
|
-
description: enriched.metadata.description,
|
|
20
|
-
type: enriched.metadata.type,
|
|
21
|
-
module: enriched.metadata.module,
|
|
22
|
-
priority: enriched.metadata.priority,
|
|
23
|
-
skillId: enriched.metadata.skillId,
|
|
24
|
-
version: enriched.metadata.version,
|
|
25
|
-
pairsWith: enriched.metadata.pairsWith,
|
|
26
|
-
conflictsWith: enriched.metadata.conflictsWith,
|
|
27
|
-
tokenBudget: enriched.metadata.tokenBudget,
|
|
28
|
-
commands: enriched.metadata.commands,
|
|
29
|
-
location: enriched.filePath,
|
|
30
|
-
};
|
|
31
|
-
}),
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const dir = getAgentDir();
|
|
35
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
writeFileSync(
|
|
39
|
-
join(dir, "skill-index.json"),
|
|
40
|
-
`${JSON.stringify(index, null, 2)}\n`,
|
|
41
|
-
"utf-8",
|
|
42
|
-
);
|
|
43
|
-
} catch {
|
|
44
|
-
// Non-fatal — index is optional
|
|
45
|
-
}
|
|
46
|
-
}
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { enrichSkill } from "./metadata.ts";
|
|
5
|
+
import type { SkillInfo } from "./types.ts";
|
|
6
|
+
|
|
7
|
+
function getAgentDir(): string {
|
|
8
|
+
return join(homedir(), ".pi", "agent");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function rebuildSkillIndex(skills: SkillInfo[]): void {
|
|
12
|
+
const index = {
|
|
13
|
+
updatedAt: new Date().toISOString(),
|
|
14
|
+
count: skills.length,
|
|
15
|
+
skills: skills.map((skill) => {
|
|
16
|
+
const enriched = enrichSkill(skill);
|
|
17
|
+
return {
|
|
18
|
+
name: enriched.name,
|
|
19
|
+
description: enriched.metadata.description,
|
|
20
|
+
type: enriched.metadata.type,
|
|
21
|
+
module: enriched.metadata.module,
|
|
22
|
+
priority: enriched.metadata.priority,
|
|
23
|
+
skillId: enriched.metadata.skillId,
|
|
24
|
+
version: enriched.metadata.version,
|
|
25
|
+
pairsWith: enriched.metadata.pairsWith,
|
|
26
|
+
conflictsWith: enriched.metadata.conflictsWith,
|
|
27
|
+
tokenBudget: enriched.metadata.tokenBudget,
|
|
28
|
+
commands: enriched.metadata.commands,
|
|
29
|
+
location: enriched.filePath,
|
|
30
|
+
};
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const dir = getAgentDir();
|
|
35
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
writeFileSync(
|
|
39
|
+
join(dir, "skill-index.json"),
|
|
40
|
+
`${JSON.stringify(index, null, 2)}\n`,
|
|
41
|
+
"utf-8",
|
|
42
|
+
);
|
|
43
|
+
} catch {
|
|
44
|
+
// Non-fatal — index is optional
|
|
45
|
+
}
|
|
46
|
+
}
|