surf-cli 2.11.0 → 2.13.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.
@@ -290,6 +290,16 @@ const TOOL_SCHEMAS = {
290
290
  timeout: z.number().optional().describe("Timeout in seconds")
291
291
  }
292
292
  },
293
+ kimi: {
294
+ desc: "Ask Kimi AI (kimi.com, Moonshot K-series) through the browser session",
295
+ schema: {
296
+ query: z.string().optional().describe("Question or prompt"),
297
+ model: z.string().optional().describe("Model: instant (default), thinking, high, or any label in kimi.com's picker"),
298
+ "with-page": z.boolean().optional().describe("Include current page context"),
299
+ timeout: z.number().optional().describe("Timeout in seconds"),
300
+ validate: z.boolean().optional().describe("Check kimi.com UI and list available models")
301
+ }
302
+ },
293
303
  "network.export": {
294
304
  desc: "Export captured network requests",
295
305
  schema: {
@@ -48,7 +48,7 @@ function runSpec(argv) {
48
48
  const parsed = parseCommandArgs(argv.slice(offset));
49
49
  const [playbook, op] = parsed.positional;
50
50
  if (!playbook || !op) throw new Error(direct ? "Usage: surf use <playbook> <op> [--arg value]" : "Usage: surf pb run <playbook> <op> [--arg value]");
51
- const reserved = new Set(["json", "no-lock", "tab-id", "write", "repeat", "retry-attempt", "override-in-doubt", "pin-built-in"]);
51
+ const reserved = new Set(["json", "no-lock", "tab-id", "write", "repeat", "retry-attempt", "override-in-doubt", "pin-built-in", "allow-script"]);
52
52
  const args = Object.fromEntries(Object.entries(parsed.options).filter(([name]) => !reserved.has(name)));
53
53
  return { playbook, op, args, options: parsed.options };
54
54
  }
@@ -89,6 +89,7 @@ async function handlePlaybookCli(argv, { endpoint, cwd = process.cwd() }) {
89
89
  retryAttempt: spec.options["retry-attempt"],
90
90
  overrideInDoubt: spec.options["override-in-doubt"] === true,
91
91
  pinBuiltIn: spec.options["pin-built-in"] === true,
92
+ allowScript: spec.options["allow-script"] === true,
92
93
  };
93
94
  const value = await requestHost(endpoint, "playbook.run", args, {
94
95
  tabId: spec.options["tab-id"],
@@ -98,7 +99,7 @@ async function handlePlaybookCli(argv, { endpoint, cwd = process.cwd() }) {
98
99
  }
99
100
  const command = argv[1];
100
101
  const parsed = parseCommandArgs(argv.slice(2));
101
- if (!command || command === "help") return { handled: true, value: "Usage: surf playbook|pb <list|show|ops|run|record|suggest|save|client|trace|export|import>" };
102
+ if (!command || command === "help") return { handled: true, value: "Usage: surf playbook|pb <list|show|ops|run|record|suggest|save|client|trace|export|import>\nRun trusted script strategies with: surf use <playbook> <op> --allow-script" };
102
103
  if (endpoint?.kind === "remote" && ["list", "show", "ops"].includes(command)) throw new Error(`playbook ${command} is local-only with --remote because runs resolve on the browser host`);
103
104
  if (command === "list") return { handled: true, value: listPlaybooks({ cwd }), json: parsed.options.json === true };
104
105
  if (command === "show") {
@@ -1,4 +1,5 @@
1
- const { executeWorkflow } = require("./workflow-runtime.cjs");
1
+ const { executeSingleStep, executeWorkflow } = require("./workflow-runtime.cjs");
2
+ const { runWorkflowScript } = require("./workflow-script-runtime.cjs");
2
3
 
3
4
  function applyTemplate(value, args) {
4
5
  if (typeof value === "string") {
@@ -93,6 +94,31 @@ return { status: response.status, ok: response.ok, url: response.url, headers: O
93
94
  }
94
95
 
95
96
  async function runStrategy(strategy, context) {
97
+ if (strategy.using === "script") {
98
+ if (context.allowScript !== true) throw new Error("script strategy requires --allow-script");
99
+ const vars = { ...context.args };
100
+ const result = await runWorkflowScript({
101
+ script: strategy.script,
102
+ input: context.args,
103
+ timeoutMs: strategy.timeoutMs ?? 10 * 60 * 1000,
104
+ signal: context.signal,
105
+ onEvent: context.onEvent,
106
+ executeTool: async (tool, args, options = {}) => {
107
+ await context.markDispatched?.();
108
+ const step = await executeSingleStep({ cmd: tool, args, as: "value" }, vars, {
109
+ autoWait: strategy.autoWait !== false,
110
+ executeTool: context.executeTool,
111
+ onEvent: context.onEvent,
112
+ signal: options.signal || context.signal,
113
+ sleep: context.sleep,
114
+ stepDelay: strategy.stepDelay ?? 100,
115
+ });
116
+ if (!step.success) throw new Error(step.error || `tool ${tool} failed`);
117
+ return step.output;
118
+ },
119
+ });
120
+ return result.value;
121
+ }
96
122
  if (strategy.using === "workflow") {
97
123
  const result = await executeWorkflow(applyTemplate(strategy.steps, context.args), {
98
124
  autoWait: strategy.autoWait !== false,
@@ -130,7 +156,7 @@ async function runStrategy(strategy, context) {
130
156
  throw new Error(`unsupported strategy: ${strategy.using}`);
131
157
  }
132
158
 
133
- async function runPlaybookOp({ playbook, op, args: providedArgs = {}, attemptId, executeTool, executeNative, signal, sleep, onEvent = () => {}, beforeDispatch = async () => {}, afterDispatch = async () => {} }) {
159
+ async function runPlaybookOp({ playbook, op, args: providedArgs = {}, attemptId, executeTool, executeNative, signal, sleep, onEvent = () => {}, beforeDispatch = async () => {}, afterDispatch = async () => {}, allowScript = false }) {
134
160
  const args = resolveArgs(op, providedArgs);
135
161
  const attempts = [];
136
162
  for (let index = 0; index < op.run.length; index++) {
@@ -154,6 +180,7 @@ async function runPlaybookOp({ playbook, op, args: providedArgs = {}, attemptId,
154
180
  allowedOrigins: op.origins || playbook.origins || [],
155
181
  markDispatched: op.effect === "write" ? markDispatched : undefined,
156
182
  serverIdempotency: op.effect === "write" ? op.safety?.serverIdempotency : undefined,
183
+ allowScript,
157
184
  });
158
185
  const value = extractResult(raw, strategy.extract);
159
186
  verifyResult(value, strategy.verify || strategy.expect || op.on?.success?.expect, raw);
@@ -75,8 +75,21 @@ function validateWriteWorkflowSteps(steps, opId) {
75
75
  }
76
76
  }
77
77
 
78
+ function normalizeScript(value) {
79
+ if (typeof value === "string") return value;
80
+ if (Array.isArray(value) && value.every((line) => typeof line === "string")) return value.join("\n");
81
+ return null;
82
+ }
83
+
78
84
  function validateStrategy(strategy, effect, opId) {
79
85
  if (!strategy || typeof strategy !== "object" || Array.isArray(strategy)) throw new Error("playbook strategy must be an object");
86
+ if (strategy.using === "script") {
87
+ if (effect === "write") throw new Error(`write op ${opId} script strategy is not supported`);
88
+ const script = normalizeScript(strategy.script);
89
+ if (!script?.trim()) throw new Error("script strategy requires script");
90
+ if (strategy.timeoutMs !== undefined && (!Number.isInteger(strategy.timeoutMs) || strategy.timeoutMs < 1)) throw new Error("script strategy timeoutMs must be a positive integer");
91
+ return { ...strategy, script };
92
+ }
80
93
  if (strategy.using === "workflow") {
81
94
  if (!Array.isArray(strategy.steps) || strategy.steps.length === 0) throw new Error("workflow strategy requires steps");
82
95
  const steps = strategy.steps.map(normalizeStep);
@@ -7,6 +7,7 @@ const COMMANDS = {
7
7
  ai: { primaryArg: "query", effect: "read", argKinds: { query: "user-input" }, sensitiveArgs: ["query"] },
8
8
  gemini: { primaryArg: "query", effect: "page-write", argKinds: { query: "user-input" }, sensitiveArgs: ["query"] },
9
9
  chatgpt: { primaryArg: "query", effect: "page-write", argKinds: { query: "user-input" }, sensitiveArgs: ["query"] },
10
+ kimi: { primaryArg: "query", effect: "page-write", argKinds: { query: "user-input" }, sensitiveArgs: ["query"] },
10
11
  "oracle.ask": { primaryArg: "prompt", effect: "page-write", argKinds: { prompt: "user-input" }, sensitiveArgs: ["prompt"] },
11
12
  perplexity: { primaryArg: "query", effect: "page-write", argKinds: { query: "user-input" }, sensitiveArgs: ["query"] },
12
13
  grok: { primaryArg: "query", effect: "page-write", argKinds: { query: "user-input" }, sensitiveArgs: ["query"] },
@@ -0,0 +1,294 @@
1
+ const { Worker } = require("node:worker_threads");
2
+
3
+ const KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
4
+
5
+ const WORKER_SOURCE = String.raw`
6
+ const { parentPort } = require("node:worker_threads");
7
+ const vm = require("node:vm");
8
+ const { inspect } = require("node:util");
9
+
10
+ let nextCallId = 0;
11
+ const pending = new Map();
12
+ const keyPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
13
+ const fingerprints = new Map();
14
+ let contextObjectPrototype;
15
+
16
+ function stableJson(value) {
17
+ if (Array.isArray(value)) return "[" + value.map(stableJson).join(",") + "]";
18
+ if (value && typeof value === "object") return "{" + Object.keys(value).sort().map((key) => JSON.stringify(key) + ":" + stableJson(value[key])).join(",") + "}";
19
+ return JSON.stringify(value) ?? "undefined";
20
+ }
21
+
22
+ function assertJsonValue(value, path = "value", seen = new Set()) {
23
+ if (value === null || typeof value === "string" || typeof value === "boolean") return;
24
+ if (typeof value === "number") {
25
+ if (!Number.isFinite(value)) throw new Error(path + " must contain only finite JSON numbers.");
26
+ return;
27
+ }
28
+ if (typeof value !== "object") throw new Error(path + " must be a JSON value; received " + typeof value + ".");
29
+ if (seen.has(value)) throw new Error(path + " must not contain cycles.");
30
+ seen.add(value);
31
+ if (Array.isArray(value)) {
32
+ for (let index = 0; index < value.length; index++) {
33
+ if (!Object.prototype.hasOwnProperty.call(value, index)) throw new Error(path + " must not contain sparse array entries.");
34
+ assertJsonValue(value[index], path + "[" + index + "]", seen);
35
+ }
36
+ } else {
37
+ const prototype = Object.getPrototypeOf(value);
38
+ if (prototype !== null && prototype !== Object.prototype && prototype !== contextObjectPrototype) throw new Error(path + " must contain only plain JSON objects.");
39
+ if (Object.getOwnPropertySymbols(value).length > 0) throw new Error(path + " must not contain symbol keys.");
40
+ for (const [key, entry] of Object.entries(value)) assertJsonValue(entry, path + "." + key, seen);
41
+ }
42
+ seen.delete(value);
43
+ }
44
+
45
+ function hostCall(method, args) {
46
+ return new Promise((resolve, reject) => {
47
+ const callId = ++nextCallId;
48
+ pending.set(callId, { resolve, reject });
49
+ parentPort.postMessage({ type: "call", callId, method, args });
50
+ });
51
+ }
52
+
53
+ function validateRunCall(key, params, label, nextFingerprints = fingerprints) {
54
+ if (typeof key !== "string" || !keyPattern.test(key)) throw new Error(label + " has an invalid key.");
55
+ if (!params || typeof params !== "object" || Array.isArray(params)) throw new Error(label + " requires a params object.");
56
+ const tool = params.tool ?? params.cmd;
57
+ if (typeof tool !== "string" || !tool) throw new Error(label + " requires a tool string.");
58
+ if (params.args !== undefined && (!params.args || typeof params.args !== "object" || Array.isArray(params.args))) throw new Error(label + " args must be an object.");
59
+ assertJsonValue(params, label + " params");
60
+ const fingerprint = stableJson(params);
61
+ const existing = nextFingerprints.get(key);
62
+ if (existing !== undefined && existing !== fingerprint) throw new Error("Duplicate script key '" + key + "' used with incompatible tool params.");
63
+ nextFingerprints.set(key, fingerprint);
64
+ }
65
+
66
+ const tools = Object.freeze({
67
+ run(key, params) {
68
+ validateRunCall(key, params, "tools.run");
69
+ return hostCall("run", { key, params });
70
+ },
71
+ all(items) {
72
+ if (!Array.isArray(items)) throw new Error("tools.all(items) requires an array.");
73
+ const nextFingerprints = new Map(fingerprints);
74
+ const calls = [];
75
+ for (let index = 0; index < items.length; index++) {
76
+ if (!Object.prototype.hasOwnProperty.call(items, index)) throw new Error("tools.all items must not contain sparse entries.");
77
+ const item = items[index];
78
+ if (!item || typeof item !== "object" || Array.isArray(item)) throw new Error("tools.all item " + index + " must be an object.");
79
+ const { key, ...params } = item;
80
+ validateRunCall(key, params, "tools.all item " + index, nextFingerprints);
81
+ calls.push({ key, params });
82
+ }
83
+ for (const { key, params } of calls) fingerprints.set(key, stableJson(params));
84
+ return Promise.all(calls.map(({ key, params }) => hostCall("run", { key, params, collectFailure: true })));
85
+ },
86
+ ref(result) {
87
+ if (!result || typeof result !== "object") throw new Error("tools.ref(result) requires a tool result object.");
88
+ return "[tool " + (result.key || "unknown") + "]";
89
+ },
90
+ refs(results) {
91
+ if (!Array.isArray(results)) throw new Error("tools.refs(results) requires an array.");
92
+ return results.map(tools.ref).join("\n");
93
+ },
94
+ });
95
+
96
+ const capturedConsole = Object.freeze(Object.fromEntries(
97
+ ["log", "info", "warn", "error"].map((level) => [level, (...args) => {
98
+ parentPort.postMessage({ type: "console", level, text: args.map((value) => typeof value === "string" ? value : inspect(value, { depth: 4, breakLength: 120 })).join(" ") });
99
+ }]),
100
+ ));
101
+
102
+ parentPort.on("message", async (message) => {
103
+ if (message.type === "response") {
104
+ const entry = pending.get(message.callId);
105
+ if (!entry) return;
106
+ pending.delete(message.callId);
107
+ if (message.ok) entry.resolve(message.value);
108
+ else entry.reject(new Error(message.error));
109
+ return;
110
+ }
111
+ if (message.type !== "start") return;
112
+ try {
113
+ const sandbox = {
114
+ input: Object.freeze(message.input ?? {}),
115
+ tools,
116
+ surf: tools,
117
+ emit(value) { assertJsonValue(value, "emit"); parentPort.postMessage({ type: "emit", value }); },
118
+ console: capturedConsole,
119
+ };
120
+ const context = vm.createContext(sandbox, { codeGeneration: { strings: false, wasm: false } });
121
+ contextObjectPrototype = vm.runInContext("Object.prototype", context);
122
+ const compiled = new vm.Script("(async () => {\n" + message.script + "\n})()", { filename: "surf-workflow-script.js" });
123
+ const value = await compiled.runInContext(context);
124
+ const persistedValue = value === undefined ? null : value;
125
+ assertJsonValue(persistedValue, "return");
126
+ parentPort.postMessage({ type: "complete", value: persistedValue });
127
+ } catch (error) {
128
+ parentPort.postMessage({ type: "error", error: error && error.stack ? error.stack : String(error) });
129
+ }
130
+ });
131
+ `;
132
+
133
+ function isRecord(value) {
134
+ return !!value && typeof value === "object" && !Array.isArray(value);
135
+ }
136
+
137
+ function assertJsonValue(value, path = "value", seen = new Set()) {
138
+ if (value === null || typeof value === "string" || typeof value === "boolean") return;
139
+ if (typeof value === "number") {
140
+ if (!Number.isFinite(value)) throw new Error(`${path} must contain only finite JSON numbers.`);
141
+ return;
142
+ }
143
+ if (typeof value !== "object") throw new Error(`${path} must be a JSON value; received ${typeof value}.`);
144
+ if (seen.has(value)) throw new Error(`${path} must not contain cycles.`);
145
+ seen.add(value);
146
+ if (Array.isArray(value)) {
147
+ for (let index = 0; index < value.length; index++) {
148
+ if (!Object.hasOwn(value, index)) throw new Error(`${path} must not contain sparse array entries.`);
149
+ assertJsonValue(value[index], `${path}[${index}]`, seen);
150
+ }
151
+ } else {
152
+ const prototype = Object.getPrototypeOf(value);
153
+ if (prototype !== null && prototype !== Object.prototype) throw new Error(`${path} must contain only plain JSON objects.`);
154
+ if (Object.getOwnPropertySymbols(value).length > 0) throw new Error(`${path} must not contain symbol keys.`);
155
+ for (const [key, entry] of Object.entries(value)) assertJsonValue(entry, `${path}.${key}`, seen);
156
+ }
157
+ seen.delete(value);
158
+ }
159
+
160
+ function omitUndefined(value, seen = new Set()) {
161
+ if (value === null || typeof value !== "object") return value;
162
+ if (seen.has(value)) return value;
163
+ seen.add(value);
164
+ const next = Array.isArray(value)
165
+ ? value.map((entry) => entry === undefined ? null : omitUndefined(entry, seen))
166
+ : Object.fromEntries(Object.entries(value).flatMap(([key, entry]) => entry === undefined ? [] : [[key, omitUndefined(entry, seen)]]));
167
+ seen.delete(value);
168
+ return next;
169
+ }
170
+
171
+ function validateKey(value) {
172
+ if (typeof value !== "string" || !KEY_PATTERN.test(value)) {
173
+ throw new Error("tool key must be 1-128 characters using letters, numbers, '.', '_' or '-', and start with a letter or number.");
174
+ }
175
+ return value;
176
+ }
177
+
178
+ async function runWorkflowScript({ script, input = {}, timeoutMs = 10 * 60 * 1000, signal, executeTool, onEvent = () => {}, onEmit = () => {}, onConsole = () => {} }) {
179
+ if (typeof script !== "string" || !script.trim()) throw new Error("script strategy requires a non-empty script string");
180
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 1) throw new Error("script timeoutMs must be a positive integer");
181
+ if (typeof executeTool !== "function") throw new Error("script strategy requires executeTool");
182
+ assertJsonValue(input, "input");
183
+
184
+ const worker = new Worker(WORKER_SOURCE, { eval: true });
185
+ const emits = [];
186
+ const consoleEntries = [];
187
+ const trace = [];
188
+ const childController = new AbortController();
189
+ let settled = false;
190
+
191
+ return await new Promise((resolve, reject) => {
192
+ const finish = (outcome) => {
193
+ if (settled) return;
194
+ settled = true;
195
+ clearTimeout(timer);
196
+ signal?.removeEventListener("abort", onAbort);
197
+ void worker.terminate();
198
+ childController.abort(outcome.error || new Error("Script strategy completed."));
199
+ if (outcome.error) reject(outcome.error);
200
+ else resolve({ value: outcome.value, emits, console: consoleEntries, trace });
201
+ };
202
+ const onAbort = () => finish({ error: new Error(signal.reason instanceof Error ? signal.reason.message : String(signal.reason || "Script strategy aborted")) });
203
+ const timer = setTimeout(() => finish({ error: new Error(`Script strategy timed out after ${timeoutMs}ms.`) }), timeoutMs);
204
+ signal?.addEventListener("abort", onAbort, { once: true });
205
+ if (signal?.aborted) return onAbort();
206
+
207
+ const respond = (callId, promise) => {
208
+ void promise.then(
209
+ (value) => {
210
+ try {
211
+ const clean = omitUndefined(value);
212
+ assertJsonValue(clean, "tool result");
213
+ worker.postMessage({ type: "response", callId, ok: true, value: clean });
214
+ } catch (error) {
215
+ worker.postMessage({ type: "response", callId, ok: false, error: error instanceof Error ? error.message : String(error) });
216
+ }
217
+ },
218
+ (error) => worker.postMessage({ type: "response", callId, ok: false, error: error instanceof Error ? error.message : String(error) }),
219
+ );
220
+ };
221
+
222
+ worker.on("error", (error) => finish({ error: new Error(`Script worker failed: ${error.message}`) }));
223
+ worker.on("exit", (code) => {
224
+ if (!settled && code !== 0) finish({ error: new Error(`Script worker exited with code ${code}.`) });
225
+ });
226
+ worker.on("message", (message) => {
227
+ if (message.type === "emit") {
228
+ try {
229
+ assertJsonValue(message.value, "emit");
230
+ emits.push(message.value);
231
+ onEmit([...emits]);
232
+ } catch (error) {
233
+ finish({ error: new Error(`Script emit could not be persisted: ${error.message}`) });
234
+ }
235
+ return;
236
+ }
237
+ if (message.type === "console") {
238
+ if (["log", "info", "warn", "error"].includes(message.level) && typeof message.text === "string") {
239
+ const entry = { level: message.level, text: message.text };
240
+ consoleEntries.push(entry);
241
+ onConsole(entry);
242
+ }
243
+ return;
244
+ }
245
+ if (message.type === "complete") {
246
+ try {
247
+ assertJsonValue(message.value, "return");
248
+ finish({ value: message.value });
249
+ } catch (error) {
250
+ finish({ error: new Error(`Script return could not be persisted: ${error.message}`) });
251
+ }
252
+ return;
253
+ }
254
+ if (message.type === "error") return finish({ error: new Error(typeof message.error === "string" ? message.error : "Script strategy failed.") });
255
+ if (message.type !== "call" || typeof message.callId !== "number" || message.method !== "run" || !isRecord(message.args)) return;
256
+ let key;
257
+ try {
258
+ key = validateKey(message.args.key);
259
+ } catch (error) {
260
+ return respond(message.callId, Promise.reject(error));
261
+ }
262
+ const params = message.args.params;
263
+ if (!isRecord(params)) return respond(message.callId, Promise.reject(new Error(`tools.run('${key}', params) requires a params object.`)));
264
+ const tool = params.tool ?? params.cmd;
265
+ if (typeof tool !== "string" || !tool) return respond(message.callId, Promise.reject(new Error(`tools.run('${key}') requires a tool string.`)));
266
+ const args = params.args === undefined ? {} : params.args;
267
+ if (!isRecord(args)) return respond(message.callId, Promise.reject(new Error(`tools.run('${key}') args must be an object.`)));
268
+ const collectFailure = message.args.collectFailure === true;
269
+ const startedAt = Date.now();
270
+ trace.push({ key, tool, state: "started" });
271
+ onEvent({ type: "script.tool.started", key, tool, startedAt: new Date().toISOString() });
272
+ respond(message.callId, Promise.resolve().then(async () => {
273
+ try {
274
+ const output = await executeTool(tool, args, { signal: childController.signal });
275
+ const result = { key, tool, ok: true, output };
276
+ trace.push({ key, tool, state: "completed", durationMs: Date.now() - startedAt });
277
+ onEvent({ type: "script.tool.completed", key, tool, endedAt: new Date().toISOString() });
278
+ return result;
279
+ } catch (error) {
280
+ const text = error instanceof Error ? error.message : String(error);
281
+ const result = { key, tool, ok: false, error: text };
282
+ trace.push({ key, tool, state: "failed", durationMs: Date.now() - startedAt, error: text });
283
+ onEvent({ type: "script.tool.failed", key, tool, error: text, endedAt: new Date().toISOString() });
284
+ if (!collectFailure) throw new Error(`Tool '${key}' failed: ${text}`);
285
+ return result;
286
+ }
287
+ }));
288
+ });
289
+
290
+ worker.postMessage({ type: "start", script, input });
291
+ });
292
+ }
293
+
294
+ module.exports = { runWorkflowScript };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "surf-cli",
3
- "version": "2.11.0",
3
+ "version": "2.13.0",
4
4
  "description": "CLI for AI agents to control Chrome. Zero config, agent-agnostic, battle-tested.",
5
5
  "keywords": [
6
6
  "chrome",
@@ -10,7 +10,8 @@
10
10
  "agent",
11
11
  "cli",
12
12
  "cdp",
13
- "devtools"
13
+ "devtools",
14
+ "pi-package"
14
15
  ],
15
16
  "author": "Nico Bailon",
16
17
  "license": "MIT",
@@ -28,6 +29,7 @@
28
29
  },
29
30
  "files": [
30
31
  "native/",
32
+ "pi-extension/",
31
33
  "playbooks/",
32
34
  "scripts/",
33
35
  "dist/",
@@ -38,7 +40,7 @@
38
40
  "scripts": {
39
41
  "dev": "vite build --watch --mode development",
40
42
  "build": "vite build",
41
- "check": "tsc --noEmit",
43
+ "check": "tsc --noEmit && tsc --noEmit -p tsconfig.pi-extension.json",
42
44
  "lint": "biome check .",
43
45
  "lint:fix": "biome check --write .",
44
46
  "lint:test": "biome check test/",
@@ -64,11 +66,24 @@
64
66
  "devDependencies": {
65
67
  "@biomejs/biome": "^2.5.4",
66
68
  "@types/chrome": "^0.2.2",
69
+ "@types/node": "^26.1.2",
67
70
  "@vitest/coverage-v8": "^4.1.9",
68
71
  "@vitest/ui": "^4.1.9",
69
- "puppeteer": "25.3.0",
72
+ "puppeteer": "25.4.0",
70
73
  "typescript": "^7.0.2",
71
74
  "vite": "^8.1.4",
72
- "vitest": "^4.1.9"
75
+ "vitest": "^4.1.9",
76
+ "typebox": "^1.3.11"
77
+ },
78
+ "pi": {
79
+ "extensions": [
80
+ "./pi-extension/surf.ts"
81
+ ],
82
+ "skills": [
83
+ "./skills"
84
+ ]
85
+ },
86
+ "peerDependencies": {
87
+ "typebox": "*"
73
88
  }
74
89
  }