@aliou/pi-dev-kit 0.5.0 → 0.6.1
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/package.json +8 -4
- package/src/skills/pi-extension/SKILL.md +51 -37
- package/src/skills/pi-extension/references/additional-apis.md +41 -1
- package/src/skills/pi-extension/references/hooks.md +12 -9
- package/src/skills/pi-extension/references/state.md +38 -27
- package/src/skills/pi-extension/references/structure.md +116 -2
- package/src/skills/pi-extension/references/testing.md +129 -0
- package/src/skills/pi-extension/references/tools.md +674 -156
- package/src/tools/changelog-tool.ts +172 -284
- package/src/tools/docs-tool.ts +87 -146
- package/src/tools/package-manager-tool.ts +98 -127
- package/src/tools/utils.ts +0 -24
- package/src/tools/version-tool.ts +13 -20
package/src/tools/docs-tool.ts
CHANGED
|
@@ -13,19 +13,15 @@ import { Text } from "@mariozechner/pi-tui";
|
|
|
13
13
|
import { Type } from "@sinclair/typebox";
|
|
14
14
|
import { findPiInstallation } from "./utils";
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
type
|
|
16
|
+
const DocsParamsSchema = Type.Object({});
|
|
17
|
+
type DocsParams = Record<string, never>;
|
|
18
18
|
|
|
19
19
|
interface DocsDetails {
|
|
20
|
-
success: boolean;
|
|
21
|
-
message: string;
|
|
22
20
|
/** Relative paths from the pi install root, markdown only. */
|
|
23
21
|
docFiles?: string[];
|
|
24
22
|
installPath?: string;
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
type ExecuteResult = AgentToolResult<DocsDetails>;
|
|
28
|
-
|
|
29
25
|
function listFilesRecursive(dir: string, prefix = ""): string[] {
|
|
30
26
|
const results: string[] = [];
|
|
31
27
|
if (!fs.existsSync(dir)) return results;
|
|
@@ -43,108 +39,78 @@ function listFilesRecursive(dir: string, prefix = ""): string[] {
|
|
|
43
39
|
}
|
|
44
40
|
|
|
45
41
|
export function setupDocsTool(pi: ExtensionAPI) {
|
|
46
|
-
pi.registerTool<typeof
|
|
42
|
+
pi.registerTool<typeof DocsParamsSchema, DocsDetails>({
|
|
47
43
|
name: "pi_docs",
|
|
48
44
|
label: "Pi Documentation",
|
|
49
45
|
description:
|
|
50
46
|
"List Pi markdown documentation files (README, docs/, examples/)",
|
|
51
47
|
|
|
52
|
-
|
|
48
|
+
promptSnippet: "List Pi documentation files",
|
|
49
|
+
promptGuidelines: [
|
|
50
|
+
"Use to discover available Pi documentation",
|
|
51
|
+
"Returns markdown files from README.md, docs/, and examples/",
|
|
52
|
+
],
|
|
53
|
+
|
|
54
|
+
parameters: DocsParamsSchema,
|
|
53
55
|
|
|
54
56
|
async execute(
|
|
55
57
|
_toolCallId: string,
|
|
56
|
-
_params:
|
|
58
|
+
_params: DocsParams,
|
|
57
59
|
_signal: AbortSignal | undefined,
|
|
58
60
|
_onUpdate: unknown,
|
|
59
61
|
_ctx: ExtensionContext,
|
|
60
|
-
): Promise<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
content: [
|
|
66
|
-
{
|
|
67
|
-
type: "text",
|
|
68
|
-
text: "Could not locate running Pi installation directory",
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
details: {
|
|
72
|
-
success: false,
|
|
73
|
-
message: "Could not locate running Pi installation directory",
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
}
|
|
62
|
+
): Promise<AgentToolResult<DocsDetails>> {
|
|
63
|
+
const piPath = findPiInstallation();
|
|
64
|
+
if (!piPath) {
|
|
65
|
+
throw new Error("Could not locate running Pi installation directory");
|
|
66
|
+
}
|
|
77
67
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
68
|
+
const readmePath = path.join(piPath, "README.md");
|
|
69
|
+
const docsDir = path.join(piPath, "docs");
|
|
70
|
+
const examplesDir = path.join(piPath, "examples");
|
|
81
71
|
|
|
82
|
-
|
|
72
|
+
const docFiles: string[] = [];
|
|
83
73
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
74
|
+
if (fs.existsSync(readmePath)) {
|
|
75
|
+
docFiles.push("README.md");
|
|
76
|
+
}
|
|
87
77
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
78
|
+
if (fs.existsSync(docsDir)) {
|
|
79
|
+
for (const file of listFilesRecursive(docsDir)) {
|
|
80
|
+
if (file.endsWith(".md")) {
|
|
81
|
+
docFiles.push(`docs/${file}`);
|
|
93
82
|
}
|
|
94
83
|
}
|
|
84
|
+
}
|
|
95
85
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
86
|
+
if (fs.existsSync(examplesDir)) {
|
|
87
|
+
for (const file of listFilesRecursive(examplesDir)) {
|
|
88
|
+
if (file.endsWith(".md")) {
|
|
89
|
+
docFiles.push(`examples/${file}`);
|
|
101
90
|
}
|
|
102
91
|
}
|
|
92
|
+
}
|
|
103
93
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
{
|
|
108
|
-
type: "text",
|
|
109
|
-
text: `No markdown documentation found in Pi installation`,
|
|
110
|
-
},
|
|
111
|
-
],
|
|
112
|
-
details: {
|
|
113
|
-
success: false,
|
|
114
|
-
message: `No markdown documentation found in Pi installation`,
|
|
115
|
-
installPath: piPath,
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Content sent to LLM: full relative paths so it can read them.
|
|
121
|
-
const lines = docFiles.map(
|
|
122
|
-
(rel) => `${path.join(piPath, rel)} (${rel})`,
|
|
94
|
+
if (docFiles.length === 0) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`No markdown documentation found in Pi installation at ${piPath}`,
|
|
123
97
|
);
|
|
124
|
-
const message = `${docFiles.length} markdown files:\n${lines.join("\n")}`;
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
content: [{ type: "text", text: message }],
|
|
128
|
-
details: {
|
|
129
|
-
success: true,
|
|
130
|
-
message: `Found ${docFiles.length} markdown files`,
|
|
131
|
-
docFiles,
|
|
132
|
-
installPath: piPath,
|
|
133
|
-
},
|
|
134
|
-
};
|
|
135
|
-
} catch (error) {
|
|
136
|
-
const message = `Error reading Pi documentation: ${error instanceof Error ? error.message : String(error)}`;
|
|
137
|
-
return {
|
|
138
|
-
content: [{ type: "text", text: message }],
|
|
139
|
-
details: {
|
|
140
|
-
success: false,
|
|
141
|
-
message,
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
98
|
}
|
|
99
|
+
|
|
100
|
+
// Content sent to LLM: full relative paths so it can read them.
|
|
101
|
+
const lines = docFiles.map((rel) => `${path.join(piPath, rel)} (${rel})`);
|
|
102
|
+
const message = `${docFiles.length} markdown files:\n${lines.join("\n")}`;
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
content: [{ type: "text", text: message }],
|
|
106
|
+
details: {
|
|
107
|
+
docFiles,
|
|
108
|
+
installPath: piPath,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
145
111
|
},
|
|
146
112
|
|
|
147
|
-
renderCall(_args:
|
|
113
|
+
renderCall(_args: DocsParams, theme: Theme) {
|
|
148
114
|
return new ToolCallHeader({ toolName: "Pi Docs" }, theme);
|
|
149
115
|
},
|
|
150
116
|
|
|
@@ -154,8 +120,15 @@ export function setupDocsTool(pi: ExtensionAPI) {
|
|
|
154
120
|
theme: Theme,
|
|
155
121
|
) {
|
|
156
122
|
const { details } = result;
|
|
123
|
+
const { isPartial } = options;
|
|
124
|
+
|
|
125
|
+
// Handle isPartial first (this tool doesn't stream, but keep the pattern)
|
|
126
|
+
if (isPartial) {
|
|
127
|
+
return new Text(theme.fg("dim", "Loading..."), 0, 0);
|
|
128
|
+
}
|
|
157
129
|
|
|
158
|
-
|
|
130
|
+
// Check for missing expected fields in details to detect errors
|
|
131
|
+
if (!details || !details.docFiles) {
|
|
159
132
|
const text = result.content[0];
|
|
160
133
|
return new Text(
|
|
161
134
|
text?.type === "text" && text.text ? text.text : "No result",
|
|
@@ -164,77 +137,45 @@ export function setupDocsTool(pi: ExtensionAPI) {
|
|
|
164
137
|
);
|
|
165
138
|
}
|
|
166
139
|
|
|
140
|
+
const { docFiles } = details;
|
|
167
141
|
const fields: Array<
|
|
168
142
|
{ label: string; value: string; showCollapsed?: boolean } | Text
|
|
169
143
|
> = [];
|
|
170
144
|
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
label: "Error",
|
|
174
|
-
value: theme.fg("error", details.message),
|
|
175
|
-
showCollapsed: true,
|
|
176
|
-
});
|
|
177
|
-
} else if (!details.docFiles || details.docFiles.length === 0) {
|
|
178
|
-
fields.push({
|
|
179
|
-
label: "Result",
|
|
180
|
-
value: theme.fg("warning", "No docs found"),
|
|
181
|
-
showCollapsed: true,
|
|
182
|
-
});
|
|
183
|
-
} else {
|
|
145
|
+
if (options.expanded) {
|
|
146
|
+
// Expanded view: show full file list
|
|
184
147
|
const lines: string[] = [];
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
);
|
|
191
|
-
for (const rel of details.docFiles) {
|
|
192
|
-
lines.push(theme.fg("dim", ` ${rel}`));
|
|
193
|
-
}
|
|
194
|
-
} else {
|
|
195
|
-
lines.push(
|
|
196
|
-
theme.fg("accent", `${details.docFiles.length} markdown files`) +
|
|
197
|
-
` (${keyHint("app.tools.expand", "to expand")})`,
|
|
198
|
-
"",
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
const filenames = details.docFiles.map((file) => path.basename(file));
|
|
202
|
-
const maxLen = Math.max(...filenames.map((file) => file.length));
|
|
203
|
-
const colWidth = maxLen + 2;
|
|
204
|
-
const cols = Math.max(1, Math.floor(80 / colWidth));
|
|
205
|
-
for (let i = 0; i < filenames.length; i += cols) {
|
|
206
|
-
const row = filenames
|
|
207
|
-
.slice(i, i + cols)
|
|
208
|
-
.map((file) => file.padEnd(colWidth))
|
|
209
|
-
.join("");
|
|
210
|
-
lines.push(theme.fg("dim", row));
|
|
211
|
-
}
|
|
148
|
+
lines.push(
|
|
149
|
+
theme.fg("accent", `${docFiles.length} markdown files:`),
|
|
150
|
+
"",
|
|
151
|
+
);
|
|
152
|
+
for (const rel of docFiles) {
|
|
153
|
+
lines.push(theme.fg("dim", ` ${rel}`));
|
|
212
154
|
}
|
|
213
|
-
|
|
214
155
|
fields.push(new Text(lines.join("\n"), 0, 0));
|
|
156
|
+
} else {
|
|
157
|
+
// Collapsed view: show file count + expand hint
|
|
158
|
+
fields.push({
|
|
159
|
+
label: "Files",
|
|
160
|
+
value:
|
|
161
|
+
theme.fg("accent", `${docFiles.length} markdown files`) +
|
|
162
|
+
` (${keyHint("app.tools.expand", "to expand")})`,
|
|
163
|
+
showCollapsed: true,
|
|
164
|
+
});
|
|
215
165
|
}
|
|
216
166
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
value: String(details.docFiles?.length ?? 0),
|
|
230
|
-
tone: "accent",
|
|
231
|
-
},
|
|
232
|
-
],
|
|
233
|
-
}),
|
|
234
|
-
},
|
|
235
|
-
options,
|
|
236
|
-
theme,
|
|
237
|
-
);
|
|
167
|
+
// Only show footer if there are items worth showing
|
|
168
|
+
const footer = new ToolFooter(theme, {
|
|
169
|
+
items: [
|
|
170
|
+
{
|
|
171
|
+
label: "docs",
|
|
172
|
+
value: String(docFiles.length),
|
|
173
|
+
tone: "accent",
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return new ToolBody({ fields, footer }, options, theme);
|
|
238
179
|
},
|
|
239
180
|
});
|
|
240
181
|
}
|
|
@@ -10,14 +10,11 @@ import type {
|
|
|
10
10
|
} from "@mariozechner/pi-coding-agent";
|
|
11
11
|
import { Text } from "@mariozechner/pi-tui";
|
|
12
12
|
import { Type } from "@sinclair/typebox";
|
|
13
|
-
import { resolveCtx } from "./utils";
|
|
14
13
|
|
|
15
14
|
const Params = Type.Object({});
|
|
16
|
-
type
|
|
15
|
+
type PackageManagerParams = Record<string, never>;
|
|
17
16
|
|
|
18
17
|
interface PackageManagerDetails {
|
|
19
|
-
success: boolean;
|
|
20
|
-
message: string;
|
|
21
18
|
packageManager?: string;
|
|
22
19
|
version?: string;
|
|
23
20
|
lockfile?: string;
|
|
@@ -26,8 +23,6 @@ interface PackageManagerDetails {
|
|
|
26
23
|
cwd?: string;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
|
-
type ExecuteResult = AgentToolResult<PackageManagerDetails>;
|
|
30
|
-
|
|
31
26
|
const LOCKFILES: Record<string, string> = {
|
|
32
27
|
"pnpm-lock.yaml": "pnpm",
|
|
33
28
|
"yarn.lock": "yarn",
|
|
@@ -49,137 +44,115 @@ export function setupPackageManagerTool(pi: ExtensionAPI) {
|
|
|
49
44
|
label: "Package Manager",
|
|
50
45
|
description:
|
|
51
46
|
"Detect the package manager used in the current project by checking lockfiles and package.json",
|
|
47
|
+
promptSnippet: "Detect the package manager for this project",
|
|
48
|
+
promptGuidelines: [
|
|
49
|
+
"Use when you need to know which package manager (npm, yarn, pnpm, bun) the project uses",
|
|
50
|
+
"Helpful before running install commands or scripts",
|
|
51
|
+
],
|
|
52
52
|
|
|
53
53
|
parameters: Params,
|
|
54
54
|
|
|
55
55
|
async execute(
|
|
56
56
|
_toolCallId: string,
|
|
57
|
-
_params:
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
_params: PackageManagerParams,
|
|
58
|
+
_signal: AbortSignal | undefined,
|
|
59
|
+
_onUpdate: unknown,
|
|
60
60
|
ctx: ExtensionContext,
|
|
61
|
-
): Promise<
|
|
62
|
-
const
|
|
63
|
-
const cwd = resolvedCtx.cwd;
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
const packageJsonPath = path.join(cwd, "package.json");
|
|
67
|
-
if (!fs.existsSync(packageJsonPath)) {
|
|
68
|
-
return {
|
|
69
|
-
content: [
|
|
70
|
-
{
|
|
71
|
-
type: "text",
|
|
72
|
-
text: `No package.json found in ${cwd}`,
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
details: {
|
|
76
|
-
success: false,
|
|
77
|
-
message: `No package.json found in ${cwd}`,
|
|
78
|
-
cwd,
|
|
79
|
-
},
|
|
80
|
-
};
|
|
81
|
-
}
|
|
61
|
+
): Promise<AgentToolResult<PackageManagerDetails>> {
|
|
62
|
+
const cwd = ctx.cwd;
|
|
82
63
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
64
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
65
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
66
|
+
throw new Error(`No package.json found in ${cwd}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Walk up from cwd to repo root, collecting packageManager and lockfile.
|
|
70
|
+
// Stop at .git boundary to avoid escaping the repository.
|
|
71
|
+
let declaredPm: string | undefined;
|
|
72
|
+
let declaredVersion: string | undefined;
|
|
73
|
+
let lockfile: string | undefined;
|
|
74
|
+
let lockfilePm: string | undefined;
|
|
75
|
+
|
|
76
|
+
let searchDir = cwd;
|
|
77
|
+
while (true) {
|
|
78
|
+
// Check packageManager field in package.json
|
|
79
|
+
if (!declaredPm) {
|
|
80
|
+
const pkgPath = path.join(searchDir, "package.json");
|
|
81
|
+
try {
|
|
82
|
+
if (fs.existsSync(pkgPath)) {
|
|
83
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
84
|
+
if (typeof pkg.packageManager === "string") {
|
|
85
|
+
const match = pkg.packageManager.match(/^([^@]+)@?(.*)?$/);
|
|
86
|
+
if (match) {
|
|
87
|
+
declaredPm = match[1];
|
|
88
|
+
declaredVersion = match[2] || undefined;
|
|
104
89
|
}
|
|
105
90
|
}
|
|
106
|
-
} catch {
|
|
107
|
-
// Ignore parse errors
|
|
108
91
|
}
|
|
92
|
+
} catch {
|
|
93
|
+
// Ignore parse errors
|
|
109
94
|
}
|
|
95
|
+
}
|
|
110
96
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
97
|
+
// Check lockfiles
|
|
98
|
+
if (!lockfilePm) {
|
|
99
|
+
for (const [filename, pm] of Object.entries(LOCKFILES)) {
|
|
100
|
+
if (fs.existsSync(path.join(searchDir, filename))) {
|
|
101
|
+
lockfilePm = pm;
|
|
102
|
+
lockfile = filename;
|
|
103
|
+
break;
|
|
119
104
|
}
|
|
120
105
|
}
|
|
106
|
+
}
|
|
121
107
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
const parent = path.dirname(searchDir);
|
|
130
|
-
if (parent === searchDir) break;
|
|
131
|
-
searchDir = parent;
|
|
108
|
+
// Stop if we found both, hit .git, or hit filesystem root
|
|
109
|
+
if (
|
|
110
|
+
(declaredPm && lockfilePm) ||
|
|
111
|
+
fs.existsSync(path.join(searchDir, ".git"))
|
|
112
|
+
) {
|
|
113
|
+
break;
|
|
132
114
|
}
|
|
115
|
+
const parent = path.dirname(searchDir);
|
|
116
|
+
if (parent === searchDir) break;
|
|
117
|
+
searchDir = parent;
|
|
118
|
+
}
|
|
133
119
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
120
|
+
const pm = declaredPm || lockfilePm || "npm";
|
|
121
|
+
const fallback = { install: `${pm} install`, run: pm };
|
|
122
|
+
const commands = COMMANDS[pm] ?? fallback;
|
|
137
123
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
if (lockfile) {
|
|
144
|
-
parts.push(`Lockfile: ${lockfile}`);
|
|
145
|
-
}
|
|
146
|
-
if (!lockfile && !declaredPm) {
|
|
147
|
-
parts.push(
|
|
148
|
-
"No lockfile or packageManager field found, defaulting to npm",
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
parts.push(`Install: ${commands.install}`);
|
|
152
|
-
parts.push(`Run: ${commands.run}`);
|
|
153
|
-
|
|
154
|
-
const message = parts.join("\n");
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
content: [{ type: "text", text: message }],
|
|
158
|
-
details: {
|
|
159
|
-
success: true,
|
|
160
|
-
message,
|
|
161
|
-
packageManager: pm,
|
|
162
|
-
version: declaredVersion,
|
|
163
|
-
lockfile,
|
|
164
|
-
installCommand: commands.install,
|
|
165
|
-
runCommand: commands.run,
|
|
166
|
-
cwd,
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
} catch (error) {
|
|
170
|
-
const message = `Error detecting package manager: ${error instanceof Error ? error.message : String(error)}`;
|
|
171
|
-
return {
|
|
172
|
-
content: [{ type: "text", text: message }],
|
|
173
|
-
details: {
|
|
174
|
-
success: false,
|
|
175
|
-
message,
|
|
176
|
-
cwd,
|
|
177
|
-
},
|
|
178
|
-
};
|
|
124
|
+
const parts: string[] = [];
|
|
125
|
+
parts.push(`Package manager: ${pm}`);
|
|
126
|
+
if (declaredVersion) {
|
|
127
|
+
parts.push(`Declared version: ${declaredVersion}`);
|
|
179
128
|
}
|
|
129
|
+
if (lockfile) {
|
|
130
|
+
parts.push(`Lockfile: ${lockfile}`);
|
|
131
|
+
}
|
|
132
|
+
if (!lockfile && !declaredPm) {
|
|
133
|
+
parts.push(
|
|
134
|
+
"No lockfile or packageManager field found, defaulting to npm",
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
parts.push(`Install: ${commands.install}`);
|
|
138
|
+
parts.push(`Run: ${commands.run}`);
|
|
139
|
+
|
|
140
|
+
const message = parts.join("\n");
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
content: [{ type: "text", text: message }],
|
|
144
|
+
details: {
|
|
145
|
+
packageManager: pm,
|
|
146
|
+
version: declaredVersion,
|
|
147
|
+
lockfile,
|
|
148
|
+
installCommand: commands.install,
|
|
149
|
+
runCommand: commands.run,
|
|
150
|
+
cwd,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
180
153
|
},
|
|
181
154
|
|
|
182
|
-
renderCall(_args:
|
|
155
|
+
renderCall(_args: PackageManagerParams, theme: Theme) {
|
|
183
156
|
return new ToolCallHeader({ toolName: "Detect Package Manager" }, theme);
|
|
184
157
|
},
|
|
185
158
|
|
|
@@ -190,24 +163,22 @@ export function setupPackageManagerTool(pi: ExtensionAPI) {
|
|
|
190
163
|
): Text {
|
|
191
164
|
const { details } = result;
|
|
192
165
|
|
|
193
|
-
|
|
166
|
+
// Check for missing expected fields (framework passes {} on error)
|
|
167
|
+
if (!details?.packageManager) {
|
|
168
|
+
// Extract error message from result.content
|
|
194
169
|
const text = result.content[0];
|
|
195
|
-
|
|
196
|
-
text?.type === "text" && text.text
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (!details.success) {
|
|
203
|
-
return new Text(theme.fg("error", details.message), 0, 0);
|
|
170
|
+
const errorMessage =
|
|
171
|
+
text?.type === "text" && text.text
|
|
172
|
+
? text.text
|
|
173
|
+
: "Failed to detect package manager";
|
|
174
|
+
return new Text(theme.fg("error", errorMessage), 0, 0);
|
|
204
175
|
}
|
|
205
176
|
|
|
206
177
|
const lines: string[] = [];
|
|
207
178
|
lines.push(
|
|
208
179
|
theme.fg(
|
|
209
180
|
"success",
|
|
210
|
-
`Package manager: ${theme.bold(details.packageManager
|
|
181
|
+
`Package manager: ${theme.bold(details.packageManager)}`,
|
|
211
182
|
),
|
|
212
183
|
);
|
|
213
184
|
if (details.version) {
|
package/src/tools/utils.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Find the currently running Pi installation directory by resolving the
|
|
@@ -37,26 +36,3 @@ export function findPiInstallation(): string | null {
|
|
|
37
36
|
return null;
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Resolve ExtensionContext from execute args, compatible with Pi 0.50.x and
|
|
43
|
-
* 0.51.0+.
|
|
44
|
-
*
|
|
45
|
-
* Pi 0.50.x: execute(id, params, onUpdate, ctx, signal)
|
|
46
|
-
* Pi 0.51.0+: execute(id, params, signal, onUpdate, ctx)
|
|
47
|
-
*
|
|
48
|
-
* When compiled against 0.51.0+, the typed params are (signal, onUpdate, ctx).
|
|
49
|
-
* On 0.50.x at runtime, `signal` actually receives the onUpdate callback
|
|
50
|
-
* (a function), `onUpdate` receives ctx, and `ctx` receives signal.
|
|
51
|
-
*/
|
|
52
|
-
export function resolveCtx(
|
|
53
|
-
signal: AbortSignal | undefined,
|
|
54
|
-
onUpdate: unknown,
|
|
55
|
-
ctx: ExtensionContext,
|
|
56
|
-
): ExtensionContext {
|
|
57
|
-
// AbortSignal is never callable; a function here means we got onUpdate (0.50.x)
|
|
58
|
-
if (typeof signal === "function") {
|
|
59
|
-
return onUpdate as ExtensionContext;
|
|
60
|
-
}
|
|
61
|
-
return ctx;
|
|
62
|
-
}
|
|
@@ -14,18 +14,18 @@ const VersionParams = Type.Object({});
|
|
|
14
14
|
type VersionParamsType = Record<string, never>;
|
|
15
15
|
|
|
16
16
|
interface VersionDetails {
|
|
17
|
-
|
|
18
|
-
message: string;
|
|
19
|
-
version: string;
|
|
17
|
+
version?: string;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
type ExecuteResult = AgentToolResult<VersionDetails>;
|
|
23
|
-
|
|
24
20
|
export function setupVersionTool(pi: ExtensionAPI) {
|
|
25
21
|
pi.registerTool<typeof VersionParams, VersionDetails>({
|
|
26
22
|
name: "pi_version",
|
|
27
23
|
label: "Pi Version",
|
|
28
24
|
description: "Get the version of the currently running Pi instance",
|
|
25
|
+
promptSnippet: "Check the current Pi version.",
|
|
26
|
+
promptGuidelines: [
|
|
27
|
+
"Use when the user asks about the Pi version or when a task depends on knowing the installed version.",
|
|
28
|
+
],
|
|
29
29
|
|
|
30
30
|
parameters: VersionParams,
|
|
31
31
|
|
|
@@ -35,15 +35,10 @@ export function setupVersionTool(pi: ExtensionAPI) {
|
|
|
35
35
|
_signal: AbortSignal | undefined,
|
|
36
36
|
_onUpdate: unknown,
|
|
37
37
|
_ctx: ExtensionContext,
|
|
38
|
-
): Promise<
|
|
39
|
-
const message = `Pi version ${VERSION}`;
|
|
38
|
+
): Promise<AgentToolResult<VersionDetails>> {
|
|
40
39
|
return {
|
|
41
|
-
content: [{ type: "text", text:
|
|
42
|
-
details: {
|
|
43
|
-
success: true,
|
|
44
|
-
message,
|
|
45
|
-
version: VERSION,
|
|
46
|
-
},
|
|
40
|
+
content: [{ type: "text", text: `Pi version ${VERSION}` }],
|
|
41
|
+
details: { version: VERSION },
|
|
47
42
|
};
|
|
48
43
|
},
|
|
49
44
|
|
|
@@ -58,13 +53,11 @@ export function setupVersionTool(pi: ExtensionAPI) {
|
|
|
58
53
|
): Text {
|
|
59
54
|
const { details } = result;
|
|
60
55
|
|
|
61
|
-
if (!details) {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
0,
|
|
67
|
-
);
|
|
56
|
+
if (!details?.version) {
|
|
57
|
+
const textBlock = result.content.find((c) => c.type === "text");
|
|
58
|
+
const msg =
|
|
59
|
+
(textBlock?.type === "text" && textBlock.text) || "Unknown version";
|
|
60
|
+
return new Text(theme.fg("error", msg), 0, 0);
|
|
68
61
|
}
|
|
69
62
|
|
|
70
63
|
return new Text(
|