codex-computer-use-mcp 0.3.4 → 0.4.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.
Files changed (45) hide show
  1. package/README.md +38 -121
  2. package/SECURITY.md +5 -63
  3. package/dist/audit.d.ts +1 -7
  4. package/dist/audit.js +4 -12
  5. package/dist/audit.js.map +1 -1
  6. package/dist/direct-broker.d.ts +18 -30
  7. package/dist/direct-broker.js +87 -161
  8. package/dist/direct-broker.js.map +1 -1
  9. package/dist/direct-service.d.ts +13 -21
  10. package/dist/direct-service.js +82 -299
  11. package/dist/direct-service.js.map +1 -1
  12. package/dist/mcp-elicitation.d.ts +1 -2
  13. package/dist/mcp-elicitation.js +18 -23
  14. package/dist/mcp-elicitation.js.map +1 -1
  15. package/dist/mcp-server.js +34 -38
  16. package/dist/mcp-server.js.map +1 -1
  17. package/dist/session-executor.d.ts +22 -0
  18. package/dist/session-executor.js +89 -0
  19. package/dist/session-executor.js.map +1 -0
  20. package/dist/tools.d.ts +743 -81
  21. package/dist/tools.js +55 -178
  22. package/dist/tools.js.map +1 -1
  23. package/dist/version.d.ts +1 -0
  24. package/dist/version.js +3 -0
  25. package/dist/version.js.map +1 -0
  26. package/integrations/pi/index.ts +99 -244
  27. package/package.json +13 -26
  28. package/ARCHITECTURE.md +0 -162
  29. package/CHANGELOG.md +0 -19
  30. package/CONTRIBUTING.md +0 -33
  31. package/MIGRATION.md +0 -134
  32. package/PROOF.md +0 -166
  33. package/dist/config.d.ts +0 -12
  34. package/dist/config.js +0 -10
  35. package/dist/config.js.map +0 -1
  36. package/dist/lock.d.ts +0 -17
  37. package/dist/lock.js +0 -137
  38. package/dist/lock.js.map +0 -1
  39. package/dist/system.d.ts +0 -25
  40. package/dist/system.js +0 -213
  41. package/dist/system.js.map +0 -1
  42. package/docs/adr/0001-standalone-code-mode-mcp.md +0 -45
  43. package/integrations/pi/README.md +0 -73
  44. package/integrations/pi/mcp.json.example +0 -15
  45. package/npm-shrinkwrap.json +0 -4827
@@ -1,237 +1,93 @@
1
+ import { mkdtemp, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
1
3
  import path from "node:path";
2
- import { StringEnum } from "@earendil-works/pi-ai";
3
- import { getAgentDir, truncateHead, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
+ import {
5
+ DEFAULT_MAX_BYTES,
6
+ DEFAULT_MAX_LINES,
7
+ formatSize,
8
+ getAgentDir,
9
+ truncateHead,
10
+ type ExtensionAPI,
11
+ } from "@earendil-works/pi-coding-agent";
4
12
  import { Text } from "@earendil-works/pi-tui";
5
- import { Type, type TSchema } from "typebox";
6
- import { executeDirectTool, getDirectStatus } from "../../dist/direct-service.js";
13
+ import { z } from "zod";
14
+ import { getDirectStatus } from "../../dist/direct-service.js";
7
15
  import {
8
- createOfficialDirectToolSession,
9
16
  type DirectBrokerElicitationRequest,
10
17
  type DirectBrokerElicitationResponse,
11
- type OfficialDirectToolSession,
12
18
  } from "../../dist/direct-broker.js";
13
- import { OFFICIAL_TOOL_METADATA, type DirectMethod } from "../../dist/tools.js";
14
-
15
- const App = Type.String({ description: "App name, full app path, or unambiguous bundle identifier" });
16
-
17
- const ToolParameters: Record<DirectMethod, TSchema> = {
18
- list_apps: Type.Object({}, { additionalProperties: false }),
19
- get_app_state: Type.Object({ app: App }, { additionalProperties: false }),
20
- click: Type.Object({
21
- app: App,
22
- click_count: Type.Optional(Type.Integer({ description: "Number of clicks. Defaults to 1" })),
23
- element_index: Type.Optional(Type.String({ description: "Element index to click" })),
24
- mouse_button: Type.Optional(StringEnum(["left", "right", "middle"] as const, { description: "Mouse button to click. Defaults to left." })),
25
- x: Type.Optional(Type.Number({ description: "X coordinate in screenshot pixel coordinates" })),
26
- y: Type.Optional(Type.Number({ description: "Y coordinate in screenshot pixel coordinates" })),
27
- }, { additionalProperties: false }),
28
- perform_secondary_action: Type.Object({
29
- app: App,
30
- element_index: Type.String({ description: "Element identifier" }),
31
- action: Type.String({ description: "Secondary accessibility action name" }),
32
- }, { additionalProperties: false }),
33
- set_value: Type.Object({
34
- app: App,
35
- element_index: Type.String({ description: "Element identifier" }),
36
- value: Type.String({ description: "Value to assign" }),
37
- }, { additionalProperties: false }),
38
- select_text: Type.Object({
39
- app: Type.String({ description: "App name or bundle identifier" }),
40
- element_index: Type.String({ description: "Text element identifier" }),
41
- text: Type.String({ description: "Target text as shown in the accessibility tree" }),
42
- prefix: Type.Optional(Type.String({ description: "Optional text immediately before the target, used to disambiguate repeated matches" })),
43
- selection: Type.Optional(StringEnum(["text", "cursor_before", "cursor_after"] as const, { description: "Whether to select the text or place the cursor before or after it. Defaults to text." })),
44
- suffix: Type.Optional(Type.String({ description: "Optional text immediately after the target, used to disambiguate repeated matches" })),
45
- }, { additionalProperties: false }),
46
- scroll: Type.Object({
47
- app: App,
48
- element_index: Type.String({ description: "Element identifier" }),
49
- direction: Type.String({ description: "Scroll direction: up, down, left, or right" }),
50
- pages: Type.Optional(Type.Number({ description: "Number of pages to scroll. Fractional values are supported. Defaults to 1" })),
51
- }, { additionalProperties: false }),
52
- drag: Type.Object({
53
- app: App,
54
- from_x: Type.Number({ description: "Start X coordinate" }),
55
- from_y: Type.Number({ description: "Start Y coordinate" }),
56
- to_x: Type.Number({ description: "End X coordinate" }),
57
- to_y: Type.Number({ description: "End Y coordinate" }),
58
- }, { additionalProperties: false }),
59
- press_key: Type.Object({ app: App, key: Type.String({ description: "Key or key combination to press" }) }, { additionalProperties: false }),
60
- type_text: Type.Object({ app: App, text: Type.String({ description: "Literal text to type" }) }, { additionalProperties: false }),
61
- };
19
+ import { DirectSessionExecutor } from "../../dist/session-executor.js";
20
+ import {
21
+ TOOL_INPUT_SCHEMAS,
22
+ TOOL_METADATA,
23
+ COMPUTER_USE_METHODS,
24
+ type DirectMethod,
25
+ type JsonObject,
26
+ } from "../../dist/tools.js";
62
27
 
63
- function toolDescription(method: DirectMethod): string {
64
- return OFFICIAL_TOOL_METADATA[method].description;
65
- }
28
+ const jsonObjectSchema = z.record(z.string(), z.json());
29
+ const toolNames = COMPUTER_USE_METHODS.map((method) => `computer_use_${method}`);
66
30
 
67
31
  function titleFor(method: DirectMethod): string {
68
32
  return method.split("_").map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
69
33
  }
70
34
 
71
- const INSPECTION_METHODS = new Set<DirectMethod>(["list_apps", "get_app_state"]);
72
- export const INSPECTION_TOOL_NAMES = [
73
- "computer_use_list_apps",
74
- "computer_use_get_app_state",
75
- ] as const;
76
- export const INTERACTION_TOOL_NAMES = [
77
- "computer_use_click",
78
- "computer_use_perform_secondary_action",
79
- "computer_use_set_value",
80
- "computer_use_select_text",
81
- "computer_use_scroll",
82
- "computer_use_drag",
83
- "computer_use_press_key",
84
- "computer_use_type_text",
85
- ] as const;
86
-
87
- export const COMPUTER_USE_TOOL_NAMES = [
88
- ...INSPECTION_TOOL_NAMES,
89
- ...INTERACTION_TOOL_NAMES,
90
- ] as const;
91
-
92
- export function setInitialComputerUseTools(pi: Pick<ExtensionAPI, "getActiveTools" | "setActiveTools">): void {
93
- pi.setActiveTools([...new Set([...pi.getActiveTools(), ...COMPUTER_USE_TOOL_NAMES])]);
94
- }
95
-
96
- interface SessionExecutorDependencies {
97
- createSession?: typeof createOfficialDirectToolSession;
98
- executeTool?: typeof executeDirectTool;
35
+ interface PiContentResult {
36
+ content: Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }>;
37
+ fullOutputPath?: string;
99
38
  }
100
39
 
101
- /** Keep the signed app-server/client alive across the required state -> action pair. */
102
- export class PiDirectSessionExecutor {
103
- private session: OfficialDirectToolSession | undefined;
104
- private queue: Promise<void> = Promise.resolve();
105
- private readonly dependencies: SessionExecutorDependencies;
106
-
107
- constructor(dependencies: SessionExecutorDependencies = {}) {
108
- this.dependencies = dependencies;
109
- }
110
-
111
- private async closeSession(): Promise<void> {
112
- const session = this.session;
113
- this.session = undefined;
114
- await session?.close();
115
- }
116
-
117
- async execute(
118
- method: DirectMethod,
119
- params: Record<string, unknown>,
120
- dependencies: Parameters<typeof executeDirectTool>[1],
121
- ): Promise<Awaited<ReturnType<typeof executeDirectTool>>> {
122
- const previous = this.queue;
123
- let release!: () => void;
124
- this.queue = new Promise<void>((resolve) => { release = resolve; });
125
- await previous;
126
- try {
127
- const executeTool = this.dependencies.executeTool ?? executeDirectTool;
128
- if (method === "list_apps") return await executeTool({ method, arguments: params }, dependencies);
129
- if (method === "get_app_state") {
130
- await this.closeSession();
131
- const createSession = this.dependencies.createSession ?? createOfficialDirectToolSession;
132
- try {
133
- const response = await executeTool(
134
- { method, arguments: params },
135
- {
136
- ...dependencies,
137
- callTool: async (directMethod, args, options) => {
138
- this.session = await createSession({ supportsOpenAiFormElicitation: true });
139
- return this.session.call(directMethod, args, options);
140
- },
141
- },
142
- );
143
- if (response.isError) await this.closeSession();
144
- return response;
145
- } catch (error) {
146
- await this.closeSession();
147
- throw error;
148
- }
149
- }
150
- const session = this.session;
151
- try {
152
- return await executeTool(
153
- { method, arguments: params },
154
- session ? {
155
- ...dependencies,
156
- callTool: async (directMethod, args, options) => {
157
- const result = await session.call(directMethod, args, options);
158
- await this.closeSession();
159
- return { ...result, brokerCleanupVerified: true };
160
- },
161
- } : dependencies,
162
- );
163
- } finally {
164
- await this.closeSession();
165
- }
166
- } finally {
167
- release();
168
- }
169
- }
170
-
171
- async close(): Promise<void> {
172
- await this.queue;
173
- await this.closeSession();
40
+ export async function toPiContent(content: JsonObject[]): Promise<PiContentResult> {
41
+ const textBlocks = content
42
+ .filter((block) => block.type !== "image")
43
+ .map((block) => String(block.text ?? ""));
44
+ const truncations = textBlocks.map((text) => truncateHead(text, {
45
+ maxLines: DEFAULT_MAX_LINES,
46
+ maxBytes: DEFAULT_MAX_BYTES,
47
+ }));
48
+ let fullOutputPath: string | undefined;
49
+ if (truncations.some((result) => result.truncated)) {
50
+ const tempDir = await mkdtemp(path.join(tmpdir(), "pi-computer-use-"));
51
+ fullOutputPath = path.join(tempDir, "output.txt");
52
+ await writeFile(fullOutputPath, textBlocks.join("\n\n"), { encoding: "utf8", mode: 0o600 });
174
53
  }
175
- }
176
54
 
177
- function toPiContent(content: Array<Record<string, unknown>>): Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }> {
178
- const result: Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }> = [];
55
+ const result: PiContentResult = { content: [] };
56
+ let textIndex = 0;
179
57
  for (const block of content) {
180
58
  if (block.type === "image") {
181
- result.push({ type: "image", data: String(block.data), mimeType: String(block.mimeType) });
59
+ result.content.push({ type: "image", data: String(block.data), mimeType: String(block.mimeType) });
182
60
  continue;
183
61
  }
184
- const original = String(block.text ?? "");
185
- const truncated = truncateHead(original, { maxLines: 2_000, maxBytes: 50 * 1024 });
62
+ const truncated = truncations[textIndex++];
186
63
  const suffix = truncated.truncated
187
- ? `\n\n[Official Computer Use text truncated in-memory: ${truncated.outputLines}/${truncated.totalLines} lines, ${truncated.outputBytes}/${truncated.totalBytes} bytes. No full-output file was written.]`
64
+ ? `\n\n[Official Computer Use text truncated: showing ${truncated.outputLines} of ${truncated.totalLines} lines (${formatSize(truncated.outputBytes)} of ${formatSize(truncated.totalBytes)}). Full output saved to: ${fullOutputPath}]`
188
65
  : "";
189
- result.push({ type: "text", text: `${truncated.content}${suffix}` });
66
+ result.content.push({ type: "text", text: `${truncated.content}${suffix}` });
190
67
  }
68
+ if (fullOutputPath) result.fullOutputPath = fullOutputPath;
191
69
  return result;
192
70
  }
193
71
 
194
- function errorText(content: Array<Record<string, unknown>>): string {
195
- return content
196
- .filter((block) => block.type === "text")
197
- .map((block) => String(block.text ?? ""))
198
- .join("\n")
199
- .slice(0, 2_000) || "Official Computer Use returned an error";
200
- }
201
-
202
- function isRecord(value: unknown): value is Record<string, unknown> {
203
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
204
- }
205
-
206
- function initialFormContent(schema: unknown): Record<string, unknown> {
207
- if (!isRecord(schema) || !isRecord(schema.properties)) return {};
208
- const content: Record<string, unknown> = {};
209
- for (const [key, rawProperty] of Object.entries(schema.properties)) {
210
- if (!isRecord(rawProperty)) continue;
211
- if (rawProperty.default !== undefined) content[key] = rawProperty.default;
212
- else if (Array.isArray(rawProperty.enum) && rawProperty.enum.length > 0) content[key] = rawProperty.enum[0];
213
- else if (rawProperty.type === "boolean") content[key] = false;
214
- else if (rawProperty.type === "number" || rawProperty.type === "integer") content[key] = 0;
215
- else if (rawProperty.type === "array") content[key] = [];
216
- else content[key] = "";
217
- }
218
- return content;
72
+ interface PiElicitationContext {
73
+ hasUI: boolean;
74
+ ui: {
75
+ select(title: string, options: string[]): Promise<string | undefined>;
76
+ editor(title: string, prefill?: string): Promise<string | undefined>;
77
+ notify(message: string, type?: "info" | "warning" | "error"): void;
78
+ };
219
79
  }
220
80
 
221
81
  export async function handleOfficialElicitation(
222
82
  request: DirectBrokerElicitationRequest,
223
- ctx: { hasUI: boolean; ui: {
224
- select(title: string, options: string[]): Promise<string | undefined>;
225
- editor(title: string, prefill?: string): Promise<string | undefined>;
226
- notify(message: string, type?: "info" | "warning" | "error"): void;
227
- } },
83
+ ctx: PiElicitationContext,
228
84
  openUrl: (url: string) => Promise<boolean>,
229
85
  ): Promise<DirectBrokerElicitationResponse> {
230
86
  if (!ctx.hasUI) return { action: "cancel" };
231
- const message = typeof request.message === "string" ? request.message : "Official Computer Use requests input";
87
+ const message = request.message ?? "Official Computer Use requests input";
232
88
 
233
89
  if (request.mode === "url") {
234
- if (typeof request.url !== "string") return { action: "cancel" };
90
+ if (!request.url) return { action: "cancel" };
235
91
  const choice = await ctx.ui.select(`${message}\n${request.url}`, ["Open URL", "Decline", "Cancel"]);
236
92
  if (choice === "Decline") return { action: "decline" };
237
93
  if (choice !== "Open URL") return { action: "cancel" };
@@ -247,19 +103,21 @@ export async function handleOfficialElicitation(
247
103
  return { action: "cancel" };
248
104
  }
249
105
  const openAiForm = request.mode === "openai/form";
250
- if (request.requestedSchema === undefined || (!openAiForm && !isRecord(request.requestedSchema))) return { action: "cancel" };
106
+ if (request.requestedSchema === undefined || (!openAiForm && !jsonObjectSchema.safeParse(request.requestedSchema).success)) {
107
+ return { action: "cancel" };
108
+ }
251
109
  const choice = await ctx.ui.select(message, ["Respond", "Decline", "Cancel"]);
252
110
  if (choice === "Decline") return { action: "decline" };
253
111
  if (choice !== "Respond") return { action: "cancel" };
254
- let prefill = JSON.stringify(initialFormContent(request.requestedSchema), null, 2);
255
112
  const title = `${message}\nSchema: ${JSON.stringify(request.requestedSchema)}`;
113
+ let prefill = "{}";
256
114
  while (true) {
257
115
  const edited = await ctx.ui.editor(title, prefill);
258
116
  if (edited === undefined) return { action: "cancel" };
259
117
  prefill = edited;
260
118
  try {
261
- const content = JSON.parse(edited);
262
- if (!openAiForm && !isRecord(content)) throw new Error("Response must be a JSON object");
119
+ const parsed = JSON.parse(edited);
120
+ const content = openAiForm ? parsed : jsonObjectSchema.parse(parsed);
263
121
  return { action: "accept", content };
264
122
  } catch (error) {
265
123
  ctx.ui.notify(error instanceof Error ? error.message : "Response must be valid JSON", "error");
@@ -269,62 +127,59 @@ export async function handleOfficialElicitation(
269
127
 
270
128
  export default function directComputerUse(pi: ExtensionAPI) {
271
129
  const stateRoot = process.env.CODEX_COMPUTER_USE_HOME || path.join(getAgentDir(), "direct-computer-use");
272
- const sessionExecutor = new PiDirectSessionExecutor();
130
+ const sessionExecutor = new DirectSessionExecutor();
273
131
 
274
132
  pi.registerCommand("computer-use-status", {
275
- description: "Show the direct-call architecture, durable no-permissions policy, signed broker, and private audit path",
133
+ description: "Show Computer Use status",
276
134
  handler: async (_args, ctx) => {
277
- const status = await getDirectStatus(stateRoot);
278
- ctx.ui.notify(JSON.stringify(status, null, 2), "info");
135
+ ctx.ui.notify(JSON.stringify(getDirectStatus(stateRoot), null, 2), "info");
279
136
  },
280
137
  });
281
138
 
282
- const methods = Object.keys(ToolParameters) as DirectMethod[];
283
- for (const method of methods) {
139
+ for (const method of COMPUTER_USE_METHODS) {
284
140
  const piName = `computer_use_${method}`;
285
- const inspectionPromptMetadata = INSPECTION_METHODS.has(method) ? {
286
- promptSnippet: `${titleFor(method)} through the official signed macOS Computer Use service`,
287
- promptGuidelines: [
288
- `${piName} is a direct typed tool: choose its arguments yourself; it does not invoke a nested planner or model.`,
289
- `Call computer_use_get_app_state once per assistant turn before interacting with an app.`,
290
- ],
291
- } : {};
292
141
  pi.registerTool({
293
142
  name: piName,
294
143
  label: titleFor(method),
295
- description: toolDescription(method),
296
- ...inspectionPromptMetadata,
297
- parameters: ToolParameters[method] as any,
298
- async execute(_toolCallId, params, signal, onUpdate, ctx) {
299
- const response = await sessionExecutor.execute(
300
- method,
301
- params as Record<string, unknown>,
302
- {
303
- stateRoot,
304
- signal,
305
- supportsOpenAiFormElicitation: true,
306
- onElicitation: (request) => handleOfficialElicitation(
307
- request,
308
- ctx,
309
- async (url) => {
310
- const result = await pi.exec("/usr/bin/open", ["--", url], { signal, timeout: 15_000 });
311
- return result.code === 0;
312
- },
313
- ),
314
- onProgress: (message) => onUpdate?.({ content: [{ type: "text", text: message }], details: { status: "running" } }),
315
- },
316
- );
317
- if (response.isError) throw new Error(errorText(response.content));
318
- return { content: toPiContent(response.content), details: response.details };
144
+ description: TOOL_METADATA[method].description,
145
+ // SAFETY: Pi accepts standard JSON Schema objects; these schemas are generated directly from the tool's Zod parser.
146
+ parameters: TOOL_INPUT_SCHEMAS[method] as any,
147
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
148
+ const parsedParams = jsonObjectSchema.parse(params);
149
+ const response = await sessionExecutor.execute(method, parsedParams, {
150
+ stateRoot,
151
+ signal,
152
+ supportsOpenAiFormElicitation: true,
153
+ onElicitation: (request) => handleOfficialElicitation(
154
+ request,
155
+ ctx,
156
+ async (url) => (await pi.exec("/usr/bin/open", ["--", url], { signal, timeout: 15_000 })).code === 0,
157
+ ),
158
+ });
159
+ if (response.isError) {
160
+ const message = response.content
161
+ .filter((block) => block.type === "text")
162
+ .map((block) => String(block.text ?? ""))
163
+ .join("\n")
164
+ .slice(0, 2_000);
165
+ throw new Error(message || "Official Computer Use returned an error");
166
+ }
167
+ const rendered = await toPiContent(response.content);
168
+ const details: JsonObject = {};
169
+ if (response.structuredContent !== undefined) details.officialStructuredContent = response.structuredContent;
170
+ if (rendered.fullOutputPath) details.fullOutputPath = rendered.fullOutputPath;
171
+ return { content: rendered.content, details };
319
172
  },
320
173
  renderCall(args, theme) {
321
- const renderedArgs = args as Record<string, unknown>;
322
- const app = typeof renderedArgs.app === "string" ? ` ${renderedArgs.app}` : "";
323
- return new Text(theme.fg("toolTitle", theme.bold(`${piName} `)) + theme.fg("accent", app.trim()), 0, 0);
174
+ const parsed = z.object({ app: z.string().optional() }).safeParse(args);
175
+ const app = parsed.success ? parsed.data.app : undefined;
176
+ const label = theme.fg("toolTitle", theme.bold(piName));
177
+ return new Text(app ? `${label} ${theme.fg("accent", app)}` : label, 0, 0);
324
178
  },
325
179
  });
326
180
  }
327
181
 
328
- pi.on("session_start", () => setInitialComputerUseTools(pi));
182
+ pi.on("session_start", () => pi.setActiveTools([...new Set([...pi.getActiveTools(), ...toolNames])]));
183
+ pi.on("agent_settled", () => sessionExecutor.close());
329
184
  pi.on("session_shutdown", () => sessionExecutor.close());
330
185
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-computer-use-mcp",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "Expose the official macOS Computer Use tools directly to Pi and MCP clients without a nested model.",
5
5
  "author": "Thomas Mustier",
6
6
  "license": "MIT",
@@ -28,57 +28,44 @@
28
28
  },
29
29
  "files": [
30
30
  "dist",
31
- "integrations/pi",
32
- "npm-shrinkwrap.json",
31
+ "integrations/pi/index.ts",
33
32
  "README.md",
34
- "CHANGELOG.md",
35
- "PROOF.md",
36
- "ARCHITECTURE.md",
37
- "MIGRATION.md",
38
33
  "SECURITY.md",
39
- "docs/adr",
40
- "CONTRIBUTING.md",
41
34
  "LICENSE"
42
35
  ],
43
36
  "scripts": {
44
37
  "build": "rm -rf dist && tsc -p tsconfig.json",
45
- "check": "tsc -p tsconfig.json --noEmit",
38
+ "lint": "oxlint --deny-warnings",
39
+ "check": "npm run lint && tsc -p tsconfig.json --noEmit",
40
+ "pretest": "npm run build",
46
41
  "test": "node --test test/*.test.ts",
47
- "prepare": "npm run build",
48
- "prepublishOnly": "npm run check && npm run check:pi && npm test && npm run build",
49
- "check:pi": "npx tsc --ignoreConfig --noEmit --target ES2022 --module NodeNext --moduleResolution NodeNext --strict --skipLibCheck --types node integrations/pi/index.ts"
42
+ "prepack": "npm run build",
43
+ "prepublishOnly": "npm run check && npm run check:pi && npm test",
44
+ "check:pi": "npm run build && npx tsc --ignoreConfig --noEmit --target ES2022 --module NodeNext --moduleResolution NodeNext --strict --skipLibCheck --types node integrations/pi/index.ts"
50
45
  },
51
46
  "dependencies": {
52
47
  "@modelcontextprotocol/sdk": "1.29.0",
53
- "@types/node": "24.13.3",
54
- "typescript": "7.0.2",
55
48
  "zod": "4.4.3"
56
49
  },
57
50
  "peerDependencies": {
58
- "@earendil-works/pi-ai": "*",
59
51
  "@earendil-works/pi-coding-agent": "*",
60
- "@earendil-works/pi-tui": "*",
61
- "typebox": "*"
52
+ "@earendil-works/pi-tui": "*"
62
53
  },
63
54
  "peerDependenciesMeta": {
64
- "@earendil-works/pi-ai": {
65
- "optional": true
66
- },
67
55
  "@earendil-works/pi-coding-agent": {
68
56
  "optional": true
69
57
  },
70
58
  "@earendil-works/pi-tui": {
71
59
  "optional": true
72
- },
73
- "typebox": {
74
- "optional": true
75
60
  }
76
61
  },
77
62
  "devDependencies": {
78
- "@earendil-works/pi-ai": "0.80.7",
79
63
  "@earendil-works/pi-coding-agent": "0.80.7",
80
64
  "@earendil-works/pi-tui": "0.80.7",
81
- "typebox": "1.1.38"
65
+ "@oxlint/plugins": "1.79.0",
66
+ "@types/node": "24.13.3",
67
+ "oxlint": "1.79.0",
68
+ "typescript": "7.0.2"
82
69
  },
83
70
  "engines": {
84
71
  "node": ">=22"