fatfish-world-pi 0.2.0 → 0.2.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # 大肥鱼世界 for Pi
2
2
 
3
- > Renamed from `fightclub-pi` to **`fatfish-world-pi`** in 0.2.0.
3
+ > Renamed from `fightclub-pi` to **`fatfish-world-pi`** in 0.2.3.
4
4
 
5
5
  Pi intentionally has no built-in MCP client. This Pi package adds the
6
6
  大肥鱼世界 tools as a Pi extension and ships the 大肥鱼世界 Skill.
@@ -41,7 +41,10 @@ class LocalMcpClient {
41
41
  const rawArgs = process.env.FATFISH_WORLD_MCP_ARGS;
42
42
  if (rawArgs) {
43
43
  const parsed = JSON.parse(rawArgs);
44
- if (!Array.isArray(parsed) || parsed.some((value) => typeof value !== "string")) {
44
+ if (
45
+ !Array.isArray(parsed) ||
46
+ parsed.some((value) => typeof value !== "string")
47
+ ) {
45
48
  throw new Error("FATFISH_WORLD_MCP_ARGS must be a JSON string array");
46
49
  }
47
50
  this.args = parsed;
@@ -54,17 +57,26 @@ class LocalMcpClient {
54
57
  const child = spawn(this.command, this.args, {
55
58
  stdio: ["pipe", "pipe", "pipe"],
56
59
  env: { ...process.env },
60
+ // Native spawn cannot execute npx.cmd directly on Windows.
61
+ shell: process.platform === "win32",
57
62
  });
58
63
  this.child = child;
59
64
  const lines = createInterface({ input: child.stdout });
60
65
  lines.on("line", (line) => this.onLine(line));
61
- child.stderr.on("data", (chunk) => process.stderr.write(`[fatfish-world] ${chunk}`));
66
+ child.stderr.on("data", (chunk) =>
67
+ process.stderr.write(`[fatfish-world] ${chunk}`),
68
+ );
62
69
  child.on("error", (error) => this.failAll(error));
63
70
  child.on("exit", (code, signal) => {
64
- this.failAll(new Error(`fatfish-world exited (${code ?? signal ?? "unknown"})`));
71
+ this.failAll(
72
+ new Error(`fatfish-world exited (${code ?? signal ?? "unknown"})`),
73
+ );
65
74
  });
66
75
  await new Promise<void>((resolve, reject) => {
67
- const timer = setTimeout(() => reject(new Error("fatfish-world did not start")), 15_000);
76
+ const timer = setTimeout(
77
+ () => reject(new Error("fatfish-world did not start")),
78
+ 15_000,
79
+ );
68
80
  child.once("spawn", () => {
69
81
  clearTimeout(timer);
70
82
  resolve();
@@ -77,9 +89,13 @@ class LocalMcpClient {
77
89
  await this.request("initialize", {
78
90
  protocolVersion: "2025-06-18",
79
91
  capabilities: {},
80
- clientInfo: { name: "pi-fatfish-world", version: "0.2.0" },
92
+ clientInfo: { name: "pi-fatfish-world", version: "0.2.3" },
93
+ });
94
+ this.write({
95
+ jsonrpc: "2.0",
96
+ method: "notifications/initialized",
97
+ params: {},
81
98
  });
82
- this.write({ jsonrpc: "2.0", method: "notifications/initialized", params: {} });
83
99
  }
84
100
 
85
101
  async listTools(): Promise<any[]> {
@@ -87,9 +103,18 @@ class LocalMcpClient {
87
103
  return Array.isArray(result?.tools) ? result.tools : [];
88
104
  }
89
105
 
90
- async callTool(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<any> {
106
+ async callTool(
107
+ name: string,
108
+ args: Record<string, unknown>,
109
+ signal?: AbortSignal,
110
+ ): Promise<any> {
91
111
  if (signal?.aborted) throw new Error("tool call aborted");
92
- return await this.request("tools/call", { name, arguments: args }, 60_000, signal);
112
+ return await this.request(
113
+ "tools/call",
114
+ { name, arguments: args },
115
+ 60_000,
116
+ signal,
117
+ );
93
118
  }
94
119
 
95
120
  private onLine(line: string) {
@@ -104,16 +129,23 @@ class LocalMcpClient {
104
129
  if (!pending) return;
105
130
  clearTimeout(pending.timer);
106
131
  this.pending.delete(message.id);
107
- if (message.error) pending.reject(new Error(message.error.message || "MCP error"));
132
+ if (message.error)
133
+ pending.reject(new Error(message.error.message || "MCP error"));
108
134
  else pending.resolve(message.result);
109
135
  }
110
136
 
111
137
  private write(message: JsonRpc) {
112
- if (!this.child?.stdin.writable) throw new Error("fatfish-world is not running");
138
+ if (!this.child?.stdin.writable)
139
+ throw new Error("fatfish-world is not running");
113
140
  this.child.stdin.write(`${JSON.stringify(message)}\n`);
114
141
  }
115
142
 
116
- private request(method: string, params: unknown, timeoutMs = 15_000, signal?: AbortSignal): Promise<any> {
143
+ private request(
144
+ method: string,
145
+ params: unknown,
146
+ timeoutMs = 15_000,
147
+ signal?: AbortSignal,
148
+ ): Promise<any> {
117
149
  const id = this.nextId++;
118
150
  return new Promise((resolve, reject) => {
119
151
  const timer = setTimeout(() => {
@@ -169,11 +201,19 @@ function contentForPi(result: any) {
169
201
  return [{ type: "text" as const, text: item.text }];
170
202
  }
171
203
  if (item?.type === "image" && typeof item.data === "string") {
172
- return [{ type: "image" as const, data: item.data, mimeType: item.mimeType ?? "image/png" }];
204
+ return [
205
+ {
206
+ type: "image" as const,
207
+ data: item.data,
208
+ mimeType: item.mimeType ?? "image/png",
209
+ },
210
+ ];
173
211
  }
174
212
  return [{ type: "text" as const, text: JSON.stringify(item) }];
175
213
  });
176
- return mapped.length > 0 ? mapped : [{ type: "text" as const, text: "大肥鱼世界 returned no content." }];
214
+ return mapped.length > 0
215
+ ? mapped
216
+ : [{ type: "text" as const, text: "大肥鱼世界 returned no content." }];
177
217
  }
178
218
 
179
219
  export default async function fatfishWorldPi(pi: any) {
@@ -189,13 +229,22 @@ export default async function fatfishWorldPi(pi: any) {
189
229
  label: tool.title || tool.name,
190
230
  description: tool.description || `大肥鱼世界 tool: ${tool.name}`,
191
231
  parameters: tool.inputSchema ?? { type: "object", properties: {} },
192
- async execute(_toolCallId: string, params: Record<string, unknown>, signal: AbortSignal) {
232
+ async execute(
233
+ _toolCallId: string,
234
+ params: Record<string, unknown>,
235
+ signal: AbortSignal,
236
+ ) {
193
237
  const result = await client.callTool(tool.name, params ?? {}, signal);
194
238
  if (result?.isError) {
195
- const message = contentForPi(result).map((item: { text?: string }) => item.text ?? "").join("\n");
239
+ const message = contentForPi(result)
240
+ .map((item: { text?: string }) => item.text ?? "")
241
+ .join("\n");
196
242
  throw new Error(message || "大肥鱼世界 tool failed");
197
243
  }
198
- return { content: contentForPi(result), details: { tool: tool.name } };
244
+ return {
245
+ content: contentForPi(result),
246
+ details: { tool: tool.name },
247
+ };
199
248
  },
200
249
  });
201
250
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fatfish-world-pi",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "description": "fatfish.world (大肥鱼世界) MCP tools and Skill for Pi coding agent.",
5
5
  "type": "module",
6
6
  "keywords": [