@aliou/pi-dev-kit 0.4.9 → 0.5.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 +60 -0
- package/package.json +59 -2
- package/src/commands/index.ts +6 -0
- package/src/commands/update.ts +144 -0
- package/src/index.ts +8 -0
- package/src/prompts/setup-demo.md +35 -0
- package/src/skills/demo-setup/SKILL.md +217 -0
- package/src/skills/pi-extension/SKILL.md +140 -0
- package/src/skills/pi-extension/references/additional-apis.md +264 -0
- package/src/skills/pi-extension/references/commands.md +100 -0
- package/src/skills/pi-extension/references/components.md +166 -0
- package/src/skills/pi-extension/references/documentation.md +54 -0
- package/src/skills/pi-extension/references/hooks.md +244 -0
- package/src/skills/pi-extension/references/messages.md +169 -0
- package/src/skills/pi-extension/references/modes.md +156 -0
- package/src/skills/pi-extension/references/providers.md +134 -0
- package/src/skills/pi-extension/references/publish.md +139 -0
- package/src/skills/pi-extension/references/state.md +56 -0
- package/src/skills/pi-extension/references/structure.md +408 -0
- package/src/skills/pi-extension/references/testing.md +54 -0
- package/src/skills/pi-extension/references/tools.md +430 -0
- package/src/tools/changelog-tool.ts +596 -0
- package/src/tools/docs-tool.ts +240 -0
- package/src/tools/index.ts +12 -0
- package/src/tools/package-manager-tool.ts +223 -0
- package/src/tools/utils.ts +62 -0
- package/src/tools/version-tool.ts +77 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { ToolBody, ToolCallHeader, ToolFooter } from "@aliou/pi-utils-ui";
|
|
4
|
+
import type {
|
|
5
|
+
AgentToolResult,
|
|
6
|
+
ExtensionAPI,
|
|
7
|
+
ExtensionContext,
|
|
8
|
+
Theme,
|
|
9
|
+
ToolRenderResultOptions,
|
|
10
|
+
} from "@mariozechner/pi-coding-agent";
|
|
11
|
+
import { keyHint } from "@mariozechner/pi-coding-agent";
|
|
12
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
13
|
+
import { Type } from "@sinclair/typebox";
|
|
14
|
+
import { findPiInstallation } from "./utils";
|
|
15
|
+
|
|
16
|
+
const DocsParams = Type.Object({});
|
|
17
|
+
type DocsParamsType = Record<string, never>;
|
|
18
|
+
|
|
19
|
+
interface DocsDetails {
|
|
20
|
+
success: boolean;
|
|
21
|
+
message: string;
|
|
22
|
+
/** Relative paths from the pi install root, markdown only. */
|
|
23
|
+
docFiles?: string[];
|
|
24
|
+
installPath?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type ExecuteResult = AgentToolResult<DocsDetails>;
|
|
28
|
+
|
|
29
|
+
function listFilesRecursive(dir: string, prefix = ""): string[] {
|
|
30
|
+
const results: string[] = [];
|
|
31
|
+
if (!fs.existsSync(dir)) return results;
|
|
32
|
+
|
|
33
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
36
|
+
if (entry.isDirectory()) {
|
|
37
|
+
results.push(...listFilesRecursive(path.join(dir, entry.name), rel));
|
|
38
|
+
} else {
|
|
39
|
+
results.push(rel);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return results;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function setupDocsTool(pi: ExtensionAPI) {
|
|
46
|
+
pi.registerTool<typeof DocsParams, DocsDetails>({
|
|
47
|
+
name: "pi_docs",
|
|
48
|
+
label: "Pi Documentation",
|
|
49
|
+
description:
|
|
50
|
+
"List Pi markdown documentation files (README, docs/, examples/)",
|
|
51
|
+
|
|
52
|
+
parameters: DocsParams,
|
|
53
|
+
|
|
54
|
+
async execute(
|
|
55
|
+
_toolCallId: string,
|
|
56
|
+
_params: DocsParamsType,
|
|
57
|
+
_signal: AbortSignal | undefined,
|
|
58
|
+
_onUpdate: unknown,
|
|
59
|
+
_ctx: ExtensionContext,
|
|
60
|
+
): Promise<ExecuteResult> {
|
|
61
|
+
try {
|
|
62
|
+
const piPath = findPiInstallation();
|
|
63
|
+
if (!piPath) {
|
|
64
|
+
return {
|
|
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
|
+
}
|
|
77
|
+
|
|
78
|
+
const readmePath = path.join(piPath, "README.md");
|
|
79
|
+
const docsDir = path.join(piPath, "docs");
|
|
80
|
+
const examplesDir = path.join(piPath, "examples");
|
|
81
|
+
|
|
82
|
+
const docFiles: string[] = [];
|
|
83
|
+
|
|
84
|
+
if (fs.existsSync(readmePath)) {
|
|
85
|
+
docFiles.push("README.md");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (fs.existsSync(docsDir)) {
|
|
89
|
+
for (const file of listFilesRecursive(docsDir)) {
|
|
90
|
+
if (file.endsWith(".md")) {
|
|
91
|
+
docFiles.push(`docs/${file}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (fs.existsSync(examplesDir)) {
|
|
97
|
+
for (const file of listFilesRecursive(examplesDir)) {
|
|
98
|
+
if (file.endsWith(".md")) {
|
|
99
|
+
docFiles.push(`examples/${file}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (docFiles.length === 0) {
|
|
105
|
+
return {
|
|
106
|
+
content: [
|
|
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})`,
|
|
123
|
+
);
|
|
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
|
+
}
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
renderCall(_args: DocsParamsType, theme: Theme) {
|
|
148
|
+
return new ToolCallHeader({ toolName: "Pi Docs" }, theme);
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
renderResult(
|
|
152
|
+
result: AgentToolResult<DocsDetails>,
|
|
153
|
+
options: ToolRenderResultOptions,
|
|
154
|
+
theme: Theme,
|
|
155
|
+
) {
|
|
156
|
+
const { details } = result;
|
|
157
|
+
|
|
158
|
+
if (!details) {
|
|
159
|
+
const text = result.content[0];
|
|
160
|
+
return new Text(
|
|
161
|
+
text?.type === "text" && text.text ? text.text : "No result",
|
|
162
|
+
0,
|
|
163
|
+
0,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const fields: Array<
|
|
168
|
+
{ label: string; value: string; showCollapsed?: boolean } | Text
|
|
169
|
+
> = [];
|
|
170
|
+
|
|
171
|
+
if (!details.success) {
|
|
172
|
+
fields.push({
|
|
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 {
|
|
184
|
+
const lines: string[] = [];
|
|
185
|
+
|
|
186
|
+
if (options.expanded) {
|
|
187
|
+
lines.push(
|
|
188
|
+
theme.fg("accent", `${details.docFiles.length} markdown files:`),
|
|
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
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
fields.push(new Text(lines.join("\n"), 0, 0));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return new ToolBody(
|
|
218
|
+
{
|
|
219
|
+
fields,
|
|
220
|
+
footer: new ToolFooter(theme, {
|
|
221
|
+
items: [
|
|
222
|
+
{
|
|
223
|
+
label: "status",
|
|
224
|
+
value: details.success ? "ok" : "error",
|
|
225
|
+
tone: details.success ? "success" : "error",
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
label: "docs",
|
|
229
|
+
value: String(details.docFiles?.length ?? 0),
|
|
230
|
+
tone: "accent",
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
}),
|
|
234
|
+
},
|
|
235
|
+
options,
|
|
236
|
+
theme,
|
|
237
|
+
);
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { setupChangelogTool } from "./changelog-tool";
|
|
3
|
+
import { setupDocsTool } from "./docs-tool";
|
|
4
|
+
import { setupPackageManagerTool } from "./package-manager-tool";
|
|
5
|
+
import { setupVersionTool } from "./version-tool";
|
|
6
|
+
|
|
7
|
+
export function setupTools(pi: ExtensionAPI) {
|
|
8
|
+
setupPackageManagerTool(pi);
|
|
9
|
+
setupVersionTool(pi);
|
|
10
|
+
setupDocsTool(pi);
|
|
11
|
+
setupChangelogTool(pi);
|
|
12
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { ToolCallHeader } from "@aliou/pi-utils-ui";
|
|
4
|
+
import type {
|
|
5
|
+
AgentToolResult,
|
|
6
|
+
ExtensionAPI,
|
|
7
|
+
ExtensionContext,
|
|
8
|
+
Theme,
|
|
9
|
+
ToolRenderResultOptions,
|
|
10
|
+
} from "@mariozechner/pi-coding-agent";
|
|
11
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
12
|
+
import { Type } from "@sinclair/typebox";
|
|
13
|
+
import { resolveCtx } from "./utils";
|
|
14
|
+
|
|
15
|
+
const Params = Type.Object({});
|
|
16
|
+
type ParamsType = Record<string, never>;
|
|
17
|
+
|
|
18
|
+
interface PackageManagerDetails {
|
|
19
|
+
success: boolean;
|
|
20
|
+
message: string;
|
|
21
|
+
packageManager?: string;
|
|
22
|
+
version?: string;
|
|
23
|
+
lockfile?: string;
|
|
24
|
+
installCommand?: string;
|
|
25
|
+
runCommand?: string;
|
|
26
|
+
cwd?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type ExecuteResult = AgentToolResult<PackageManagerDetails>;
|
|
30
|
+
|
|
31
|
+
const LOCKFILES: Record<string, string> = {
|
|
32
|
+
"pnpm-lock.yaml": "pnpm",
|
|
33
|
+
"yarn.lock": "yarn",
|
|
34
|
+
"package-lock.json": "npm",
|
|
35
|
+
"bun.lockb": "bun",
|
|
36
|
+
"bun.lock": "bun",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const COMMANDS: Record<string, { install: string; run: string }> = {
|
|
40
|
+
pnpm: { install: "pnpm install", run: "pnpm" },
|
|
41
|
+
yarn: { install: "yarn install", run: "yarn" },
|
|
42
|
+
npm: { install: "npm install", run: "npm run" },
|
|
43
|
+
bun: { install: "bun install", run: "bun run" },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function setupPackageManagerTool(pi: ExtensionAPI) {
|
|
47
|
+
pi.registerTool<typeof Params, PackageManagerDetails>({
|
|
48
|
+
name: "detect_package_manager",
|
|
49
|
+
label: "Package Manager",
|
|
50
|
+
description:
|
|
51
|
+
"Detect the package manager used in the current project by checking lockfiles and package.json",
|
|
52
|
+
|
|
53
|
+
parameters: Params,
|
|
54
|
+
|
|
55
|
+
async execute(
|
|
56
|
+
_toolCallId: string,
|
|
57
|
+
_params: ParamsType,
|
|
58
|
+
signal: AbortSignal | undefined,
|
|
59
|
+
onUpdate: unknown,
|
|
60
|
+
ctx: ExtensionContext,
|
|
61
|
+
): Promise<ExecuteResult> {
|
|
62
|
+
const resolvedCtx = resolveCtx(signal, onUpdate, ctx);
|
|
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
|
+
}
|
|
82
|
+
|
|
83
|
+
// Walk up from cwd to repo root, collecting packageManager and lockfile.
|
|
84
|
+
// Stop at .git boundary to avoid escaping the repository.
|
|
85
|
+
let declaredPm: string | undefined;
|
|
86
|
+
let declaredVersion: string | undefined;
|
|
87
|
+
let lockfile: string | undefined;
|
|
88
|
+
let lockfilePm: string | undefined;
|
|
89
|
+
|
|
90
|
+
let searchDir = cwd;
|
|
91
|
+
while (true) {
|
|
92
|
+
// Check packageManager field in package.json
|
|
93
|
+
if (!declaredPm) {
|
|
94
|
+
const pkgPath = path.join(searchDir, "package.json");
|
|
95
|
+
try {
|
|
96
|
+
if (fs.existsSync(pkgPath)) {
|
|
97
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
98
|
+
if (typeof pkg.packageManager === "string") {
|
|
99
|
+
const match = pkg.packageManager.match(/^([^@]+)@?(.*)?$/);
|
|
100
|
+
if (match) {
|
|
101
|
+
declaredPm = match[1];
|
|
102
|
+
declaredVersion = match[2] || undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
// Ignore parse errors
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check lockfiles
|
|
112
|
+
if (!lockfilePm) {
|
|
113
|
+
for (const [filename, pm] of Object.entries(LOCKFILES)) {
|
|
114
|
+
if (fs.existsSync(path.join(searchDir, filename))) {
|
|
115
|
+
lockfilePm = pm;
|
|
116
|
+
lockfile = filename;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Stop if we found both, hit .git, or hit filesystem root
|
|
123
|
+
if (
|
|
124
|
+
(declaredPm && lockfilePm) ||
|
|
125
|
+
fs.existsSync(path.join(searchDir, ".git"))
|
|
126
|
+
) {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
const parent = path.dirname(searchDir);
|
|
130
|
+
if (parent === searchDir) break;
|
|
131
|
+
searchDir = parent;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const pm = declaredPm || lockfilePm || "npm";
|
|
135
|
+
const fallback = { install: `${pm} install`, run: pm };
|
|
136
|
+
const commands = COMMANDS[pm] ?? fallback;
|
|
137
|
+
|
|
138
|
+
const parts: string[] = [];
|
|
139
|
+
parts.push(`Package manager: ${pm}`);
|
|
140
|
+
if (declaredVersion) {
|
|
141
|
+
parts.push(`Declared version: ${declaredVersion}`);
|
|
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
|
+
};
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
renderCall(_args: ParamsType, theme: Theme) {
|
|
183
|
+
return new ToolCallHeader({ toolName: "Detect Package Manager" }, theme);
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
renderResult(
|
|
187
|
+
result: AgentToolResult<PackageManagerDetails>,
|
|
188
|
+
_options: ToolRenderResultOptions,
|
|
189
|
+
theme: Theme,
|
|
190
|
+
): Text {
|
|
191
|
+
const { details } = result;
|
|
192
|
+
|
|
193
|
+
if (!details) {
|
|
194
|
+
const text = result.content[0];
|
|
195
|
+
return new Text(
|
|
196
|
+
text?.type === "text" && text.text ? text.text : "No result",
|
|
197
|
+
0,
|
|
198
|
+
0,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!details.success) {
|
|
203
|
+
return new Text(theme.fg("error", details.message), 0, 0);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const lines: string[] = [];
|
|
207
|
+
lines.push(
|
|
208
|
+
theme.fg(
|
|
209
|
+
"success",
|
|
210
|
+
`Package manager: ${theme.bold(details.packageManager || "unknown")}`,
|
|
211
|
+
),
|
|
212
|
+
);
|
|
213
|
+
if (details.version) {
|
|
214
|
+
lines.push(theme.fg("dim", `Version: ${details.version}`));
|
|
215
|
+
}
|
|
216
|
+
if (details.lockfile) {
|
|
217
|
+
lines.push(theme.fg("dim", `Lockfile: ${details.lockfile}`));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Find the currently running Pi installation directory by resolving the
|
|
7
|
+
* pi-coding-agent package location.
|
|
8
|
+
*/
|
|
9
|
+
export function findPiInstallation(): string | null {
|
|
10
|
+
try {
|
|
11
|
+
const piModulePath = require.resolve(
|
|
12
|
+
"@mariozechner/pi-coding-agent/package.json",
|
|
13
|
+
);
|
|
14
|
+
return path.dirname(piModulePath);
|
|
15
|
+
} catch (_error) {
|
|
16
|
+
const scriptPath = process.argv[1];
|
|
17
|
+
if (scriptPath) {
|
|
18
|
+
let currentDir = path.dirname(scriptPath);
|
|
19
|
+
|
|
20
|
+
while (currentDir !== path.dirname(currentDir)) {
|
|
21
|
+
const packageJsonPath = path.join(currentDir, "package.json");
|
|
22
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
23
|
+
try {
|
|
24
|
+
const packageContent = fs.readFileSync(packageJsonPath, "utf-8");
|
|
25
|
+
const packageJson = JSON.parse(packageContent);
|
|
26
|
+
if (packageJson.name === "@mariozechner/pi-coding-agent") {
|
|
27
|
+
return currentDir;
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// Continue searching
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
currentDir = path.dirname(currentDir);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ToolCallHeader } from "@aliou/pi-utils-ui";
|
|
2
|
+
import type {
|
|
3
|
+
AgentToolResult,
|
|
4
|
+
ExtensionAPI,
|
|
5
|
+
ExtensionContext,
|
|
6
|
+
Theme,
|
|
7
|
+
ToolRenderResultOptions,
|
|
8
|
+
} from "@mariozechner/pi-coding-agent";
|
|
9
|
+
import { VERSION } from "@mariozechner/pi-coding-agent";
|
|
10
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
11
|
+
import { Type } from "@sinclair/typebox";
|
|
12
|
+
|
|
13
|
+
const VersionParams = Type.Object({});
|
|
14
|
+
type VersionParamsType = Record<string, never>;
|
|
15
|
+
|
|
16
|
+
interface VersionDetails {
|
|
17
|
+
success: boolean;
|
|
18
|
+
message: string;
|
|
19
|
+
version: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type ExecuteResult = AgentToolResult<VersionDetails>;
|
|
23
|
+
|
|
24
|
+
export function setupVersionTool(pi: ExtensionAPI) {
|
|
25
|
+
pi.registerTool<typeof VersionParams, VersionDetails>({
|
|
26
|
+
name: "pi_version",
|
|
27
|
+
label: "Pi Version",
|
|
28
|
+
description: "Get the version of the currently running Pi instance",
|
|
29
|
+
|
|
30
|
+
parameters: VersionParams,
|
|
31
|
+
|
|
32
|
+
async execute(
|
|
33
|
+
_toolCallId: string,
|
|
34
|
+
_params: VersionParamsType,
|
|
35
|
+
_signal: AbortSignal | undefined,
|
|
36
|
+
_onUpdate: unknown,
|
|
37
|
+
_ctx: ExtensionContext,
|
|
38
|
+
): Promise<ExecuteResult> {
|
|
39
|
+
const message = `Pi version ${VERSION}`;
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: message }],
|
|
42
|
+
details: {
|
|
43
|
+
success: true,
|
|
44
|
+
message,
|
|
45
|
+
version: VERSION,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
renderCall(_args: VersionParamsType, theme: Theme) {
|
|
51
|
+
return new ToolCallHeader({ toolName: "Pi Version" }, theme);
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
renderResult(
|
|
55
|
+
result: AgentToolResult<VersionDetails>,
|
|
56
|
+
_options: ToolRenderResultOptions,
|
|
57
|
+
theme: Theme,
|
|
58
|
+
): Text {
|
|
59
|
+
const { details } = result;
|
|
60
|
+
|
|
61
|
+
if (!details) {
|
|
62
|
+
const text = result.content[0];
|
|
63
|
+
return new Text(
|
|
64
|
+
text?.type === "text" && text.text ? text.text : "No result",
|
|
65
|
+
0,
|
|
66
|
+
0,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return new Text(
|
|
71
|
+
theme.fg("accent", `Pi version: ${details.version}`),
|
|
72
|
+
0,
|
|
73
|
+
0,
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|