@aliou/pi-dev-kit 0.6.4 → 0.7.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.
@@ -4,17 +4,19 @@ import { ToolBody, ToolCallHeader, ToolFooter } from "@aliou/pi-utils-ui";
4
4
  import type {
5
5
  AgentToolResult,
6
6
  ExtensionAPI,
7
- ExtensionContext,
8
7
  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";
8
+ } from "@earendil-works/pi-coding-agent";
9
+ import { defineTool, keyHint } from "@earendil-works/pi-coding-agent";
10
+ import { Text } from "@earendil-works/pi-tui";
11
+ import { type Static, Type } from "typebox";
14
12
  import { findPiInstallation } from "./utils";
15
13
 
16
14
  const DocsParamsSchema = Type.Object({});
17
- type DocsParams = Record<string, never>;
15
+ type DocsParams = Static<typeof DocsParamsSchema>;
16
+
17
+ function renderDocsCall(_args: DocsParams, theme: Theme) {
18
+ return new ToolCallHeader({ toolName: "Pi Docs" }, theme);
19
+ }
18
20
 
19
21
  interface DocsDetails {
20
22
  /** Relative paths from the pi install root, markdown only. */
@@ -38,144 +40,139 @@ function listFilesRecursive(dir: string, prefix = ""): string[] {
38
40
  return results;
39
41
  }
40
42
 
41
- export function setupDocsTool(pi: ExtensionAPI) {
42
- pi.registerTool<typeof DocsParamsSchema, DocsDetails>({
43
- name: "pi_docs",
44
- label: "Pi Documentation",
45
- description:
46
- "List Pi markdown documentation files (README, docs/, examples/)",
47
-
48
- promptSnippet: "List Pi documentation files",
49
- promptGuidelines: [
50
- "Use pi_docs to discover available Pi documentation",
51
- "pi_docs returns markdown files from README.md, docs/, and examples/",
52
- ],
53
-
54
- parameters: DocsParamsSchema,
55
-
56
- async execute(
57
- _toolCallId: string,
58
- _params: DocsParams,
59
- _signal: AbortSignal | undefined,
60
- _onUpdate: unknown,
61
- _ctx: ExtensionContext,
62
- ): Promise<AgentToolResult<DocsDetails>> {
63
- const piPath = findPiInstallation();
64
- if (!piPath) {
65
- throw new Error("Could not locate running Pi installation directory");
66
- }
43
+ const docsTool = defineTool({
44
+ name: "pi_docs",
45
+ label: "Pi Documentation",
46
+ description:
47
+ "List Pi markdown documentation files (README, docs/, examples/)",
48
+
49
+ promptSnippet: "List Pi documentation files",
50
+ promptGuidelines: [
51
+ "Use pi_docs to discover available Pi documentation",
52
+ "pi_docs returns markdown files from README.md, docs/, and examples/",
53
+ ],
54
+
55
+ parameters: DocsParamsSchema,
56
+
57
+ async execute(
58
+ _toolCallId,
59
+ _params,
60
+ _signal,
61
+ _onUpdate,
62
+ _ctx,
63
+ ): Promise<AgentToolResult<DocsDetails>> {
64
+ const piPath = findPiInstallation();
65
+ if (!piPath) {
66
+ throw new Error("Could not locate running Pi installation directory");
67
+ }
67
68
 
68
- const readmePath = path.join(piPath, "README.md");
69
- const docsDir = path.join(piPath, "docs");
70
- const examplesDir = path.join(piPath, "examples");
69
+ const readmePath = path.join(piPath, "README.md");
70
+ const docsDir = path.join(piPath, "docs");
71
+ const examplesDir = path.join(piPath, "examples");
71
72
 
72
- const docFiles: string[] = [];
73
+ const docFiles: string[] = [];
73
74
 
74
- if (fs.existsSync(readmePath)) {
75
- docFiles.push("README.md");
76
- }
75
+ if (fs.existsSync(readmePath)) {
76
+ docFiles.push("README.md");
77
+ }
77
78
 
78
- if (fs.existsSync(docsDir)) {
79
- for (const file of listFilesRecursive(docsDir)) {
80
- if (file.endsWith(".md")) {
81
- docFiles.push(`docs/${file}`);
82
- }
79
+ if (fs.existsSync(docsDir)) {
80
+ for (const file of listFilesRecursive(docsDir)) {
81
+ if (file.endsWith(".md")) {
82
+ docFiles.push(`docs/${file}`);
83
83
  }
84
84
  }
85
+ }
85
86
 
86
- if (fs.existsSync(examplesDir)) {
87
- for (const file of listFilesRecursive(examplesDir)) {
88
- if (file.endsWith(".md")) {
89
- docFiles.push(`examples/${file}`);
90
- }
87
+ if (fs.existsSync(examplesDir)) {
88
+ for (const file of listFilesRecursive(examplesDir)) {
89
+ if (file.endsWith(".md")) {
90
+ docFiles.push(`examples/${file}`);
91
91
  }
92
92
  }
93
+ }
93
94
 
94
- if (docFiles.length === 0) {
95
- throw new Error(
96
- `No markdown documentation found in Pi installation at ${piPath}`,
97
- );
98
- }
95
+ if (docFiles.length === 0) {
96
+ throw new Error(
97
+ `No markdown documentation found in Pi installation at ${piPath}`,
98
+ );
99
+ }
99
100
 
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")}`;
101
+ // Content sent to LLM: full relative paths so it can read them.
102
+ const lines = docFiles.map((rel) => `${path.join(piPath, rel)} (${rel})`);
103
+ const message = `${docFiles.length} markdown files:\n${lines.join("\n")}`;
104
+
105
+ return {
106
+ content: [{ type: "text", text: message }],
107
+ details: {
108
+ docFiles,
109
+ installPath: piPath,
110
+ },
111
+ };
112
+ },
113
+
114
+ renderCall(args, theme) {
115
+ return renderDocsCall(args, theme);
116
+ },
117
+
118
+ renderResult(result, options, theme) {
119
+ const { details } = result;
120
+ const { isPartial } = options;
121
+
122
+ // Handle isPartial first (this tool doesn't stream, but keep the pattern)
123
+ if (isPartial) {
124
+ return new Text(theme.fg("dim", "Loading..."), 0, 0);
125
+ }
103
126
 
104
- return {
105
- content: [{ type: "text", text: message }],
106
- details: {
107
- docFiles,
108
- installPath: piPath,
109
- },
110
- };
111
- },
112
-
113
- renderCall(_args: DocsParams, theme: Theme) {
114
- return new ToolCallHeader({ toolName: "Pi Docs" }, theme);
115
- },
116
-
117
- renderResult(
118
- result: AgentToolResult<DocsDetails>,
119
- options: ToolRenderResultOptions,
120
- theme: Theme,
121
- ) {
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
- }
127
+ // Check for missing expected fields in details to detect errors
128
+ if (!details || !details.docFiles) {
129
+ const text = result.content[0];
130
+ return new Text(
131
+ text?.type === "text" && text.text ? text.text : "No result",
132
+ 0,
133
+ 0,
134
+ );
135
+ }
129
136
 
130
- // Check for missing expected fields in details to detect errors
131
- if (!details || !details.docFiles) {
132
- const text = result.content[0];
133
- return new Text(
134
- text?.type === "text" && text.text ? text.text : "No result",
135
- 0,
136
- 0,
137
- );
137
+ const { docFiles } = details;
138
+ const fields: Array<
139
+ { label: string; value: string; showCollapsed?: boolean } | Text
140
+ > = [];
141
+
142
+ if (options.expanded) {
143
+ // Expanded view: show full file list
144
+ const lines: string[] = [];
145
+ lines.push(theme.fg("accent", `${docFiles.length} markdown files:`), "");
146
+ for (const rel of docFiles) {
147
+ lines.push(theme.fg("dim", ` ${rel}`));
138
148
  }
149
+ fields.push(new Text(lines.join("\n"), 0, 0));
150
+ } else {
151
+ // Collapsed view: show file count + expand hint
152
+ fields.push({
153
+ label: "Files",
154
+ value:
155
+ theme.fg("accent", `${docFiles.length} markdown files`) +
156
+ ` (${keyHint("app.tools.expand", "to expand")})`,
157
+ showCollapsed: true,
158
+ });
159
+ }
139
160
 
140
- const { docFiles } = details;
141
- const fields: Array<
142
- { label: string; value: string; showCollapsed?: boolean } | Text
143
- > = [];
144
-
145
- if (options.expanded) {
146
- // Expanded view: show full file list
147
- const lines: string[] = [];
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}`));
154
- }
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
- });
165
- }
161
+ // Only show footer if there are items worth showing
162
+ const footer = new ToolFooter(theme, {
163
+ items: [
164
+ {
165
+ label: "docs",
166
+ value: String(docFiles.length),
167
+ tone: "accent",
168
+ },
169
+ ],
170
+ });
166
171
 
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
- });
172
+ return new ToolBody({ fields, footer }, options, theme);
173
+ },
174
+ });
177
175
 
178
- return new ToolBody({ fields, footer }, options, theme);
179
- },
180
- });
176
+ export function setupDocsTool(pi: ExtensionAPI) {
177
+ pi.registerTool(docsTool);
181
178
  }
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { setupChangelogTool } from "./changelog-tool";
3
3
  import { setupDocsTool } from "./docs-tool";
4
4
  import { setupPackageManagerTool } from "./package-manager-tool";
@@ -10,3 +10,7 @@ export function setupTools(pi: ExtensionAPI) {
10
10
  setupDocsTool(pi);
11
11
  setupChangelogTool(pi);
12
12
  }
13
+
14
+ export default function toolsExtension(pi: ExtensionAPI) {
15
+ setupTools(pi);
16
+ }
@@ -4,15 +4,18 @@ import { ToolCallHeader } from "@aliou/pi-utils-ui";
4
4
  import type {
5
5
  AgentToolResult,
6
6
  ExtensionAPI,
7
- ExtensionContext,
8
7
  Theme,
9
- ToolRenderResultOptions,
10
- } from "@mariozechner/pi-coding-agent";
11
- import { Text } from "@mariozechner/pi-tui";
12
- import { Type } from "@sinclair/typebox";
8
+ } from "@earendil-works/pi-coding-agent";
9
+ import { defineTool } from "@earendil-works/pi-coding-agent";
10
+ import { Text } from "@earendil-works/pi-tui";
11
+ import { type Static, Type } from "typebox";
13
12
 
14
13
  const Params = Type.Object({});
15
- type PackageManagerParams = Record<string, never>;
14
+ type PackageManagerParams = Static<typeof Params>;
15
+
16
+ function renderPackageManagerCall(_args: PackageManagerParams, theme: Theme) {
17
+ return new ToolCallHeader({ toolName: "Detect Package Manager" }, theme);
18
+ }
16
19
 
17
20
  interface PackageManagerDetails {
18
21
  packageManager?: string;
@@ -38,157 +41,159 @@ const COMMANDS: Record<string, { install: string; run: string }> = {
38
41
  bun: { install: "bun install", run: "bun run" },
39
42
  };
40
43
 
41
- export function setupPackageManagerTool(pi: ExtensionAPI) {
42
- pi.registerTool<typeof Params, PackageManagerDetails>({
43
- name: "detect_package_manager",
44
- label: "Package Manager",
45
- description:
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 detect_package_manager when you need to know which package manager (npm, yarn, pnpm, bun) the project uses",
50
- "detect_package_manager is helpful before running install commands or scripts",
51
- ],
52
-
53
- parameters: Params,
54
-
55
- async execute(
56
- _toolCallId: string,
57
- _params: PackageManagerParams,
58
- _signal: AbortSignal | undefined,
59
- _onUpdate: unknown,
60
- ctx: ExtensionContext,
61
- ): Promise<AgentToolResult<PackageManagerDetails>> {
62
- const cwd = ctx.cwd;
63
-
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;
89
- }
44
+ const packageManagerTool = defineTool({
45
+ name: "detect_package_manager",
46
+ label: "Package Manager",
47
+ description:
48
+ "Detect the package manager used in the current project by checking lockfiles and package.json",
49
+ promptSnippet: "Detect the package manager for this project",
50
+ promptGuidelines: [
51
+ "Use detect_package_manager when you need to know which package manager (npm, yarn, pnpm, bun) the project uses",
52
+ "detect_package_manager is helpful before running install commands or scripts",
53
+ ],
54
+
55
+ parameters: Params,
56
+
57
+ async execute(
58
+ _toolCallId,
59
+ _params,
60
+ _signal,
61
+ _onUpdate,
62
+ ctx,
63
+ ): Promise<AgentToolResult<PackageManagerDetails>> {
64
+ const cwd = ctx.cwd;
65
+
66
+ const packageJsonPath = path.join(cwd, "package.json");
67
+ if (!fs.existsSync(packageJsonPath)) {
68
+ throw new Error(`No package.json found in ${cwd}`);
69
+ }
70
+
71
+ // Walk up from cwd to repo root, collecting packageManager and lockfile.
72
+ // Stop at .git boundary to avoid escaping the repository.
73
+ let declaredPm: string | undefined;
74
+ let declaredVersion: string | undefined;
75
+ let lockfile: string | undefined;
76
+ let lockfilePm: string | undefined;
77
+
78
+ let searchDir = cwd;
79
+ while (true) {
80
+ // Check packageManager field in package.json
81
+ if (!declaredPm) {
82
+ const pkgPath = path.join(searchDir, "package.json");
83
+ try {
84
+ if (fs.existsSync(pkgPath)) {
85
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
86
+ if (typeof pkg.packageManager === "string") {
87
+ const match = pkg.packageManager.match(/^([^@]+)@?(.*)?$/);
88
+ if (match) {
89
+ declaredPm = match[1];
90
+ declaredVersion = match[2] || undefined;
90
91
  }
91
92
  }
92
- } catch {
93
- // Ignore parse errors
94
93
  }
94
+ } catch {
95
+ // Ignore parse errors
95
96
  }
97
+ }
96
98
 
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;
104
- }
99
+ // Check lockfiles
100
+ if (!lockfilePm) {
101
+ for (const [filename, pm] of Object.entries(LOCKFILES)) {
102
+ if (fs.existsSync(path.join(searchDir, filename))) {
103
+ lockfilePm = pm;
104
+ lockfile = filename;
105
+ break;
105
106
  }
106
107
  }
107
-
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;
114
- }
115
- const parent = path.dirname(searchDir);
116
- if (parent === searchDir) break;
117
- searchDir = parent;
118
108
  }
119
109
 
120
- const pm = declaredPm || lockfilePm || "npm";
121
- const fallback = { install: `${pm} install`, run: pm };
122
- const commands = COMMANDS[pm] ?? fallback;
123
-
124
- const parts: string[] = [];
125
- parts.push(`Package manager: ${pm}`);
126
- if (declaredVersion) {
127
- parts.push(`Declared version: ${declaredVersion}`);
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
- };
153
- },
154
-
155
- renderCall(_args: PackageManagerParams, theme: Theme) {
156
- return new ToolCallHeader({ toolName: "Detect Package Manager" }, theme);
157
- },
158
-
159
- renderResult(
160
- result: AgentToolResult<PackageManagerDetails>,
161
- _options: ToolRenderResultOptions,
162
- theme: Theme,
163
- ): Text {
164
- const { details } = result;
165
-
166
- // Check for missing expected fields (framework passes {} on error)
167
- if (!details?.packageManager) {
168
- // Extract error message from result.content
169
- const text = result.content[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);
110
+ // Stop if we found both, hit .git, or hit filesystem root
111
+ if (
112
+ (declaredPm && lockfilePm) ||
113
+ fs.existsSync(path.join(searchDir, ".git"))
114
+ ) {
115
+ break;
175
116
  }
176
-
177
- const lines: string[] = [];
178
- lines.push(
179
- theme.fg(
180
- "success",
181
- `Package manager: ${theme.bold(details.packageManager)}`,
182
- ),
117
+ const parent = path.dirname(searchDir);
118
+ if (parent === searchDir) break;
119
+ searchDir = parent;
120
+ }
121
+
122
+ const pm = declaredPm || lockfilePm || "npm";
123
+ const fallback = { install: `${pm} install`, run: pm };
124
+ const commands = COMMANDS[pm] ?? fallback;
125
+
126
+ const parts: string[] = [];
127
+ parts.push(`Package manager: ${pm}`);
128
+ if (declaredVersion) {
129
+ parts.push(`Declared version: ${declaredVersion}`);
130
+ }
131
+ if (lockfile) {
132
+ parts.push(`Lockfile: ${lockfile}`);
133
+ }
134
+ if (!lockfile && !declaredPm) {
135
+ parts.push(
136
+ "No lockfile or packageManager field found, defaulting to npm",
183
137
  );
184
- if (details.version) {
185
- lines.push(theme.fg("dim", `Version: ${details.version}`));
186
- }
187
- if (details.lockfile) {
188
- lines.push(theme.fg("dim", `Lockfile: ${details.lockfile}`));
189
- }
138
+ }
139
+ parts.push(`Install: ${commands.install}`);
140
+ parts.push(`Run: ${commands.run}`);
141
+
142
+ const message = parts.join("\n");
143
+
144
+ return {
145
+ content: [{ type: "text", text: message }],
146
+ details: {
147
+ packageManager: pm,
148
+ version: declaredVersion,
149
+ lockfile,
150
+ installCommand: commands.install,
151
+ runCommand: commands.run,
152
+ cwd,
153
+ },
154
+ };
155
+ },
156
+
157
+ renderCall(args, theme) {
158
+ return renderPackageManagerCall(args, theme);
159
+ },
160
+
161
+ renderResult(result, options, theme) {
162
+ if (options.isPartial) {
163
+ return new Text(theme.fg("dim", "Package Manager: detecting..."), 0, 0);
164
+ }
165
+
166
+ const { details } = result;
167
+
168
+ // Check for missing expected fields (framework passes {} on error)
169
+ if (!details?.packageManager) {
170
+ // Extract error message from result.content
171
+ const text = result.content[0];
172
+ const errorMessage =
173
+ text?.type === "text" && text.text
174
+ ? text.text
175
+ : "Failed to detect package manager";
176
+ return new Text(theme.fg("error", errorMessage), 0, 0);
177
+ }
178
+
179
+ const lines: string[] = [];
180
+ lines.push(
181
+ theme.fg(
182
+ "success",
183
+ `Package manager: ${theme.bold(details.packageManager)}`,
184
+ ),
185
+ );
186
+ if (details.version) {
187
+ lines.push(theme.fg("dim", `Version: ${details.version}`));
188
+ }
189
+ if (details.lockfile) {
190
+ lines.push(theme.fg("dim", `Lockfile: ${details.lockfile}`));
191
+ }
192
+
193
+ return new Text(lines.join("\n"), 0, 0);
194
+ },
195
+ });
190
196
 
191
- return new Text(lines.join("\n"), 0, 0);
192
- },
193
- });
197
+ export function setupPackageManagerTool(pi: ExtensionAPI) {
198
+ pi.registerTool(packageManagerTool);
194
199
  }